在 Mysql 文档中:“ https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlocks-handling.html ”
它提到:“如果您使用锁定读取(SELECT ... FOR UPDATE 或 SELECT ... LOCK IN SHARE MODE),请尝试使用较低的隔离级别,例如 READ COMMITTED。 ”
有人能告诉我为什么我不能使用“可重复阅读”吗?例子会很好。
干杯
在 Mysql 文档中:“ https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlocks-handling.html ”
它提到:“如果您使用锁定读取(SELECT ... FOR UPDATE 或 SELECT ... LOCK IN SHARE MODE),请尝试使用较低的隔离级别,例如 READ COMMITTED。 ”
有人能告诉我为什么我不能使用“可重复阅读”吗?例子会很好。
干杯
如果您使用 read-committed,InnoDB 会避免使用某些类型的锁。这可以帮助您避免死锁。
我为此设计了一个完整的演示文稿:InnoDB Locking Explained with Stick Figures。
但是您几乎永远无法避免 100% 的死锁情况。它们不是错误,它们是并发系统的自然组成部分。您可以减少死锁发生的频率,但您不妨习惯一些。设计代码以捕获异常并在出现死锁时重试数据库操作。
原因是,如果表中不存在记录,则当隔离模式处于活动状态时,对于不存在的记录之后的索引记录,它看起来像在共享模式下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 UPDATE
fromt1
并且不会相互阻塞。对于现有记录,它会在 中的记录上获得 X(排他)锁,因此将被阻塞,直到提交或回滚。但是,如果记录不存在 - 它会在间隙上获取 S(共享)next-key 锁(我不确定它是否真的是 S 锁(它没有记录在任何地方),但是 MySQL 还允许如何在同一个间隙上同时获取 2 个锁?)。这就是这里死锁的主要原因 - 两者都试图在间隙上获取 IX(插入意图)锁,然后在插入的记录上获取 X 锁,但两者都因为锁而相互等待,由.t2
t1
t2
t1
t1
t2
SELECT ... 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
,两个事务都会成功。这对于某些应用程序来说是一个很大的区别。