如何在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操作:
- 开启事务:
START TRANSACTION;
- 对id=1的行加排他锁:
SELECT * FROM test_user WHERE id=1 FOR UPDATE;
此时会话A已经持有id=1行的排他锁。
会话B操作:
- 开启事务:
START TRANSACTION;
- 对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
拓展:共享锁与排他锁冲突的死锁模拟
如果你想模拟另一种场景(共享锁和排他锁冲突导致的死锁),可以调整命令:
- 会话A开启事务后执行:
SELECT * FROM test_user WHERE id=1 LOCK IN SHARE MODE;(获取id=1的共享锁) - 会话B开启事务后执行:
SELECT * FROM test_user WHERE id=2 LOCK IN SHARE MODE;(获取id=2的共享锁) - 会话A尝试获取id=2的排他锁:
SELECT * FROM test_user WHERE id=2 FOR UPDATE;(进入等待) - 会话B尝试获取id=1的排他锁:
SELECT * FROM test_user WHERE id=1 FOR UPDATE;
这时候同样会触发死锁,因为共享锁和排他锁互斥,两个会话形成了循环等待。
额外提醒
- 死锁的核心是循环等待,所以必须至少两个会话,且锁的获取顺序相反。
- InnoDB会自动检测死锁,并回滚代价较小的事务,避免无限等待。
- 你提到的生产环境中查询返回空值,除了死锁导致的事务回滚,也可能和事务隔离级别、快照读的特性有关,死锁只是其中一种可能性。
内容的提问来源于stack exchange,提问作者jump4791




