3-1-1-2-MySQL锁机制
MySQL中锁的分类
1、按照实现方式:
乐观锁、悲观锁;
2、按照作用对象:
Lock、Latch
3、按照锁定对象:
全局锁、表锁、页锁、行锁
4、按照操作类型:
共享锁、独占锁
5、按照用途场景:
记录锁、间隙锁、临键锁、意向锁、插入意向锁、自增锁、元数据锁
我将以结构化表格+关键原理拆解+场景示例的方式,帮你系统梳理MySQL锁的全分类知识体系。以下是按你要求的5大维度展开的内容:
一、按「实现方式」分类:乐观锁 vs 悲观锁
| 维度 | 乐观锁 | 悲观锁 |
|---|---|---|
| 核心思想 | 假设并发冲突少,不提前加锁,通过「数据版本校验」处理冲突 | 假设并发冲突多,提前加锁,直接阻止其他事务修改 |
| 实现原理 | 1. 版本号机制:表中增加version字段,更新时检查version是否等于旧值 2. 时间戳机制:用update_time替代版本号 公式:update t set val=?, version=version+1 where id=? and version=旧版本 |
依赖数据库原生锁机制(如MySQL的SELECT ... FOR UPDATE/LOCK IN SHARE MODE) |
| 用途 | 读多写少场景(如库存扣减、点赞数更新),避免锁竞争提升并发 | 写多读少/强一致性场景(如转账、订单状态变更),确保数据绝对安全 |
| 示例 | 库存扣减:先查version=1,更新时带version=1,若失败则重试 |
转账:BEGIN; SELECT balance FROM account WHERE id=1 FOR UPDATE; -- 扣减余额; COMMIT; |
| 注意事项 | 解决不了「ABA问题」(版本号回绕),需配合递增版本号或时间戳规避 | 长事务会导致锁持有时间长,容易引发阻塞或死锁 |
二、按「作用对象」分类:Latch(门闩) vs Lock(锁)
Latch和Lock都是InnoDB的同步机制,但定位和粒度完全不同:
| 维度 | Latch | Lock |
|---|---|---|
| 核心定位 | 轻量级内存同步原语,保护InnoDB内部短期临界资源(如缓冲池页、数据字典) | 重量级事务同步原语,保护长期数据一致性(如事务中的行/表修改) |
| 实现原理 | 基于Spinlock(自旋锁):线程会循环尝试获取锁,直到成功(避免上下文切换) | 基于Mutex(互斥锁)+ 等待队列:未获取锁的线程进入阻塞队列,等待唤醒 |
| 生命周期 | 极短(纳秒/微秒级),获取后立即释放 | 较长( milliseconds级),伴随事务生命周期 |
| 用途 | 保护缓冲池页的读取/修改(如buf_flush_page)、数据字典的加载 |
事务中的行锁、表锁、间隙锁等 |
| 示例 | InnoDB读取缓冲池中的页时,会加buf0buf.h中的page_mutexlatch |
事务执行UPDATE时,对目标行加row_lock(属于Lock) |
| 关键差异 | 不涉及事务上下文,无死锁检测;Latch冲突会直接自旋,不会阻塞线程 | 支持两阶段锁协议(2PL),会参与死锁检测;Lock冲突会阻塞线程 |
三、按「锁定对象」分类:全局锁→表锁→页锁→行锁
锁的粒度从大到小排列,粒度越细,并发越高,但开销越大:
| 维度 | 全局锁 | 表锁 | 页锁 | 行锁 |
|---|---|---|---|---|
| 锁定范围 | 整个MySQL实例(所有数据库/表) | 单张表 | InnoDB缓冲池中的单个页(16KB) | 表中的单行数据(基于索引) |
| 实现原理 | 通过FLUSH TABLES WITH READ LOCK命令触发,修改global_read_lock状态 |
MyISAM引擎默认支持;InnoDB可通过LOCK TABLES ... READ/WRITE手动加 |
InnoDB内部对缓冲池页的修改加锁(如页分裂时) | 基于B+树索引,对目标行的索引项加锁(如主键/二级索引) |
| 用途 | 全局备份(如mysqldump不支持事务时的替代方案)、禁止所有写操作 |
MyISAM引擎的读写隔离;手动锁定表进行批量操作 | 缓冲池页的并发修改(如页分裂时防止其他线程读取不完整页) | InnoDB事务的行级隔离(如UPDATE某行) |
| 示例 | FLUSH TABLES WITH READ LOCK; -- 备份所有表; UNLOCK TABLES; |
MyISAM:LOCK TABLE orders READ; -- 查询; UNLOCK TABLES; |
InnoDB缓冲池页分裂时,对父页加page_mutex |
UPDATE user SET name='张三' WHERE id=1; -- 对id=1的行加行锁 |
| 注意事项 | 全局锁会导致数据库完全不可写,仅用于紧急备份 | MyISAM表锁并发差(读写互斥),已被InnoDB取代 | 页锁粒度介于表锁和行锁之间,InnoDB中很少手动感知 | 若无索引,行锁会升级为表锁(InnoDB的 fallback 机制) |
四、按「操作类型」分类:共享锁(S) vs 独占锁(X)
这是锁的兼容性基础,所有行锁/表锁都可以归属到这两类:
| 维度 | 共享锁(Shared Lock,S锁) | 独占锁(Exclusive Lock,X锁) |
|---|---|---|
| 别名 | 读锁 | 写锁 |
| 核心规则 | 允许多个事务加S锁,但禁止加X锁 | 仅允许一个事务加X锁,禁止其他事务加S/X锁 |
| 实现原理 | 锁结构中标记lock_mode=S,通过lock_rec_check检查兼容性 |
锁结构中标记lock_mode=X,兼容性检查直接拒绝其他锁 |
| 用途 | 读多写少场景(如报表查询),允许多个事务并发读 | 写场景(如更新/删除),确保数据修改的独占性 |
| 示例 | SELECT * FROM user WHERE id=1 LOCK IN SHARE MODE; -- 加S锁 |
BEGIN; SELECT * FROM user WHERE id=1 FOR UPDATE; -- 加X锁; UPDATE ...; COMMIT; |
| 兼容性矩阵 | S锁 ↔ S锁:兼容 S锁 ↔ X锁:不兼容 X锁 ↔ X锁:不兼容 | (同上,本质是X锁排斥所有其他锁) |
五、按「用途场景」分类:8类锁的深度拆解
这是MySQL锁的核心细分,直接关联事务隔离级别(如RR下的间隙锁):
1. 记录锁(Record Lock)
- 概念:锁定索引中的具体记录(仅锁存在的记录)。
- 实现:锁结构标记
lock_mode=REC_NOT_GAP,作用于B+树的叶子节点。 - 用途:防止其他事务修改/删除目标记录(如
UPDATE id=1)。 - 示例:
SELECT * FROM user WHERE id=1 FOR UPDATE; -- 对id=1的主键记录加记录锁。
2. 间隙锁(Gap Lock)
- 概念:锁定索引记录之间的间隙(锁不存在的区间),解决幻读问题。
- 触发条件:事务隔离级别≥
REPEATABLE READ(RR),且SQL使用范围查询(如BETWEEN/>/<)。 - 实现:锁结构标记
lock_mode=GAP,作用于B+树的间隙(如(10,20))。 - 用途:防止其他事务插入间隙中的数据(如
WHERE id BETWEEN 10 AND 20,锁(10,20)间隙,禁止插入id=15)。 - 示例:
SELECT * FROM user WHERE id BETWEEN 10 AND 20 FOR UPDATE; -- 对(10,20)间隙加间隙锁。
3. 临键锁(Next-Key Lock)
- 概念:记录锁+间隙锁的组合,锁定
前一条记录的末尾到当前记录的区间(即(gap_left, current_record])。 - 默认行为:InnoDB在RR隔离级别下的行锁默认类型。
- 实现:锁结构同时包含记录锁和间隙锁,标记
lock_mode=NEXT_KEY_LOCK。 - 用途:同时防止记录修改和幻读(覆盖记录本身+前后间隙)。
- 示例:
SELECT * FROM user WHERE id=15 FOR UPDATE; -- 对(10,15](假设前一条是10)和(15,20)间隙加临键锁?不,准确说:若id=15存在,临键锁锁的是(prev_gap, 15],比如前一条是10,则锁(10,15];若查询是WHERE id>10,则锁(10,15]+(15,20]`等)。
4. 意向锁(Intention Lock)
-
概念:表级别的锁,表示「事务打算对表中的行加行锁」,用于协调表锁与行锁的冲突。
-
分类:
- 意向共享锁(IS锁):打算加行S锁;
- 意向独占锁(IX锁):打算加行X锁。
-
实现:锁结构标记
lock_mode=IS/IX,存储在表级别的锁表中。 -
用途:避免表锁获取时遍历所有行检查行锁(如表锁要加S锁,只需检查表是否有IX锁,无需遍历行)。
-
兼容性:
IS锁 IX锁 S锁 X锁 IS锁 ✅ ✅ ✅ ❌ IX锁 ✅ ✅ ❌ ❌ S锁 ✅ ❌ ✅ ❌ X锁 ❌ ❌ ❌ ❌ -
示例:事务A对
user表的id=1行加X锁→表级加IX锁;事务B想加表级S锁→检查到IX锁,需等待。
5. 插入意向锁(Insert Intention Lock)
- 概念:间隙锁的特殊类型,表示「事务打算在某个间隙插入数据」。
- 触发条件:插入数据时,对目标间隙加插入意向锁。
- 实现:锁结构标记
lock_mode=INSERT_INTENTION,属于间隙锁的子类。 - 用途:允许并发插入不同的间隙(如事务A插入间隙(10,20)的15,事务B插入(20,30)的25,两者不冲突)。
- 示例:
INSERT INTO user (id) VALUES (15); -- 对(10,20)间隙加插入意向锁。
6. 自增锁(Auto-Inc Lock)
- 概念:InnoDB为自增主键设计的锁,保证自增id的唯一性。
- 触发条件:执行
INSERT时,对自增计数器加锁。 - 模式控制:由
innodb_autoinc_lock_mode参数决定:- 0:连续模式(表级X锁,直到语句结束,保证id连续,但并发低);
- 1:默认交错模式(语句级锁,INSERT完成后立即释放,保证同一语句内id连续);
- 2:完全交错模式(无锁,用原子操作递增,并发最高,但同一语句内id可能不连续)。
- 用途:防止自增id重复(如高并发插入时,保证id唯一)。
7. 元数据锁(Metadata Lock,MDL)
- 概念:表级别的锁,保护数据库元数据(如表结构、索引)的一致性。
- 分类:
- MDL读锁(SHARED_READ):所有DML/查询操作自动加,允许并发读元数据;
- MDL写锁(EXCLUSIVE):DDL操作(如
ALTER TABLE)自动加,禁止其他DML/查询。
- 实现:锁结构存储在
mysql.innodb_dynamic_metadata表中,通过锁管理器同步。 - 用途:防止DDL操作与DML操作冲突(如
ALTER TABLE时,禁止其他事务修改表数据,避免元数据不一致)。 - 坑点:长事务持有MDL读锁→DDL操作无法获取写锁→数据库卡住(需kill长事务)。
六、关键总结:锁的关联与演进
- 锁的粒度:全局锁>表锁>页锁>行锁→粒度越细,并发越高,但开销越大;
- 锁的兼容性:S锁兼容S锁,X锁排斥所有→基于此实现事务的隔离;
- RR与幻读:InnoDB通过临键锁(记录锁+间隙锁)解决幻读,而非MVCC;
- 元数据锁的重要性:长事务会阻塞DDL,需避免(如设置
wait_timeout或监控长事务)。
七、模拟面试提问
假设我问:「InnoDB的临键锁是怎么解决幻读的?请结合B+树结构说明」
- 优秀回答要点:
- 幻读定义:同一事务中,两次相同范围查询返回不同行;
- 临键锁结构:
(前一条记录的末尾, 当前记录]→锁定记录+前面的间隙; - B+树示例:若表有id=10、15、20的记录,查询
WHERE id>10→临键锁锁(10,15]+(15,20]+(20, +inf)→禁止插入id=12/18/25,解决幻读; - 触发条件:RR隔离级别+范围查询。
- 常见不足:忘记提B+树的结构,或混淆间隙锁与临键锁的区别→需明确临键锁是「记录锁+间隙锁」的组合。
如果需要深入某个锁的细节(如间隙锁的排查、自增锁的性能优化),或想模拟面试中的锁问题,随时告诉我!
本文来自博客园,作者:哈罗·沃德,转载请注明原文链接:https://www.cnblogs.com/panhua/p/19210461
浙公网安备 33010602011771号