适用读者:要决定"用哪个引擎、主键怎么定、锁冲突怎么排查"的人。
前置:S11-B06。
实测环境:MySQL 8.0.46 / InnoDB(死锁实测,2026-11-05)。
覆盖旧文:858(111) 859(97) 860(86) 2404(377) 841(80) 842(80) = 831 点;认领 S05 移交的 A-2381、S08 移交的死锁内容。
覆盖扩展:E-S11-007(自增不连续)、E-S11-008(锁升级)、E-S11-020(死锁排查 SOP)。
上一篇:S11-J05 · 日志与恢复:redo / undo / binlog
下一篇:S11-A02 · 部署与迁移。
1. 一句话结论
InnoDB 是唯一该考虑的引擎(事务 + 行锁 + 崩溃恢复);自增主键不连续是正常的;死锁不是"错误",是检测器主动回滚——加锁顺序一致就能避免。
2. InnoDB vs MyISAM(A-838)
| 维度 | InnoDB | MyISAM |
|---|---|---|
| 事务 | ✅ | ❌ |
| 锁粒度 | 行锁 | 表锁 |
| 崩溃恢复 | ✅(redo) | ❌ |
| 外键 | ✅ | ❌ |
| 全文索引 | 5.6+ 支持 | 支持 |
判据:"需要事务吗?" 需要 → InnoDB。MyISAM 在现代业务里基本没有位置(除只读归档)。
3. 自增主键为什么不连续(E-S11-007,A-2404)
| 原因 | 实测/说明 |
|---|---|
delete 不重置 |
实测:删完再插 → id=4(不是 1) |
truncate 才重置 |
实测:→ id=1 |
| 批量插入预分配 | 一次申请 N 个,用完丢弃 |
| 事务回滚 | 已分配的 id 不会退回 |
| 唯一键冲突 | 失败也消耗 id |
"主键不连续是 bug 吗"——不是,它是"性能换连续性"的设计:批量预分配减少锁竞争。
"自增用完了怎么办"(A-841):bigint 上限约 9.2×10^18,实际不会用完;真担心 → 雪花算法/号段(S08-A01)。
4. 锁与死锁(E-S11-008、E-S11-020)
4.1 锁的层次
| 锁 | 粒度 | 说明 |
|---|---|---|
| 行锁 | 行 | 基于索引;没走索引会退化为表锁 |
| 间隙锁 | 索引区间 | RR 下防幻读 |
| 表锁/意向锁 | 表 | DDL、显式 lock tables |
"没走索引会锁全表" 是最容易被忽略的(A-860):update t set ... where 无索引列 → 锁住扫描到的所有行。
4.2 死锁实测
两个会话交叉加锁:
-- A: update t_dl set v=v+1 where id=1; ... update ... where id=2;
-- B: update t_dl set v=v+1 where id=2; ... update ... where id=1;
实测输出:
ERROR 1213 (40001) at line 1: Deadlock found when trying to get lock;
try restarting transaction
SHOW ENGINE INNODB STATUS 的关键段:
LATEST DETECTED DEADLOCK
*** (1) TRANSACTION: ...
*** (1) HOLDS THE LOCK(S): RECORD LOCKS ... lock_mode X locks rec but not gap
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: ... waiting
读法:持有 A 锁、等 B 锁;对方持有 B 锁、等 A 锁 → 环形等待 → 检测器回滚一侧。
4.3 死锁四条件与破坏
| 条件 | 破坏方式 |
|---|---|
| 互斥 | 尽量无锁/乐观锁(A-858) |
| 占有并等待 | 一次性申请 |
| 不可剥夺 | 设置 innodb_lock_wait_timeout(默认 50s) |
| 循环等待 | 统一加锁顺序(最实用) |
4.4 排查 SOP
① 应用侧:ERROR 1213 → 重试(幂等前提下)
② 数据库侧:SHOW ENGINE INNODB STATUS → LATEST DETECTED DEADLOCK
③ 定位:两个事务的 SQL 与加锁顺序
④ 修复:统一顺序 / 缩短事务 / 补索引(避免锁全表)
5. 为什么不推荐外键(A-842)
| 原因 | 说明 |
|---|---|
| 性能 | 每次写入都要检查父表 |
| 分库分表后失效 | 跨库外键不可能 |
| 锁扩散 | 父子表互相加锁,增加死锁概率 |
| 迁移困难 | 改结构受约束顺序限制 |
实践:外键约束放在应用层(或干脆靠业务保证),数据库只做索引。
6. 五个误区
| 误区 | 真相 |
|---|---|
| "自增一定连续" | 不连续是常态 |
| "行锁就是锁一行" | 没走索引会锁更多行 |
| "死锁是数据库 bug" | 是检测器的正常机制 |
| "外键越多越安全" | 性能/死锁/分库都受影响 |
| "MyISAM 更快所以能用" | 无事务,现代业务不可接受 |
7. 自检(架构师版)
- InnoDB 与 MyISAM 的四点差异?
- 自增不连续的五个原因?
- 死锁
ERROR 1213的含义?为什么是"回滚一侧"? - 统一加锁顺序为什么能防死锁?
- 不推荐外键的四条理由?
8. 下一篇
S11-A02 · 部署与迁移:Docker 跑 MySQL、Flyway
锁讲完,下一篇讲"怎么把数据库跑起来、怎么安全改结构"。
本篇属于系列《S11 · 数据库与 SQL》。旧文来源:A-858/859/860/2404/841/842(含 S05/S08 移交的死锁内容);扩展点 E-S11-007/008/020。死锁与自增行为为 MySQL 8.0.46 实测。