4

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

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

有人能告诉我为什么我不能使用“可重复阅读”吗?例子会很好。

干杯

4

2 回答 2

8

如果您使用 read-committed,InnoDB 会避免使用某些类型的锁。这可以帮助您避免死锁。

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

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

于 2017-09-15T04:21:11.497 回答
0

原因是,如果表中不存在记录,则当隔离模式处于活动状态时,对于不存在的记录之后的索引记录,它看起来像在共享模式下SELECT ... FOR UPDATE获取下一个键锁(或类似的东西 - 它没有记录在任何地方)。REPEATABLE READ

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

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因为它也落入同一个间隙,由 锁定SELECT ... FOR UPDATE,在 中执行t1。如果桌子是空的,这个间隙是无穷大的。如果表不是空的,则插入不同记录的死锁概率较小,但仍然很大(这取决于表中有多少间隙以及插入表末尾的频率 - 最大间隙)。

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

READ COMMITED使用事务隔离级别时不存在此问题。SELECT ... FOR UPDATE如果没有找到记录并且READ COMMITED使用了隔离级别,则不持有任何锁。所以第一个INSERT会成功。第二个INSERT将被 first 获取的 EXCLUSIVE 锁阻塞,INSERT而 aftert1将被提交,第二个INSERT将只是 throw Duplicate entry '1' for key 'PRIMARY'

您现在可以认为,这种情况并不比僵局更好。只是另一个错误。但现在想象一下,第二个INSERT尝试插入一条记录id=2。在这种情况下,它不会被阻塞t1,两个事务都会成功。这对于某些应用程序来说是一个很大的区别。

于 2021-11-24T21:25:21.710 回答