为什么我不应该使用 "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 lock当 REPEATABLE 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=] - 最大差距)。
发生这种情况是因为 t1
和 t2
中的 SELECT ... FOR UPDATE
在记录不存在时不会相互阻塞 。对于已存在的记录,它获取 t1
中记录的 X(独占)锁,因此 t2
将被阻塞,直到 t1
被提交或回滚。但是,如果记录不存在 - 它会在间隙上获取 S(共享)下一个键锁(我不确定它是否真的是 S 锁(它在任何地方都没有记录),但是 MySQL 允许在同一个间隙上同时获取 2 个锁?)。这是这里出现死锁的主要原因——t1
和 t2
都试图在间隙上获取 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
阻止,并且两个事务都会成功。对于某些应用程序,这是一个 很大的区别。
在 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 lock当 REPEATABLE 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=] - 最大差距)。
发生这种情况是因为 t1
和 t2
中的 SELECT ... FOR UPDATE
在记录不存在时不会相互阻塞 。对于已存在的记录,它获取 t1
中记录的 X(独占)锁,因此 t2
将被阻塞,直到 t1
被提交或回滚。但是,如果记录不存在 - 它会在间隙上获取 S(共享)下一个键锁(我不确定它是否真的是 S 锁(它在任何地方都没有记录),但是 MySQL 允许在同一个间隙上同时获取 2 个锁?)。这是这里出现死锁的主要原因——t1
和 t2
都试图在间隙上获取 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
阻止,并且两个事务都会成功。对于某些应用程序,这是一个 很大的区别。