锁与并发控制
1. 这是什么
锁与并发控制描述了 MySQL 在多事务并发下如何保证数据一致性。
这是理解数据库写入冲突、阻塞和死锁的核心。
一句话理解:
- 锁不是为了“卡住别人”
- 它是在并发场景下用更少的自由度换正确性
2. 为什么重要
线上系统一旦并发写多起来,锁问题就很容易暴露。
如果不了解锁类型和竞争方式,就很难解释:
- 更新超时
- 请求阻塞
- 死锁回滚
3. 先建立直觉:锁问题通常要和索引一起看
很多人一看到锁,就只盯事务和 SQL。
但真正决定锁范围的,常常还有:
- 是否命中索引
- 索引是不是合理
所以锁分析常常要和索引设计一起看。
4. 核心内容
4.1 行锁、表锁
行锁
- 锁住的是部分记录
- 并发度通常更高
表锁
- 锁住整张表
- 并发自由度更低
学习阶段最重要的不是记定义,而是理解:
- 锁粒度越大,冲突通常越大
4.2 间隙锁、临键锁
在 InnoDB 里,范围条件下的并发控制不只是“锁某一行”这么简单。
为了防止某些并发插入问题,还会涉及:
- 间隙锁
- 临键锁
学习阶段先抓住核心目的即可:
- 控制范围内并发写入
- 避免某些幻读风险
4.3 锁等待
锁等待本质上是:
- 一个事务想拿锁,但锁暂时被别人持有
如果等待时间过长,业务上就会表现成:
- 请求慢
- 超时
- 接口被拖长
4.4 死锁形成机制
死锁通常发生在:
- 事务 A 持有资源 1,等待资源 2
- 事务 B 持有资源 2,等待资源 1
数据库通常会检测死锁并:
- 回滚其中一个事务
所以线上看到死锁错误,不是数据库“坏了”,而是访问顺序和锁路径出了问题。
4.5 并发控制与吞吐权衡
锁更强,一致性更稳;
但锁越多、越久,并发能力通常就越差。
所以数据库并发控制永远是在做权衡:
- 正确性
- 吞吐
- 延迟
5. 学习重点
这一章最重要的是掌握:
- 数据库锁的目标是保证正确性
- 不同 SQL 会带来不同锁行为
- 索引是否命中会直接影响锁范围
- 死锁要回到访问顺序和索引设计上分析
6. 常见问题
6.1 不清楚更新语句实际锁了什么
这是很多并发写问题的起点。
6.2 忽视索引缺失导致锁范围扩大
索引没设计好时,锁问题常常会被放大。
6.3 遇到死锁只会重试,不会定位原因
重试只是缓解,不是解决。
7. 动手验证
当前环境没有 mysql 客户端,所以这里整理成双会话实验步骤。
7.1 准备测试表
sql
DROP TABLE IF EXISTS lock_demo;
CREATE TABLE lock_demo (
id BIGINT PRIMARY KEY,
amount INT NOT NULL
);
INSERT INTO lock_demo (id, amount) VALUES
(1, 100),
(2, 200);7.2 观察锁等待
会话 A
sql
START TRANSACTION;
UPDATE lock_demo SET amount = amount - 10 WHERE id = 1;不要提交。
会话 B
sql
START TRANSACTION;
UPDATE lock_demo SET amount = amount + 10 WHERE id = 1;观察点:
- 会话 B 会等待
7.3 观察死锁
会话 A
sql
START TRANSACTION;
UPDATE lock_demo SET amount = amount - 10 WHERE id = 1;会话 B
sql
START TRANSACTION;
UPDATE lock_demo SET amount = amount - 10 WHERE id = 2;再回会话 A
sql
UPDATE lock_demo SET amount = amount + 10 WHERE id = 2;再回会话 B
sql
UPDATE lock_demo SET amount = amount + 10 WHERE id = 1;观察点:
- 一方会被数据库判定死锁并回滚
8. 练习建议
- 构造一个简单死锁场景
- 对比命中索引和不命中索引的加锁差异
- 总结锁问题排查步骤
- 用自己的话解释“为什么索引会影响锁范围”
9. 自测问题
- 为什么索引会影响锁范围?
- 死锁是怎样形成的?
- 锁等待为什么会拖慢整个业务链路?
- 为什么锁问题经常不能只从 SQL 表面看?
10. 自测核对要点
- 锁粒度和访问路径会直接影响并发能力
- 索引命中情况常常决定锁范围
- 死锁本质上是资源循环等待
- 锁等待会把数据库慢问题传导到整个业务链路