【问题标题】:Deadlock using SELECT ... FOR UPDATE in MySQL在 MySQL 中使用 SELECT ... FOR UPDATE 死锁
【发布时间】:2020-05-28 15:31:19
【问题描述】:

假设我有桌子:

CREATE TABLE t (id INTEGER AUTOINCREMENT NOT NULL, desc TEXT NOT NULL)

我用 1 个元素填充表格:

INSERT INTO TABLE t VALUES (1, 'Hello')

我在 MySQL 中运行了两个事务。在t1 我跑:

START TRANSACTION;
SELECT * FROM t WHERE id = 1 FOR UPDATE;

t2 我跑:

START TRANSACTION;
SELECT * FROM t WHERE id = 1 FOR UPDATE;

此时我希望t1 在行上持有一个 e(X) 独占锁,而t2 等到它可以得到一个 X 锁(并且t2 确实被阻塞了,到目前为止一切都很好)。然后我在t1 中运行更新(没有任何 WHERE 子句!):

UPDATE t SET desc = 'Hello from t1';

此时在t2 我立即得到(无需COMMIT 事务)错误:

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

为什么会出现此错误?我猜t2 正在获得一个锁,需要继续进行完整的 UPDATE,从而造成死锁,但我不明白t2 如何获得锁,因为它应该等待t1 完成.

【问题讨论】:

  • 我无法使用 5.5.27(在 Windows 上运行)重现此问题 - 对我来说这听起来像是一个错误。
  • 你用的是什么版本?
  • MySQL 版本 5.5.35

标签: mysql


【解决方案1】:

什么有效,什么无效

使两个事务都运行而不会出现死锁的一种方法是在两个连接中将isolation level 更改为READ COMMITED(或READ UNCOMMITED):

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

(在start transaction 之前)。

t2 中设置它可能就足够了,但为了确保示例,在两者中都设置它。

更改事务的隔离级别确实会带来一些副作用,在生产环境中更改此设置之前应告知in the manual

关于死锁的状态信息

------------------------
LATEST DETECTED DEADLOCK
------------------------
140424  8:45:46
*** (1) TRANSACTION:
TRANSACTION B6F18A3, ACTIVE 5 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s)
MySQL thread id 13885, OS thread handle 0x7f8b1dbd2700, query id 901012
 localhost root statistics
SELECT * FROM t WHERE id = 1 FOR UPDATE
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 22921 n bits 72 index `PRIMARY` of table
 `test`.`t` trx id B6F18A3 lock_mode X locks rec but not gap waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 00000b6f1883; asc    o  ;;
 2: len 7; hex 06000059a211ea; asc    Y   ;;
 3: len 5; hex 48656c6c6f; asc Hello;;

*** (2) TRANSACTION:
TRANSACTION B6F18A2, ACTIVE 10 sec starting index read
mysql tables in use 1, locked 1
3 lock struct(s), heap size 376, 2 row lock(s)
MySQL thread id 13888, OS thread handle 0x7f8b1f64d700, query id 901068
 localhost root Updating
UPDATE t SET `descc` = 'Hello from t1'
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 22921 n bits 72 index `PRIMARY` of table
 `test`.`t` trx id B6F18A2 lock_mode X locks rec but not gap
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 00000b6f1883; asc    o  ;;
 2: len 7; hex 06000059a211ea; asc    Y   ;;
 3: len 5; hex 48656c6c6f; asc Hello;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 22921 n bits 72 index `PRIMARY` of table
 `test`.`t` trx id B6F18A2 lock_mode X waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 00000b6f1883; asc    o  ;;
 2: len 7; hex 06000059a211ea; asc    Y   ;;
 3: len 5; hex 48656c6c6f; asc Hello;;

*** WE ROLL BACK TRANSACTION (1)

说明

正如 a_horse_with_no_name 提到的,这似乎是 MySQL 中的一个错误。事务 (2) 希望在它已经持有 X 锁的同一行上获得间隙锁。事务 (1) 等待该行上的非间隙 X 锁。我不清楚为什么这个请求会发生冲突。将隔离级别设置为READ COMMITTED 将禁用间隙锁定。由于该示例随后有效,这暗示间隙锁定确实是这里的问题。

【讨论】:

  • 隔离级别(至少是“真正的”事务性 - read uncommitted 不是)不应该有所作为。 T1 中没有的update 不会触及任何尚未锁定的行,等待的 T2 不会等待任何其他行。因此,绝对没有理由发生死锁。从我的角度来看,这是 MySQL 中的一个错误
  • @a_horse_with_no_name 我使用 5.5.35-0+wheezy1-log。我更新了我的答案,还显示了状态输出。仔细一看,我同意你的看法,这似乎是一个错误。虽然我想知道为什么你不能用旧版本的 MySQL 重现错误?
  • @GhostGambler 我认为你的答案中的“什么有效,什么无效”部分应该仅限于说这个错误可以使用 READ COMMITTED 或 READ UNCOMMITTED 隔离级别来解决,如果用户可以处理这些事务的这种隔离。如果您编辑答案,我会接受。我在 mysql (bugs.mysql.com/bug.php?id=72439) 中记录了一个关于此的错误
  • @JoaquinCuencaAbela 我缩短了该部分并添加了一条通知,即在未彻底阅读手册之前不应更改隔离级别。
  • 如果您使用 Isolation Serializable,我可以确认死锁发生在 MySQL 5.7 上。更改为 Read Committed 死锁错误消失了。很好的答案,顺便说一句。
猜你喜欢
  • 1970-01-01
  • 2015-10-21
  • 2019-06-13
  • 1970-01-01
  • 1970-01-01
  • 2020-11-24
  • 1970-01-01
  • 2014-03-10
  • 2010-10-31
相关资源
最近更新 更多