MySQL的锁
MySQL 锁全面扫盲
一、按锁的粒度分
- 全局锁(Global Lock)
1 | FLUSH TABLES WITH READ LOCK; -- 加全局读锁 |
- 整个数据库实例变为只读,所有写操作、DDL、更新事务全部阻塞
- 使用场景:全库逻辑备份(mysqldump)
- 问题:业务完全停摆
- 更好的替代:mysqldump –single-transaction(利用 MVCC 一致性快照,仅限
InnoDB)
- 表级锁(Table-Level Lock)
a) 表锁
1 | LOCK TABLES t READ; -- 表读锁(共享) |
- MyISAM 默认用表锁,InnoDB 一般不用这个
- 粒度大,并发低
b) 元数据锁(MDL, Metadata Lock)
- 不需要显式加,MySQL 5.5+ 自动加
- 对表做 CRUD → 自动加 MDL 读锁
- 对表做 DDL(ALTER TABLE 等)→ 自动加 MDL 写锁
- 经典坑:一个长事务持有 MDL 读锁不提交 → 你去 ALTER TABLE →
被阻塞且后续所有查询全部排队 → 线上雪崩
c) 意向锁(Intention Lock)
- InnoDB 特有的表级标记锁
- 意向共享锁(IS):事务打算对某些行加共享锁前,先在表上加 IS
- 意向排他锁(IX):事务打算对某些行加排他锁前,先在表上加 IX
- 作用:让表级锁判断能快速知道”表里有没有行锁”,避免逐行扫描
- IS/IX 之间互不冲突,只与表级 S/X 锁冲突
d) AUTO-INC 锁
- 对自增列插入时加的特殊表级锁
- MySQL 8.0 默认 innodb_autoinc_lock_mode =
2(交叉模式),只在分配自增值时短暂持有一个轻量级互斥量,不再持有整条 INSERT
语句级别的表锁
- 行级锁(Row-Level Lock)— InnoDB 核心
这是你重点要掌握的部分。InnoDB 的行锁都是加在索引上的,不是加在数据行上。
a) 记录锁(Record Lock)
- 锁住索引上的一条确切记录
- SELECT * FROM t WHERE id = 5 FOR UPDATE; → 对 id=5 这条索引记录加 X 型记录锁
b) 间隙锁(Gap Lock)
- 锁住索引记录之间的间隙,不锁记录本身
- 是一个开区间 (a, b)
- 唯一目的:防止其他事务在间隙中插入新记录 → 解决幻读
- 间隙锁之间不冲突(两个事务可以同时对同一间隙加 Gap Lock)
- 只在 RR(REPEATABLE READ) 隔离级别下生效,RC 下没有间隙锁
c) 临键锁(Next-Key Lock)
- Record Lock + Gap Lock = 左开右闭区间 (a, b]
- InnoDB 在 RR 级别下,默认加的就是 Next-Key Lock
- 举例:索引上有记录 5, 10, 15,那么 Next-Key Lock 可能锁的区间是:
(-∞, 5] (5, 10] (10, 15] (15, +∞)
d) 插入意向锁(Insert Intention Lock)
- 一种特殊的间隙锁,在 INSERT 操作时加
- 多个事务向同一个间隙的不同位置插入时互不阻塞
- 但如果间隙上已有 Gap Lock,INSERT 意向锁会被阻塞 → 这就是很多死锁的根源
二、按锁的模式/兼容性分
共享锁(S Lock / 读锁)
1 | SELECT ... LOCK IN SHARE MODE; -- MySQL 5.x |
- 允许其他事务也加 S 锁,阻止其他事务加 X 锁
排他锁(X Lock / 写锁)
1 | SELECT ... FOR UPDATE; |
- 阻止其他事务加任何锁
兼容矩阵
请求 S 请求 X
持有 S ✅ ❌
持有 X ❌ ❌
三、按加锁方式分
乐观锁(应用层面,不是 MySQL 内置)
1 | UPDATE t SET stock = stock - 1, version = version + 1 |
– 影响行数为 0 → 说明被别人改过了,重试
悲观锁(数据库层面)
1 | SELECT * FROM t WHERE id = 1 FOR UPDATE; -- 先锁住 |
四、行锁加锁规则(面试高频)
InnoDB 在 RR 下的加锁规则可以总结为几条:
两个原则:
- 加锁的基本单位是 Next-Key Lock(左开右闭)
- 只有访问到的索引记录才会加锁
两个优化:
- 唯一索引上的等值查询,命中记录 → Next-Key Lock 退化为 Record Lock
- 普通索引上的等值查询,向右遍历到第一个不满足条件的值 → Next-Key Lock 退化为
Gap Lock
一个 bug(特性):
- 唯一索引上的范围查询,会访问到不满足条件的第一个值为止
举例说明
表数据 id (主键): 5, 10, 15, 20
┌──────────────┬──────────────┬──────────────────────────────────────────┐
│ 查询 │ 加锁范围 │ 解释 │
├──────────────┼──────────────┼──────────────────────────────────────────┤
│ WHERE id = │ 仅锁 id=10 │ │
│ 10 FOR │ 这一条 │ 唯一索引等值命中 → 退化为记录锁 │
│ UPDATE │ (Record │ │
│ │ Lock) │ │
├──────────────┼──────────────┼──────────────────────────────────────────┤
│ WHERE id = 7 │ 间隙锁 (5, │ 唯一索引等值未命中 → Next-Key Lock │
│ FOR UPDATE │ 10) │ (5,10] 退化为 Gap Lock (5,10) │
├──────────────┼──────────────┼──────────────────────────────────────────┤
│ WHERE id >= │ Next-Key │ │
│ 10 AND id < │ Lock (5,10] │ 范围查询,锁到不满足条件的第一个值 │
│ 15 FOR │ + (10,15] │ │
│ UPDATE │ │ │
└──────────────┴──────────────┴──────────────────────────────────────────┘
五、死锁
经典死锁场景
1 | 事务A: SELECT * FROM t WHERE id = 1 FOR UPDATE; -- 锁住 id=1 |
InnoDB 处理方式
- 等待超时:innodb_lock_wait_timeout(默认 50s)
- 死锁检测:innodb_deadlock_detect = ON(默认开启),主动回滚代价最小的事务
- 查看最近死锁:SHOW ENGINE INNODB STATUS;
六、一张图总结
MySQL 锁
├── 按粒度
│ ├── 全局锁 (FTWRL)
│ ├── 表级锁
│ │ ├── 表锁 (LOCK TABLES)
│ │ ├── 元数据锁 (MDL)
│ │ ├── 意向锁 (IS/IX)
│ │ └── AUTO-INC 锁
│ └── 行级锁 (InnoDB only, 加在索引上)
│ ├── 记录锁 (Record Lock)
│ ├── 间隙锁 (Gap Lock)
│ ├── 临键锁 (Next-Key Lock)
│ └── 插入意向锁 (Insert Intention Lock)
├── 按模式
│ ├── 共享锁 (S)
│ └── 排他锁 (X)
└── 按思想
├── 乐观锁 (版本号/CAS, 应用层)
└── 悲观锁 (FOR UPDATE, DB层)
七、面试常问问题速答
┌────────────────────────┬────────────────────────────────────────────────┐
│ 问题 │ 答案 │
├────────────────────────┼────────────────────────────────────────────────┤
│ InnoDB │ 索引,不是数据行。没有索引会退化为表锁 │
│ 行锁锁的是什么? │ │
├────────────────────────┼────────────────────────────────────────────────┤
│ 间隙锁解决了什么问题? │ 幻读(Phantom Read) │
├────────────────────────┼────────────────────────────────────────────────┤
│ RC │ 没有,只有 Record Lock │
│ 隔离级别有间隙锁吗? │ │
├────────────────────────┼────────────────────────────────────────────────┤
│ 死锁怎么排查? │ SHOW ENGINE INNODB STATUS 看 LATEST DETECTED │
│ │ DEADLOCK │
├────────────────────────┼────────────────────────────────────────────────┤
│ 怎么减少锁冲突? │ 控制事务大小、合理使用索引、降低隔离级别(RC) │
└────────────────────────┴────────────────────────────────────────────────┘
核心记住一句话:InnoDB 的锁加在索引上,没索引就锁全表;RR 级别默认是 Next-Key
Lock,等值查询会根据情况退化。把第四部分的加锁规则吃透,面试基本够用了。