You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在MySQL单表中制造死锁?本地复现及命令示例求助

嘿,我来帮你搞定这个单表死锁的模拟问题!首先得明确一个关键前提:MySQL的普通SELECT快照读(一致性读),不会加锁,自然也不会触发死锁。要模拟和SELECT相关的死锁,必须使用加锁读语句,比如SELECT ... FOR UPDATE(排他锁)或者SELECT ... LOCK IN SHARE MODE(共享锁)——只有加锁的操作才会产生锁等待,进而触发死锁。

如何在MySQL中模拟单表SELECT相关的死锁

第一步:准备测试环境

先打开一个MySQL命令行窗口,创建测试表并插入数据:

-- 创建测试表(必须用InnoDB引擎,MyISAM不支持事务和行锁)
CREATE TABLE `test_user` (
  `id` INT PRIMARY KEY AUTO_INCREMENT,
  `name` VARCHAR(50) NOT NULL,
  `age` INT
) ENGINE=InnoDB;

-- 插入测试数据
INSERT INTO `test_user` (`name`, `age`) VALUES ('Alice', 25), ('Bob', 30), ('Charlie', 35);

第二步:用两个会话模拟死锁

你需要打开两个独立的MySQL命令行窗口(我们称之为会话A和会话B),严格按以下顺序执行命令:

会话A操作:

  1. 开启事务:
START TRANSACTION;
  1. 对id=1的行加排他锁:
SELECT * FROM test_user WHERE id=1 FOR UPDATE;

此时会话A已经持有id=1行的排他锁。

会话B操作:

  1. 开启事务:
START TRANSACTION;
  1. 对id=2的行加排他锁:
SELECT * FROM test_user WHERE id=2 FOR UPDATE;

此时会话B已经持有id=2行的排他锁。

回到会话A,尝试获取id=2的排他锁:

SELECT * FROM test_user WHERE id=2 FOR UPDATE;

这时会话A会进入等待状态,因为id=2的锁被会话B牢牢占着。

回到会话B,尝试获取id=1的排他锁:

SELECT * FROM test_user WHERE id=1 FOR UPDATE;

执行这条命令后,MySQL会立即检测到死锁:会话A持有id=1的锁,等待id=2的锁;会话B持有id=2的锁,等待id=1的锁。此时MySQL会自动终止其中一个事务(通常是后发起请求的那个),返回类似如下的错误:

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

拓展:共享锁与排他锁冲突的死锁模拟

如果你想模拟另一种场景(共享锁和排他锁冲突导致的死锁),可以调整命令:

  1. 会话A开启事务后执行:SELECT * FROM test_user WHERE id=1 LOCK IN SHARE MODE;(获取id=1的共享锁)
  2. 会话B开启事务后执行:SELECT * FROM test_user WHERE id=2 LOCK IN SHARE MODE;(获取id=2的共享锁)
  3. 会话A尝试获取id=2的排他锁:SELECT * FROM test_user WHERE id=2 FOR UPDATE;(进入等待)
  4. 会话B尝试获取id=1的排他锁:SELECT * FROM test_user WHERE id=1 FOR UPDATE;
    这时候同样会触发死锁,因为共享锁和排他锁互斥,两个会话形成了循环等待。

额外提醒

  • 死锁的核心是循环等待,所以必须至少两个会话,且锁的获取顺序相反。
  • InnoDB会自动检测死锁,并回滚代价较小的事务,避免无限等待。
  • 你提到的生产环境中查询返回空值,除了死锁导致的事务回滚,也可能和事务隔离级别、快照读的特性有关,死锁只是其中一种可能性。

内容的提问来源于stack exchange,提问作者jump4791

火山引擎 最新活动