MySQL不同隔离级别下的加锁情况
# 简介
InnoDB的四个事务隔离级别:1992标准: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ,和 SERIALIZABLE。默认隔离级别InnoDB是 REPEATABLE READ。
那么在各个隔离级别的加锁情况也略有不同。
# REPEATABLE READ
这是的默认隔离级别 , 同一事务中的一致读取将读取第一次读取建立的快照。这意味着,如果您SELECT 在同一事务中发出多个普通(非锁定)语句,则这些 SELECT语句彼此之间也是一致的。
对于锁定读取 (SELECT使用FOR UPDATE或FOR SHARE), UPDATE和 DELETE语句,锁定取决于该语句使用的是具有唯一搜索条件的唯一索引还是范围类型搜索条件。
- 对于具有等值查询的条件的唯一索引,InnoDB仅锁定找到的索引记录,而不锁定其前的间隙,也就行是只加行级锁不加间隙锁。
- 对于其他查询条件(不管索引类型),使用间隙锁和next-key lock来进行锁定。
# READ COMMITTED
因为READ COMMITTED在执行锁定读取(SELECT 使用FOR UPDATE或FOR SHARE),UPDATE 语句和DELETE 语句的时候,InnoDB仅锁定索引记录,而不锁定它们之间的间隙。所以可以在这条锁定记录旁边插入数据,导致幻读。
加锁情况:
- 对于UPDATE或 DELETE语句, InnoDB仅对其更新或删除的行加锁。MySQL评估WHERE条件后,将释放不匹配行的行级锁 。这大大降低了死锁的可能性,但是仍然可以发生。
- 对于UPDATE语句,如果某行已被锁定,则InnoDB 执行 “semi-consistent” 读取,也就是说将事务A中修改后提交的最新数据反馈给MySQL,让事务B拿到事务A一句提交的最新数据进行比较where匹配情况。也就是读已经提交的数据进行比较。而RR隔离级别就是会直接阻塞等待,不会拿最新提交的数据比较。
CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB; INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2); COMMIT; # Session A START TRANSACTION; UPDATE t SET b = 5 WHERE b = 3; # Session B UPDATE t SET b = 4 WHERE b = 2;
1
2
3
4
5
6
7
8
9
10 - 但是对于等值查询的唯一索引,即使有第二个update来之后,也不会进行semi-consistent读取,而是阻塞住,等待第一个update结束后才进行。
CREATE TABLE t (a INT NOT NULL, b INT, c INT, INDEX (b)) ENGINE = InnoDB; INSERT INTO t VALUES (1,2,3),(2,2,4); COMMIT; # Session A START TRANSACTION; UPDATE t SET b = 3 WHERE b = 2 AND c = 3; # Session B UPDATE t SET b = 4 WHERE b = 2 AND c = 4;
1
2
3
4
5
6
7
8
9
10
# READ UNCOMMITTED
SELECT语句以非锁定方式执行,但可能使用行的早期版本。因此,使用这个隔离级别,这样的读取是不一致的。这也叫脏读。否则,此隔离级别的工作方式与READ COMMITTED类似。
# SERIALIZABLE
这个级别类似于RR,但是InnoDB隐式地将所有纯SELECT语句转换为SELECT ... FOR SHARE 如果禁用自动提交,则为共享。如果启用了自动提交,则选择是它自己的事务。因此,已知它是只读的,如果作为一致(非锁定)读取执行,则可以序列化它,并且不需要阻塞其他事务。(若要强制普通选择阻止其他事务已修改选定行,请禁用自动提交。)
# 参考文档
Transaction Isolation Levels (opens new window)
Locks Set by Different SQL Statements in InnoDB (opens new window)