为什么我不应该使用 "Repeatable Read" 锁定阅读(select..for update)”?

Why I shouldn't use "Repeatable Read" with locking reading (select..for update)"?

在 Mysql 文档中:“https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlocks-handling.html

它提到:“如果您使用锁定读取(SELECT ... FOR UPDATE 或 SELECT ... LOCK IN SHARE MODE),尝试使用较低的隔离级别如 READ COMMITTED."

谁能告诉我为什么我不能使用 "Repeatable Read"?例子会很好。

干杯

如果您使用已提交读,InnoDB 会避免使用某些类型的锁。这可以帮助您避免死锁。

我为此设计了一个完整的演示文稿:InnoDB Locking Explained with Stick Figures

但实际上您永远无法 100% 避免死锁情况。它们不是错误,它们是并发系统的自然组成部分。您可以减少死锁发生的频率,但您最好习惯于获得一些。设计您的代码以捕获异常并在发生死锁时重试数据库操作。

原因是如果 table 中不存在记录,它看起来像 SELECT ... FOR UPDATE 以共享模式(或类似的东西 - 它没有在任何地方记录)获取 next-key lockREPEATABLE READ 隔离模式处于活动状态时,索引记录跟在不存在的记录之后。

让我们尝试一个在 REPEATABLE READ 隔离模式下为空的简单 table t 示例。

t1> SELECT * FROM t WHERE id = 1 FOR UPDATE;
    no rows found, next-key lock acquired in shared mode
t2> SELECT * FROM t WHERE id = 1 FOR UPDATE;
    no rows found, next-key lock acquired in shared mode
t1> INSERT INTO t (id) VALUES (1);
    transaction t1 is blocked by t2
t2> INSERT INTO t (id) VALUES (1);
    transaction t2 is blocked by t1 - deadlock

即使第二个SELECT和INSERT会使用id=2也会出现死锁,因为它也落入同一个间隙,被[=11=锁定],在[=17=执行].如果 table 为空,则此间隙为 infinity。如果 table 不为空插入不同的记录死锁的概率较小,但仍然很大(这取决于 table 中有多少间隙以及插入到 [ 末尾的频率=60=] - 最大差距)。

发生这种情况是因为 t1t2 中的 SELECT ... FOR UPDATE 在记录不存在时不会相互阻塞 。对于已存在的记录,它获取 t1 中记录的 X(独占)锁,因此 t2 将被阻塞,直到 t1 被提交或回滚。但是,如果记录不存在 - 它会在间隙上获取 S(共享)下一个键锁(我不确定它是否真的是 S 锁(它在任何地方都没有记录),但是 MySQL 允许在同一个间隙上同时获取 2 个锁?)。这是这里出现死锁的主要原因——t1t2 都试图在间隙上获取 IX(插入意图)锁,然后在插入的记录上获取 X 锁,但两者都在等待对方,因为锁的,由 SELECT ... FOR UPDATE.

获得

使用READ COMMITED事务隔离级别时不存在该问题。 SELECT ... FOR UPDATE 如果未找到记录并且使用 READ COMMITED 隔离级别,则不会持有任何锁。所以第一个INSERT会成功。第二个 INSERT 将被第一个 INSERT 获取的独占锁阻塞,在 t1 提交后,第二个 INSERT 将抛出 Duplicate entry '1' for key 'PRIMARY'.

你现在可能会想,这种情况并不比死锁好多少。只是另一个错误。但现在想象一下,第二个 INSERT 试图插入一条带有 id=2 的记录。在这种情况下,它不会被 t1 阻止,并且两个事务都会成功。对于某些应用程序,这是一个 很大的区别