并发操作带来的数据不一致性有
-
丢失修改
两个事务读入同一数据
-
不可重复读
2次读取未用数据,前后不一致(为了检验)
-
读“脏”数据
事务A修改数据并写会DB后,事务B读取,事务A因某些原因回滚,事务B此时没意识到,则读取了“脏”数据
封锁协议
-
一级锁协议
事务T在修改数据R之前加X锁,直到事务结束,防止丢失修改
-
二级锁协议
在一级锁协议基础上,事务T在读取数据R之前先加S锁,读完即可释放,防止读脏数据
-
三级锁协议
在一级锁协议基础上,事务T在读取数据R之前先对其加S锁,直到事务结束才释放,防止不可重复读
隔离级别
-
读不提交(Read Uncommited, RU)
事务不隔离,可能产生脏读,可以读取未提交记录,实际中不会使用
-
读提交(Read Commited, RC)
仅读已提交数据,但可能产生不可重复读与幻读,因为在此隔离机制下,每条语句都会读取到已提交事务的更新,若两次查询之间有其他事务提交,将会导致查询结果不同,对于insert也无法保证,但这种隔离机制使用还是比较广泛的
-
可重复读(Repeatable Read, RR)
限定了其他事务对当前事务的删改,从而保证反复读取同一条记录不会发生变动,但未禁止增操作,会发生幻读
-
串行化(Serializable)
消除幻读、不可重复读、脏读,但是并发度下降
幻读与不可重复读的区别
两者都表现为两次读取的结果不一致。
首先,抓住重点,不可重复读重点在于update和delete,而幻读的重点在于insert。
如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据(只是这些),就可以实现可重复 读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会 发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免!!!
死锁的诊断
超时法、等待图法(有环)
可串行化调度
类似java的指令重排序,在单线程里面指令重排但是结果一致,而此相当于对并发事务重排
两端锁协议
为了保证数据库的可串行化,在对数据进行读、写前,要申请并获得对该数据的封锁,但此时不能释放锁(扩展阶段);在释放一个封锁之后,事务不在申请和获得任何其他封锁(收缩阶段)
封锁的粒度
粒度越大,封锁的数据单元越少,并发度越小,系统开销也越小;反之则反
-
显式封锁
直接将锁加到数据对象上
-
隐式封锁
数据对象没有被独立加锁,而是其上级节点解锁而使该数据对象加上锁
一般地,对某个对象加锁,系统要检查数据对象上的显式锁有无冲突,再检查隐式锁,还要检查下级节点的显式加锁是否与本事务的隐式加锁冲突,效率低。因而有了意向锁,系统则无需检查下一节点的显式加锁。
意向锁
含义:如果对一个节点加意向锁,说明该节点的下层节点正在被加锁;对任一节点加锁时,必须先对它的上层节点加意向锁
分为3种:
-
IS
若对一个数据对象加IS锁,表示它的后裔节点准备加S锁
-
IX
若对一个数据对象加IX锁,表示它的后裔节点准备加X锁
-
SIX
若对一个数据对象加SIX锁,表示对它加S锁,再加IX锁
意向锁兼容矩阵:(+表示兼容,-表示不兼容)
/ | IS | IX | S | X |
---|---|---|---|---|
IS | + | + | + | - |
IX | + | + | - | - |
S | + | - | + | - |
X | - | - | - | - |
- S、X、IS、IX锁兼容性矩阵为什么是这样子呢?
- 意向锁之间彼此不会冲突,因为它们都只是“有意”,而不是真干,所以是可以兼容的。在加行锁之前,会使用意向锁判断是否冲突;
- IX和X的关系等同于X和X之间的关系,为什么呢?因为事务获得了IX锁,接下来就有权利获取X锁,这样就会出现两个事务都获取X锁的情况,这和我们已知的X锁和X锁之间互斥是矛盾的;
- S和IS、X和IS、IX和IS也可以由此推导出来。