事务、MVCC 与锁
InnoDB 使用 MVCC 处理普通一致性读,使用锁协调写入、当前读和结构变更。MVCC 不等于没有锁:UPDATE、DELETE、SELECT ... FOR UPDATE、外键检查和 DDL 都可能等待锁。
隔离级别
| 隔离级别 | 一致性读 | 主要特点 |
|---|---|---|
| Read Uncommitted | 可见未提交变化 | 很少用于业务系统 |
| Read Committed | 每条一致性读建立新 Read View | 减少部分 Gap Lock,同一事务可能不可重复读 |
| Repeatable Read | 首次一致性读后通常复用 Read View | InnoDB 默认;当前读仍使用锁 |
| Serializable | 普通 SELECT 也可能按锁定读处理 | 一致性强但并发成本高 |
快照读和当前读必须区分:普通 SELECT 通常读取 Read View;FOR UPDATE、FOR SHARE 和 DML 读取当前版本并加锁。
MVCC
InnoDB 记录包含事务标识和指向 Undo 旧版本的回滚指针。Read View 根据活跃事务范围判断当前版本是否可见;不可见时沿 Undo Version Chain 查找合适版本。
长事务维持旧 Read View,会阻止 Purge 清理历史版本。事务即使没有持续执行 SQL,只要未提交仍可能影响整个实例。
锁类型
| 锁 | 保护对象 | 说明 |
|---|---|---|
| Record Lock | 索引记录 | InnoDB 行锁实际锁索引项 |
| Gap Lock | 索引记录之间的间隙 | 阻止在间隙插入,不锁定已有记录本身 |
| Next-Key Lock | Record + 前方 Gap | Repeatable Read 下范围当前读的常见形式 |
| Insert Intention | 准备插入的间隙位置 | 不同位置插入可并发,但会与冲突 Gap Lock 等待 |
| Intention Lock | 表级意向 | 表明事务准备获取行级 S/X Lock |
| Auto-Increment Lock | 自增值分配 | 行为受自增锁模式和批量写入影响 |
| Metadata Lock | 表、Schema 等元数据 | DML 持有共享 MDL,DDL 需要不兼容的排他 MDL |
间隙不是固定的“左开右闭业务区间”,而是索引顺序中的记录间空间。具体锁范围取决于访问索引、查询谓词、隔离级别、唯一性和最终执行计划。
唯一索引等值查询命中唯一记录时通常只需要 Record Lock;非唯一范围扫描可能锁定扫描到的记录和间隙。没有合适索引时,扫描范围和锁影响可能显著扩大。
MDL 与 DDL 阻塞
事务访问表后会持有 Metadata Lock 直到事务结束。一个长期未提交事务可能让 ALTER TABLE 等待;等待中的 DDL 又可能阻塞后续请求,形成连接堆积。
处理前应先查清阻塞链,而不是立即重启:
sql
SELECT * FROM performance_schema.metadata_locks;
SELECT * FROM performance_schema.data_lock_waits;
SELECT * FROM information_schema.innodb_trx;死锁
死锁是事务形成循环等待,InnoDB 检测后选择一个事务回滚。应用应把死锁和 Lock Wait Timeout 视为可重试错误,并保证事务幂等。
降低死锁概率:
- 多事务按相同顺序访问对象和行。
- 保持事务短小,不在事务中等待外部接口或人工操作。
- 为过滤条件建立正确索引,缩小扫描与加锁范围。
- 批量修改分段执行,但保持明确一致性边界。
- 保存 Deadlock Log 和完整 SQL/参数进行复盘。