一、脏读
脏读是指在不同的事务下,当前事务可以读取到另外事务未提交的数据。
以下例子说明了脏读发生的场景:
脏读发生的条件是事务隔离级别为READ UNCOMMITED。
二、不可重复读
在一个事务中,两次读到的数据出现不一致情况 ,称为不可重复读,也就是上面说的幻像问题。
不可重复读发生时的事务隔离级别为READ COMMITED。一般来说,不可重复读是可以接受的,因为读到的是已经提交的数据,不会带来很大的问题。
三、丢失更新
一个事务的更新操作被另一个事务的更新操作所覆盖,从而导致数据不一致。
一般情况下,数据库在更新记录时加X锁,不会出现后面的更新操作覆盖前面操作的情况。
在应用程序方面可能出现丢失更新:
1)事务1查询一条数据,放入本地缓存,并展示到终端用户USER1;
2)事务2也查询该数据,并将取得的结果显示给终端用户2。
3)User1更新这条记录,更新数据库并提交。
4)User2更新这条记录,更析数据库并提交
在这个过程当中,User1的修改更新操作丢失了,在这种情况下,需要让事务在更新时并行化处理。
四、阻塞
参数innodb_lock_wait_timeout是用来控制锁等待的时间(默认50秒),innodb_rollback_on_timeout用来设定是否是等待超时对事务进行回滚操作(默认OFF,不回滚)。
当发现锁超时等待时,会抛出一个异常:ERROR:1205:Lock wait timeout exceeded;try restart transaction;
五、死锁
1、死锁的概念和检测原理
死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成相互等待的现象。解决死锁最简单的方法是超时,即当两个事务等待时,一个等待超时阈值时,回滚,另一个等待的事务就能进行。超时的参数是innodb_lock_wait_timeout。
当前数据库还使用wait-for graph(等待图)的方式来进行死锁检测,wait-for graph要求数据库保存以下两种信息:
• 锁的信息链表
• 事务等待链表
通过以上链表,可以构造出一个图,若这个图中存在回路,那就代表存在死锁,因此资源间相互等待。事务为图中的节点,事务t1指向事务t2的边定义为:
• 事务t1等待事务t2所占用的资源;
• 事务t1最终等待事务t2,也就是事务之间在等待相同的资源,而事务t1发生在事务t2之后。
示例:当前的事务和锁的状态如下图:
从Transaction Wait Lists可以看出有t1~t4四个事务,因此在wait-for graph中有四个节点。而事务2对row1战胜x锁,事务1对row2占用S锁,事务1需要等待事务2中row1的资源,因此wait-for graph有条边从t1指向t2。事务t2 需要等待事务t1、t4占用的row2对象,故存在节点t2到t1、t4的边,同样,存在节点从t3到节点t1、t2、t4的边。因此最终wait-for graph如图所示:
可以看到存在回路(t1,t2),因此存在死锁。若存在死锁,InnoDB引擎一般选择回滚undo量最小的事务。
2、死锁示例
下表显示了一种经典的死锁,即A等待B,而B在等待A,这种死锁问题称为AB-BA死锁。
此外还有另一种死锁,即当前事务持有了待插入记录的下一个记录的X锁,但在等待队列中有一个S锁请求,则可以发生死锁,如下:
CREATE TABLE t7(a INT PRIMARY KEY) ENGINE=INNODB;
INSERT INTO t7 VALUES(1),(2),(4),(5);
执行下表的查询:
可以看到会话A持有了记录4的X锁,但因为插入记录3引起死锁,是因为会话B中的S锁引起了等待,而这里死锁后回滚的是undo log记录大的事务。
六、锁升级
锁升级(Lock Escalation)是指将当前锁的粒度降低,如果数据库可以把1000个行锁升级为一个页锁,或者将页锁升级为表锁。
InnoDB引擎不存在锁升级问题,因为其不是根据每行记录来产生锁的,相反,是根据每个事务访问每个页对锁进行管理的,采用的是位图的方式,因此不管理 事务锁住一个页中的一条记录还是多条记录,开销都是一致的。
参考《MySQL技术内幕 -InnoDB存储引擎》整理,如侵权请联系vinin@163.com。