多个事务并发运行时的并发问题
第一类丢失更新:撤销一个事务时,把其他事务已提交的更新数据覆盖
脏读:一个事务读到另一个事务未提交的数据
虚读:一个事务读到另一个事务已提交的新插入的数据
不可重复读:一个事务读到另一个事务已提交的更新数据。
第二类丢失更新:不可重复读中的特例,一个事务覆盖另一个事务的更新数据
数据库锁的基本原理
1.事务执行select语句时,要先获得共享锁,insert,delete等更新语句时,要先获得独占锁。
2.第二个事务执行select语句时,要先获得共享锁,执行更新语句时要获得独占锁。要根据资源上锁的类型,来决定是否等待第一个事务释放对资源的锁定,还是马上获得锁。
锁的多粒度性及自动锁的升级
数据库中能够锁定的资源有:数据库,表,区域(锁定数据库的特定区域),页面(锁定数据库的特定页面),键值(带有索引的行数据),行(当行数据)
锁的粒度越大,事务的隔离性越高,事务之间的并发性能越低。一般数据库都会把锁自动升级,就是多个低级锁来升级成高级锁,来降低系统负荷。
锁的类型及兼容性
共享锁:用于读数据操作,它是非独占的,允许其他事务同时读取锁定的资源,但不允许更新。
1.执行select时,加锁。
2.默认情况下,数据读取后就解除了共享锁,例如select * from a.先锁定第一行,读取,释放,锁定第二行。读取释放。
3.如果资源上被放置了共享锁,还能放置共享锁和更新锁。
4,具有良好的并发性能
独占锁:也加排他锁,锁定的资源不能读取也不能修改。
1.更新操作语句时,加锁,如果有了独占锁,就无法在放置。
2.事务结束释放锁。
3.不与其他锁兼容。
4.并发性能差,只允许一个事务锁定特定的数据,其他事务必须等到释放锁后才能操作这些数据。
更新锁:在更新操作的初始化阶段锁定可能要被修改的资源。可以避免共享锁造成的死锁问题。
update a set xx=xx where a.name='hello'
先获得共享锁,然后升级为独占锁。有两个事务时,同时获得共享锁,然后要升级共享锁,但是独占锁要等待其他事务释放共享锁,所以所有的事务都等待了,就造成了死锁。
1.update语句时分配更新锁
2.读取数据后执行更新操作时,把更新锁升级为独占锁。
3.更新锁和共享锁兼容,但是只能有一个更新锁。
4.允许其他事务同时读锁定的资源,但是不允许修改。
死锁及其防止办法
死锁是指多个事务分别锁定了一个资源,又试图请求锁定对方已经锁定的资源,那么所有的事务就会等待别的事务释放锁。
1.合理安排表的访问顺序。
2.使用短事务
3.如果对一致性要求不高,可以允许脏读,脏读不需要加锁,可以避免锁冲突。
4.可能的话,错开访问相同资源的时间,防止锁冲突。
5.尽可能使用事务隔离级别低的事务
数据库的事务隔离级别
1.Serializable:串行化
2.RepeatAble Read:可重复读
3.Read Commit:读已提交数据
4.Read Uncommit:读未提交数据。
串行化:一个事务完全看不到其他事务对数据的更新。
可重复读:可以看到其他事务已提交的新插入的数据,看不到对已有记录的更新。
读已提交数据:可以看到插入的记录,也可以看到已提交更新的记录
读未提交数据:可以看到插入的记录,也可以未提交的更新记录
在程序中采用悲观锁
悲观锁:应用程序显示的为资源加锁。悲观锁假定当前资源会被同时访问,先加锁,可以防止不可重复读和丢失更新。
乐观锁:假定资源没有竞争,完全让数据库隔离级别来操作数据。
悲观锁的两种实现方式
1.显式的指定独占锁来锁定资源。select ... for update就可以指定独占锁。
2.在数据库中指定一个表明状态的字段,为Y表示锁定,为N表示没锁定。
Hibernate版本控制来实现乐观锁
1.使用<version>
表中有一个version字段
<class>
<id>
<version name="" column/>//必须紧跟id
</class>
hibernate执行update语句时,先根据缓存中对象的version去匹配数据库
update xxx set version=1 where xxx and version=0
然后version加一,更新到session,因为其他事务得到的对象还是原来的,是游离对象,多以其他事务再执行上面的语句会报StaleObjectStateException.
有两种方式处理:
1.自动撤销事务
2.通知用户数据被修改了,是否继续事务,还是撤销事务
还可以使用<timestamp>跟version一样,只是这个是时间,version是数字加1。timestamp可能比version不安全,因为可能两个事务的时间是一样的。
2.对游离对象进行版本检查
session.lock(对象,LockMode.READ)
lock设定为LockMode.READ,则马上进行版本检查,不会计划执行语句。数据库没有匹配到就包StaleObjectStateException。update()方法不会马上检查,会计划执行一条语句。等到清理缓存时,如果
数据库没有匹配到就包StaleObjectStateException。