用chrome和firefox(或chrome无痕+正常窗口)各开一个phpmyadmin会话,均执行set autocommit=0,再通过交叉顺序执行select ... for update锁定不同行(如会话a锁id=1再锁id=2,会话b锁id=2再锁id=1),即可稳定触发死锁。
怎么用两个浏览器标签页触发死锁
phpmyadmin 默认每个标签页是独立会话,但共享同一用户权限和数据库连接池,这刚好满足死锁复现的最基本条件:两个并发事务互相持有对方需要的锁。
关键不是“开两个 phpMyAdmin 窗口”,而是确保两个会话不串(比如别在同一个浏览器窗口里用 Ctrl+T 新建标签页后又切回去操作,容易被 session 复用或自动提交干扰)。
- 用 Chrome 和 Firefox 各开一个 phpMyAdmin 登录页,或 Chrome 无痕 + 正常窗口
- 两个会话都先执行
SET autocommit = 0,关掉自动提交 - 不要点“执行”后立刻切走——phpMyAdmin 的查询框焦点可能滞留,导致你误以为执行了其实没点确认
- 每个会话只操作一张表的两行数据,且交叉加锁顺序(A 先锁 row1 再锁 row2,B 先锁 row2 再锁 row1)
为什么 UPDATE + WHERE 主键会导致行锁但不一定死锁
死锁不是靠“锁得多”触发的,而是靠“加锁顺序不一致”。即使你用 UPDATE users SET status=1 WHERE id=100 这种主键更新,InnoDB 也会对目标行加 X 锁,但单条语句本身不会死锁——除非另一个事务正以相反顺序锁着 id=101 并试图锁 id=100。
常见误区:以为 SELECT ... FOR UPDATE 才算“显式加锁”,其实 UPDATE 和 DELETE 在事务中默认就是带 X 锁的。
- 务必用
SELECT * FROM accounts WHERE id IN (1, 2) FOR UPDATE预占锁,比直接UPDATE更可控 - 避免在事务里混用
SELECT(无锁)、SELECT ... LOCK IN SHARE MODE、SELECT ... FOR UPDATE—— 锁类型不同,升级逻辑复杂,容易绕晕 - MySQL 5.7+ 默认隔离级别是
REPEATABLE READ,gap lock 可能意外锁住区间,测试时建议先确认SELECT @@tx_isolation
怎么一眼看出死锁发生在哪两条语句之间
phpMyAdmin 本身不显示死锁详情,但 MySQL 服务端会记录。你需要去查 SHOW ENGINE INNODB STATUS\G 的输出,重点看 LATEST DETECTED DEADLOCK 区块。
这个区块里会明确写出两个事务各自执行到哪条 SQL、持有哪些锁、等待什么锁、最后谁被回滚。注意它只保留最近一次死锁信息,所以必须在触发后立刻查。
- 在任意一个 phpMyAdmin 查询框里执行
SHOW ENGINE INNODB STATUS,然后 Ctrl+F 搜DEADLOCK - 如果看到
*** (1) TRANSACTION:下面有mysql tables in use 1, locked 1,说明该事务只锁了一张表一行,另一方也类似——这是典型交叉加锁 - 如果其中一方出现
lock_mode X locks gap before rec insert intention waiting,说明是插入意向锁和 gap lock 冲突,多半因为 WHERE 条件用了非唯一索引或范围查询
为什么 SET innodb_lock_wait_timeout=1 不适合模拟死锁
这是最常踩的坑:innodb_lock_wait_timeout 控制的是“等锁超时”,不是“死锁检测”。它会让事务卡住 1 秒后报 Lock wait timeout exceeded,但 MySQL 根本没进入死锁检测流程——也就不会触发自动 rollback,更不会写入 INNODB STATUS 的死锁日志。
真正触发死锁检测的是 InnoDB 自带的 waits-for graph 分析器,毫秒级运行,不需要你调任何参数。你唯一要做的,就是让两个事务真的卡住彼此。
- 别动
innodb_deadlock_detect(默认 ON),关了它就真死等了 - 别设
innodb_lock_wait_timeout过小,否则你还没来得及看INNODB STATUS,事务就被踢了 - 如果两次执行都得到
Deadlock found when trying to get lock,但INNODB STATUS里没新记录,说明上一次死锁日志已被覆盖——立刻重查
死锁复现的关键不在“多快”,而在“错开加锁时机”:一个会话执行完第一条 SELECT ... FOR UPDATE 后,必须等另一个会话也执行完它的第一条,再各自执行第二条。中间那几百毫秒的窗口,就是死锁发生的全部条件。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











