锁机制全解析:表锁 / 行锁 / 间隙锁 / Next-Key Lock 一次讲透.
线上 80% 的数据库性能问题 ,根本原因都不是 SQL,而是:锁冲突 & 锁等待 & 死锁。
真正的 MySQL 高手,拼的不是 SQL 技巧,而是:对锁机制的理解深度。
一、真实业务场景引入
你是否遇到过:
-
明明走索引,SQL 依然慢?
-
QPS 不高,数据库却经常卡死?
-
事务频繁阻塞?
-
线上偶发死锁?
最终排查发现: 锁冲突严重。
问题来了:
-
MySQL 锁了什么?
-
锁住了哪几行?
-
锁为什么这么大?
二、MySQL 锁机制整体架构
从层级看,MySQL 锁分为:
-
表锁(Table Lock)
-
行锁(Row Lock, InnoDB)
从语义看,锁分为:
-
共享锁(S Lock)
-
排他锁(X Lock)
从实现看,InnoDB 行锁细分为:
-
Record Lock(记录锁)
-
Gap Lock(间隙锁)
-
Next-Key Lock(记录锁 + 间隙锁)
三、表锁(Table Lock)
锁住整张表,粒度最大,并发性能最低。
常见实用方式:
cs
lock tables user read;
lock tables user write;
- 读锁(Read Lock / S)
bash
lock tables user read;
效果:
-
自己可读
-
别人可读
-
别人不可写
- 写锁(Write Lock / X)
cs
lock tables user write;
效果:
-
自己可读可写
-
别人不可读不可写
- 表锁的应用场景
-
MyISAM 存储引擎
-
备份
-
批量离线任务
InnoDB 生产环境: 极少使用表锁
四、行锁(Row Lock)
InnoDB 的核心竞争力:行级锁
特点:
-
锁粒度小
-
并发能力强
- 行锁本质
InnoDB 的行锁:是加在索引上的,不是直接锁数据行。
- 行锁触发条件
sql
select * from user where id = 10 for update;
前提:必须命中索引, 否则: 行锁 → 退化为 表级锁扫描
五、共享锁(S Lock) vs 排他锁(X Lock)
|-------|---|---|
| 锁类型 | 读 | 写 |
| 共享锁 S | ✔ | ✘ |
| 排他锁 X | ✔ | ✔ |
- 共享锁(S Lock)
cs
select * from user where id=1 lock in share mode;
特点:允许多个事务同时读取, 禁止写.
- 排他锁(X Lock)
sql
select * from user where id=1 for update;
特点: 独占锁, 禁止其他事务读 & 写.
六、Record Lock(记录锁)
锁住单条索引记录
sql
select * from user where id = 10 for update;
锁住:id = 10 这一行
七、Gap Lock(间隙锁)
锁住索引之间的"空隙",防止插入新记录
sql
select * from user where id > 5 and id < 10 for update;
已有数据:6,8
锁区间:(5,6), (6,8), (8,10)
任何事务都不能插入 7、9
八、Next-Key Lock(终极核心)
Next-Key Lock = Record Lock + Gap Lock
这是 InnoDB 在 RR 隔离级别的默认锁策略
示例拆解:
索引值:1, 5, 10, 20
sql
select * from t where id = 10 for update;
加锁区间:(5,10]
不仅锁住 `10`,还锁住 `(5,10)` 整个区间。
九、为什么要有间隙锁 & Next-Key Lock?
- 解决幻读
cs
事务 A:
select * from t where id between 5 and 10;
事务 B:
insert into t values(7,...);
如果没有间隙锁:A 前后两次读取结果不同 → 幻读
- RR 解决方案:MVCC + Next-Key Lock
-
MVCC 解决历史可见性
-
Next-Key Lock 锁住插入区间
从读取 & 写入两个方向同时封死幻读
十、锁与索引的致命关系(生产必懂)
是否命中索引,决定你锁几行。
-
命中索引:行锁
-
未命中索引:全表扫描 + 行行加锁 → 近似表锁
-
线上事故案例
sql
update user set status=1 where mobile='13800138000';
如果 `mobile` 没索引:全表扫描 + 每行加锁 → 线上雪崩
十一、当前读 vs 快照读
- 快照读(无锁)
sql
select * from user where id=1;
走:MVCC
- 当前读(加锁)
sql
select * from user where id=1 for update;
update user ...
delete user ...
走:锁机制
十二、锁等待 & 死锁简析
- 锁等待
-
事务 A 占锁
-
事务 B 等锁 → 进入等待队列
可通过下面的语句查看:
css
show engine innodb status\G
- 死锁经典案例
事务 A:
bash
update t set v=1 where id=1;
update t set v=1 where id=2;
事务 B:
bash
update t set v=1 where id=2;
update t set v=1 where id=1;
产生:循环等待 → 死锁
InnoDB 自动:回滚代价较小事务
十三、Go 工程实践规范(敲黑板)
-
事务中统一访问顺序
所有业务统一按主键递增顺序访问,从根源杜绝死锁
-
事务一定要短
事务时间 = 锁持有时间
-
所有 update / delete 必须走索引
工程规范:禁止非索引条件更新
十四、经典面试题
- MySQL 有哪些锁?
答:表锁、行锁、共享锁、排他锁、间隙锁、Next-Key Lock
- Next-Key Lock 是什么?
答:记录锁 + 间隙锁,用于解决幻读
- 为什么 update 没索引很危险?
答:全表扫描 + 行行加锁 → 并发雪崩
十五、今日总结
-
表锁 vs 行锁
-
共享锁 vs 排他锁
-
间隙锁 & Next-Key Lock
-
索引与锁的强耦合关系
源码地址*
1、https://pan.baidu.com/s/1B6pgLWfSgMngVeFfSTcPdg?pwd=jc1s
如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!