Mysql 日志分析结果
我先读取这个 CSV 文件,了解日志内容的结构和数据。
我已经读取了日志文件,这是一个 MySQL InnoDB 死锁(Deadlock)日志,问题非常明确。下面我给你一份完整分析。
🔍 日志分析结果
一、问题本质:InnoDB 死锁(Deadlock)
日志中反复出现:
1 | InnoDB: Transactions deadlock detected, dumping detailed information. |
这是 MySQL 检测到死锁后,主动回滚其中一个事务以解除死锁的标准行为。
二、涉事 SQL(所有死锁的共同点)
1 | DELETE FROM `times_original` |
涉及的时间段:2026-06-19 ~ 2026-07-01,fm 值为 9 或 4。
三、死锁根因:两个索引交叉等待
这是典型的 索引锁交叉死锁,两个事务各自持有对方需要的锁:
| 事务 | 已持有锁(HOLDS) | 等待锁(WAITING FOR) |
|---|---|---|
| Tx 1 | index date 的 X 锁 |
index account_id 的 X 锁 |
| Tx 2 | index account_id 的 X 锁 |
index date 的 X 锁 |
形成闭环,死锁发生。
四、为什么会发生这种交叉锁?
核心原因:DELETE 语句没有最优索引,导致分别走不同索引,锁的顺序不一致。
- 并发执行两条 DELETE,分别用不同的
account_id - 事务 A 先锁
account_id索引记录,再去锁date索引 - 事务 B 先锁
date索引记录,再去锁account_id索引 - 互相等待 → 死锁
此外,日志中还有以下佐证:
LOCK WAIT+ 大量lock struct(有的事务积累了 1500+ 个锁结构),说明锁持有时间长、范围广fetching rows状态说明 DELETE 在扫描大量行Got an error reading communication packets:客户端因等待超时而断开,说明锁等待时间很长
五、死锁发生时间线(部分)
| 时间 | account_id | 回滚事务 |
|---|---|---|
| 20:09:40 | 5787 | Tx (1) |
| 19:22:36 | 6734 | Tx (1) |
| 17:53:28 | 6655 | Tx (1) |
| 17:14:30 | 5874 | Tx (2) |
| 17:10:24 | 6626 | Tx (2) |
| 16:29:24 | 5872 | Tx (2) |
| 15:56:20 | 6701 | Tx (1) |
| 21:27:26(昨天) | 7949 | Tx (1) |
死锁非常频繁,几乎每小时都在发生。
六、解决方案(按优先级)
✅ 方案 1(最重要):建复合索引
1 | CREATE INDEX idx_times_original_account_date_fm |
效果:DELETE 只走一个索引,锁顺序一致,从根本上消除交叉锁死锁。
⚠️ 建索引前请确认现有索引:
1 SHOW INDEX FROM `times_original`;如果已有
(account_id, date, fm)的开头前缀索引,则无需重复创建。
✅ 方案 2:降低并发度
多个应用线程同时执行同表 DELETE 是死锁的催化剂。可以:
- 对同一个
account_id的删除操作加分布式锁(如 Redis 锁),保证串行化 - 或在应用层用队列控制并发
✅ 方案 3:缩小事务范围
日志中看到部分事务持有 1000+ 个行锁(LOCK WAIT 1484 lock struct(s)),说明单次 DELETE 影响行数过多。
改为分批删除:
1 | DELETE FROM `times_original` |
循环执行直到删除完毕,减少单次事务锁持有量。
✅ 方案 4:调整隔离级别(辅助手段)
如果业务允许,考虑将事务隔离级别从 REPEATABLE READ 改为 READ COMMITTED:
1 | SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; |
这可以减少 Gap Lock 的范围,降低死锁概率(但不能根治)。
七、总结
| 问题 | 原因 | 解决 |
|---|---|---|
| 死锁频繁 | 无最优复合索引,两索引交叉加锁 | 建 (account_id, date, fm) 复合索引 |
| 锁等待时间长 | 单次 DELETE 影响行数过多 | 分批 DELETE + LIMIT |
| 客户端超时断开 | 锁等待超时 | 降低并发 + 优化索引 |
首要任务:确认 times_original 表现在有哪些索引,然后创建复合索引。如果你能把 SHOW CREATE TABLE times_original; 的结果发我,我可以帮你确认最准确的索引优化方案。