XAMPP中需配置innodb_print_all_deadlocks=1和log_error路径以持久化记录死锁日志,重启MySQL后所有死锁信息将追加至指定错误日志文件末尾的LATEST DETECTED DEADLOCK块中。

怎么看 XAMPP 里 MySQL 的死锁日志
XAMPP 默认不记录完整死锁日志,SHOW ENGINE INNODB STATUS\G 只保留最后一次死锁快照,容易被覆盖。必须先开启持久化记录:
- 编辑
XAMPP\mysql\bin\my.ini(Windows)或/Applications/XAMPP/xamppfiles/etc/my.cnf(macOS),在[mysqld]下添加:
innodb_print_all_deadlocks = 1 log_error = "C:/xampp/mysql/data/mysql_error.log"
注意路径要真实可写,Windows 下推荐用正斜杠或双反斜杠;重启 MySQL 服务后,所有死锁都会追加进该日志文件。
别只盯着错误弹窗里的 ERROR 1213 (40001): Deadlock found when trying to get lock —— 这只是结果,真正线索藏在 mysql_error.log 末尾的 LATEST DETECTED DEADLOCK 块里。
死锁日志里关键字段怎么对应到你的 SQL
一个典型片段:
*** (1) TRANSACTION: TRANSACTION 198765, ACTIVE 0 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s) MySQL thread id 123, query id 456 localhost ::1 root updating UPDATE account SET balance = balance - 10 WHERE user_id = 'U001' *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 12 page no 5 n bits 72 index PRIMARY of table `test`.`account`
重点抓三行:
-
query id 456 ... UPDATE account ...→ 这是触发等待的那条 SQL,确认它是否真走索引(用EXPLAIN UPDATE account SET balance = balance - 10 WHERE user_id = 'U001'验证) -
WAITING FOR THIS LOCK ... index PRIMARY→ 它想锁主键,但很可能是因为user_id字段没索引,导致全表扫描,最终锁住主键页 - 往上翻同一事务块里最近的
INSERT/UPDATE/DELETE,再结合表结构看:如果user_id是VARCHAR类型,而你传了数字WHERE user_id = 123,就会隐式转换、索引失效
本地高并发测试时死锁复现率低?检查这几点
用 sysbench 或 Python 多线程脚本压测时,经常跑几十次才出一次死锁,不是没发生,而是条件没凑齐:
- 两个事务必须几乎同时启动,且执行顺序交错 —— 在代码里加
time.sleep(0.001)微调时机,比纯随机更易复现 - 确保操作的是同一张表的**不同行但相同索引页**:
user_id值尽量接近(如'U001'和'U002'),InnoDB 行锁以 page 为单位管理,跨页冲突概率骤降 - 关闭 autocommit 后手动
BEGIN/COMMIT,否则单条语句自动提交,锁瞬间释放,根本构不成循环等待 - 避免用
SELECT * FROM ... FOR UPDATE开头 —— 它会提前锁住一堆行,干扰真实业务逻辑下的锁竞争路径
为什么加了索引还是死锁?常见翻车点
建了 INDEX idx_user_status (user_id, status),但查询写成 WHERE status = 'active',照样全表扫 —— 索引不是摆设,得每次都被真正用上:
-
EXPLAIN中key字段为空,或type是ALL/index,说明没走你期望的索引 - 联合索引最左前缀失效:查
status = ?不走(user_id, status),但查user_id = ? AND status = ?就可以 - 函数操作废掉索引:
WHERE DATE(create_time) = '2026-06-17'→ 改成create_time >= '2026-06-17' AND create_time -
IN列表过大(超过 200 项),MySQL 可能放弃索引走全表,尤其在REPEATABLE READ隔离级别下,间隙锁范围爆炸式增长
真正的难点不在发现死锁,而在确认「哪一行、哪个索引、哪条 SQL」在每一次死锁中都稳定复现 —— 日志里 space id 和 page no 是物理定位锚点,比业务 ID 更可靠。


















