MySQL 锁
覆盖:核心概念、锁类型、加锁方式、锁等待、死锁、诊断、优化、实战案例。
MySQL 主流存储引擎是 InnoDB,锁机制围绕事务、行级锁、MVCC 等展开。理解锁的目的:
- 保证事务隔离
- 防止并发冲突
- 保证数据一致性
- 提高并发性能(行锁比表锁更 granular)
- 锁:阻塞写,部分情况下阻塞读
- MVCC:让读尽量不加锁
- 事务隔离级别:控制读写冲突何时需要锁
掌握这三者的关系,是理解 MySQL 并发行为的基础。
下面是 MySQL 里所有重要锁(完整体系化):
| 类型 | 说明 |
|---|---|
| 全局锁(Global Lock) | FLUSH TABLES WITH READ LOCK(FTWRL);逻辑备份常用;影响全库 |
| 表级锁(Table Lock) | MySQL 层面的表锁;DDL 使用 |
| 元数据锁(MDL) | 保证 DDL 与 DML 不冲突 |
| 行级锁(Row Lock) | InnoDB 的核心锁;精细锁粒度 |
| 间隙锁(Gap Lock) | 阻止幻读;锁不存在的记录范围 |
| 临键锁(Next-Key Lock) | 行锁 + 间隙锁;RR 隔离级别默认 |
| 插入意向锁(Insert Intention Lock) | 插入前的范围锁,用于协调 Insert 并发 |
| 属性 | 类型 | 说明 |
|---|---|---|
| 读锁 | S 锁(Shared Lock) | 可共享读 |
| 写锁 | X 锁(Exclusive Lock) | 独占写 |
| 意向锁 | IS, IX | 用于加速判断是否能在行级加锁 |
| AUTO-INC 锁 | 自增字段上的轻量锁 | INSERT 时序保证 |
| 隐式锁 | insert 时自动产生,不需要显式锁结构 | MVCC + undo 支持 |
| 锁类型 | 说明 |
|---|---|
| Record Lock(记录锁) | 锁单行记录 |
| Gap Lock(间隙锁) | 锁一个范围但不含目标记录 |
| Next-Key Lock(临键锁) | 记录锁 + Gap;用于防止幻读 |
| Insert Intention Lock | INSERT 时对 gap 加的锁 |
- 事务隔离级别(如 RR 会自动加 gap/next-key)
- 是否走索引
- 是否是精准匹配(索引等值查询)
🔥 重点:没有走索引 = 锁全表 = 性能灾难
必须牢记这一点。
以下规则帮助你准确判断 SQL 会锁什么。
SELECT * FROM users WHERE id = 10 FOR UPDATE;
-> 加 record lock(单行)
SELECT * FROM users WHERE age = 20 FOR UPDATE;
可能锁住多个范围,而不是单条。
SELECT * FROM users WHERE age > 20 FOR UPDATE;
-> 锁整个范围 -> 容易出现锁等待
SELECT * FROM users WHERE name LIKE '%abc%' FOR UPDATE;
-> 锁全表(灾难级)
多个插入能并发执行,不会相互阻塞。
SELECT * FROM information_schema.innodb_locks;
SELECT * FROM information_schema.innodb_lock_waits;
SELECT * FROM information_schema.innodb_trx;
SELECT * FROM performance_schema.data_lock_waits;
SHOW ENGINE INNODB STATUS;
重点看:
- LATEST DETECTED DEADLOCK
- 哪个事务被杀
- 锁类型(record, next-key, gap)
- 触发语句
- 必须使用 索引(避免锁全表)
- 等值查询尽量使用 唯一索引
- 避免范围查询 + FOR UPDATE
- 避免大事务(大事务锁范围大)
- DML 语句必须带条件(WHERE / LIMIT)
- 尽快提交事务(短事务)
- 减少业务逻辑放在事务内部
- 尽量按固定顺序访问表(避免死锁)
- 禁止事务里执行外部 RPC 或耗时操作
- 减少锁冲突 -> 最小化热点
- 用 Redis 或分布式锁降低数据库争抢
- MySQL 里热点计数可以使用:
- 分库拆表
- 乐观锁(version 字段)
- 行级锁控制更新顺序(FOR UPDATE)
| 参数 | 说明 |
|---|---|
| innodb_lock_wait_timeout | 锁等待时长 |
| innodb_deadlock_detect | 死锁检测开关 |
| innodb_print_all_deadlocks | 打印所有死锁 |
| transaction_isolation | 建议使用 RC 提升并发 |
UPDATE orders SET status=1 WHERE id > 1000;
- 锁住巨大范围
- 大量锁等待
解决:分批更新
UPDATE orders SET status=1 WHERE id > 1000 LIMIT 500;
SELECT * FROM user WHERE email LIKE '%@gmail.com%' FOR UPDATE;
- 索引失效
- 锁全表
解决:改成反向存储等方式
- 事务 A:先锁用户表 -> 再锁订单表
- 事务 B:先锁订单表 -> 再锁用户表
-> 环路 -> 死锁
解决:访问顺序统一
user -> order 统一顺序即可避免大多数死锁
- MySQL 锁 = 行锁 + 间隙锁 + 临键锁 + 意向锁 + MDL + AUTO-INC
- 使用索引 = 避免行锁退化为表锁
- RR 隔离级别 = 默认产生 Next-Key 锁
- 死锁本质 = 两个事务循环等待
- 最好的手段 = SQL 优化 + 索引设计 + 事务最小化