你们生产环境的 MySQL 中使用了什么事务隔离级别?为什么?

Sherwin.Wei Lv7

你们生产环境的 MySQL 中使用了什么事务隔离级别?为什么?

回答重点

MySQL 数据库的默认隔离级别是 RR(可重复读),但是很多大公司把隔离级别改成了 RC(读已提交),主要原因是为了提高并发降低死锁概率

为了解决幻读的问题 RR 相比 RC 多了间隙锁(gap lock)和临键锁(next-key lock)。而 RC 中修改数据仅用行锁,锁定的范围更小,因此相比而言 RC 的并发更高。

扩展知识

RC 和 RR 区别实际例子

来看下例子,感受更深刻一些。

创建如下的表,并插入一些记录(以下例子参考 MySQL 官网 ):

1
2
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);

此时执行 SQL-A,且未提交事务:

1
UPDATE t SET b = 5 WHERE b = 3;

在 RR 即可重复隔离级别情况下,会锁哪几条数据呢?

1
2
3
4
5
x-lock(1,2); 保留 x-lock
x-lock(2,3); update(2,3) to (2,5); 保留 x-lock
x-lock(3,2); 保留 x-lock
x-lock(4,3); update(4,3) to (4,5); 保留 x-lock
x-lock(5,2); 保留 x-lock

可以看到全锁了,此时执行 SQL-B:

1
UPDATE t SET b = 4 WHERE b = 2;

就会被阻塞了。

而执行 SQL-A 在 RC 即读已提交隔离级别下,会锁哪几条数据呢?

1
2
3
4
5
x-lock(1,2); unlock(1,2)
x-lock(2,3); update(2,3) to (2,5); 保留 x-lock
x-lock(3,2); unlock(3,2)
x-lock(4,3); update(4,3) to (4,5); 保留 x-lock
x-lock(5,2); unlock(5,2)

可以看到,只锁了两条数据,此时执行 SQL-B 会怎样?

1
2
3
4
5
x-lock(1,2); update(1,2) to (1,4); 保留 x-lock
x-lock(2,3); unlock(2,3)
x-lock(3,2); update(3,2) to (3,4); 保留 x-lock
x-lock(4,3); unlock(4,3)
x-lock(5,2); update(5,2) to (5,4); 保留 x-lock

可以看到仅锁了 b = 2 的数据,完美避开了 SQL-A 加的锁。此时可能有同学会有疑问:SQL-B 不是应该被 (2,3) 这行的锁给阻塞吗?

我们再扩展一个知识点,半一致性读(“semi-consistent” read)。

这其实也是 InnoDB 做的一个优化。在执行 update 的时候,扫描发现当前行已经被锁定了,它就会执行半一致性读的操作,得到当前数据的最新版本(上述中 SQL-A 锁定的行最新版本的 b 都为 5),来判断是否和当前的(SQL-B) update 的 where 条件匹配,如果匹配则说明当前的 update 也需要锁定这行,因此需要等待。如果不匹配说明它们之间没关联,因此不需要等待锁,这个优化提升了并发度。

所以 RC + 半一致性读能进一步的提升 SQL 执行的并发度。

并且 RC 锁的粒度更小,意味着死锁的概率会更低,但是缺点是可能会产生幻读,这个就需要业务自己评估幻读的问题(大部分情况下都没啥影响)。

MySQL 可重复读能完全避免幻读的发生吗?

Comments