第5章:锁机制(完整版)
🎯 本章目标
弄懂 MySQL 的各种锁类型及作用
理解 InnoDB 如何实现高并发控制
掌握死锁场景、排查与解决方案
弄清楚 MVCC 与锁的关系
一、锁的分类总览
🔒 1. 粒度分类
锁类型 | 粒度 | 说明 |
---|---|---|
表锁 | 表级别 | 一次锁整张表 |
行锁 | 行级别 | 精细到某一行 |
意向锁 | 表级别元信息 | 标识是否打算加行锁 |
🔀 2. 模式分类
锁模式 | 含义 |
---|---|
共享锁 (S) | 多个事务可读 |
排它锁 (X) | 只有一个事务可写 |
📌 InnoDB 默认使用行级锁 + 意向锁组合来实现高并发。
二、意向锁的作用
意向锁不是业务加的,而是 InnoDB 自动加的表级锁。
🔍 目的:
解决“表锁 vs 行锁”冲突判断的问题
举例:
若事务 T1 想对某行加排它锁,InnoDB 会先加“意向排它锁 (IX)”到表上
这样其他事务想加表锁就能快速判断是否有冲突
三、行锁实现与加锁范围
📌 行锁是通过 索引加锁实现的。
SELECT * FROM user WHERE id = 5 FOR UPDATE;
✅ 会加锁成功(id 是主键)
SELECT * FROM user WHERE name = '张三' FOR UPDATE;
❌ name 无索引,会退化为表锁!
锁的种类(InnoDB 内部)
类型 | 说明 |
---|---|
Record Lock | 行锁,锁定某一行数据 |
Gap Lock | 间隙锁,锁定两个记录间的空隙 |
Next-Key Lock | 行 + 间隙,用于防止幻读 |
四、死锁与检测机制
死锁是两个或多个事务互相持有资源,形成循环等待。
常见死锁场景:
-- T1:
BEGIN;
UPDATE user SET age = 30 WHERE id = 1;
-- T2:
BEGIN;
UPDATE user SET age = 40 WHERE id = 2;
-- T1:
UPDATE user SET age = 40 WHERE id = 2; -- 阻塞
-- T2:
UPDATE user SET age = 30 WHERE id = 1; -- 死锁发生!
InnoDB 的解决方案:
默认启用 死锁检测(选择牺牲一个事务)
可通过:
SHOW ENGINE INNODB STATUS\G
查看最近一次死锁信息。
避免死锁建议:
避免不同事务更新同一张表的不同顺序
一次性加锁(批量更新)
保持事务短小
五、MVCC 与锁的关系
MVCC(多版本并发控制)是 InnoDB 提高并发性能的核心机制。
它的作用:
避免读操作加锁 → 快速返回数据
📘 两种读方式:
类型 | 是否加锁 | 说明 |
---|---|---|
快照读 | ❌ | SELECT 不加锁,读取旧版本(多版本) |
当前读 | ✅ | SELECT ... FOR UPDATE / UPDATE / DELETE |
实现方式:
每行记录维护两个隐藏字段:
trx_id
、roll_pointer
undo log 存历史版本
SELECT 判断版本可见性:只能看到自己“开始时”已经提交的事务版本
📌 快照读 = 无锁读取一致性视图;当前读 = 加锁读当前数据
六、实验与练习题
🧪 实验:验证 FOR UPDATE 是否加锁
-- 会加锁
SELECT * FROM user WHERE id = 1 FOR UPDATE;
-- 无索引字段:可能退化为表锁!
SELECT * FROM user WHERE name = '张三' FOR UPDATE;
✅ 自测题
表锁和行锁谁并发性能更好?
✅ 行锁
什么是 next-key lock?
✅ 行锁 + 间隙锁,用于防幻读
InnoDB 如何避免幻读?
✅ 使用 next-key lock
快照读是否加锁?MVCC 是如何实现的?
✅ 不加锁,基于 undo log + 版本判断
七、图解锁结构与事务并发控制
[InnoDB 锁体系结构]
表级:意向锁(IS/IX)
↑
行级:Record Lock(精确)
Gap Lock(防止插入)
Next-Key Lock(复合)
事务并发:
- MVCC 快照读提升性能
- 当前读强一致性控制
- 死锁检测避免系统卡死
📣 下一章我们将深入 第6章:事务与日志系统,包括:
undo log / redo log / binlog 的区别和用途
两阶段提交协议原理
MySQL 如何保证事务的 ACID 特性