MySQL 锁机制与全局锁全景指南
【核心主旨】:锁是数据库在并发环境下保障数据一致性与隔离性的核心屏障。MySQL 中的锁按照作用粒度可分为全局锁(Global Lock) 、表级锁(Table Lock)与行级锁(Row Lock)。全局锁用于全库逻辑备份,表级锁控制整张表的读写与结构变更,行级锁控制高并发精细化行数据修改。
一、 全局锁全景解析(Global Lock)
1. 全局锁物理定义与机制
全局锁是对**整个数据库实例(Database Instance)**加的锁。加锁后,整个数据库实例将强制处于只读(Read-Only)状态。
sql
┌─────────────────────────────────────────────────────────────────────────────┐
│ 全局读锁 (FLUSH TABLES WITH READ LOCK) │
│ ├─ DQL (SELECT 查询) ➔ 允许通行(绿色) │
│ ├─ DML (INSERT/UPDATE/DELETE) ➔ 强制阻塞等待(红色锁死) │
│ └─ DDL (CREATE/ALTER/DROP) ➔ 强制阻塞等待(红色锁死) │
└─────────────────────────────────────────────────────────────────────────────┘
2. 典型使用场景:全库逻辑备份
全局锁的核心应用场景是进行全库逻辑备份(如 mysqldump) 。在备份过程中锁定所有的表,从而获取物理一致性快照视图(Consistency View),保证导出数据的完整性与账目对齐。
3. 不加锁备份的物理灾难:数据不一致
若在全库逻辑备份过程中不加全局锁,由于线上业务写操作持续进行,按顺序导出的各表之间会产生严重的数据错乱:
- 场景 :备份包含
tb_stock(库存表)、tb_order(订单表)、tb_orderlog(日志表)。 - 事故 :先备份了
tb_stock(库存 = 100);在备份tb_order过程中,用户下单购买 1 件商品(库存变 99,生成 1 条新订单);最后备份了tb_order(备份到了新订单)。 - 结果 :导出的备份文件中库存未扣减(100),但订单表却多出了一笔订单,导致财务账目对不上、订单有记录但库存没扣的脏数据灾难。
4. 全局锁实战演示与语法流程
sql
-- 1. 登录 MySQL 客户端,对整个数据库实例加全局读锁
FLUSH TABLES WITH READ LOCK;
-- 2. 在 Linux 操作系统终端执行逻辑备份指令(导出 SQL 文件)
mysqldump -uroot -p1234 itcast > itcast.sql
-- 3. 备份完成后,在 MySQL 客户端中释放全局锁(恢复读写)
UNLOCK TABLES;
5. 大厂高并发无锁一致性备份方案(--single-transaction)
全局锁对线上业务伤害极高(主库无法写入、从库主从延迟)。在生产环境中,若存储引擎全为 InnoDB,推荐使用 mysqldump --single-transaction 开启 MVCC 快照读进行无锁一致性备份!
二、 表级锁全景解析(Table-level Lock)
表级锁每次操作锁定整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用于 MyISAM、InnoDB、BDB 等存储引擎中。表级锁主要分为三类:
scss
┌─────────────────────────────────────────────────────────────────────────────┐
│ 表级锁三大分类 │
│ ├─ 1. 表锁 (Table Lock) ➔ 表共享读锁 (READ) / 表独占写锁 (WRITE) │
│ ├─ 2. 元数据锁 (MDL Lock) ➔ 防 DML 读写数据与 DDL 修改表结构产生冲突 │
│ └─ 3. 意向锁 (Intention Lock) ➔ 表大门挂标识 (IS/IX),实现 O(1) 表锁校验 │
└─────────────────────────────────────────────────────────────────────────────┘
1. 表锁(表共享读锁 read vs 表独占写锁 write)
① 语法指令
sql
-- 1. 手动显式给表加锁
LOCK TABLES 表名1 READ; -- 加表共享读锁
LOCK TABLES 表名2 WRITE; -- 加表独占写锁
-- 2. 释放表锁(或客户端断开连接时自动释放)
UNLOCK TABLES;
② 表锁读写兼容性矩阵表
| 锁指令 | 加锁者自身权力 | 其他客户端权力 | 物理核心总结 |
|---|---|---|---|
LOCK TABLES t1 READ (读锁) |
允许读 t1(禁止写 t1) |
允许读,禁止写(阻塞挂起) | 大家都可以看,但谁也别想改! |
LOCK TABLES t1 WRITE (写锁) |
允许读,也允许写 t1 |
禁止读,也禁止写(全线阻塞) | 只有我自己能读写,别人啥也不能干! |
2. 元数据锁(Metadata Lock, MDL)
MDL 锁由存储引擎自动控制 ,无需显式使用。主要作用是维护表结构元数据的一致性,防止在有活动事务时对表结构进行修改(避免 DML 与 DDL 冲突)。
① MDL 锁分类与 SQL 对应矩阵表
| 对应的 SQL 语句操作 | MDL 锁类型 | 兼容性与阻塞规则说明 |
|---|---|---|
LOCK TABLES xxx READ / WRITE |
SHARED_READ_ONLY / SHARED_NO_READ_WRITE |
显式表锁级别的 MDL |
SELECT / SELECT ... LOCK IN SHARE MODE |
SHARED_READ |
与 SHARED_READ, SHARED_WRITE 兼容;与 EXCLUSIVE 互斥 |
INSERT / UPDATE / DELETE / SELECT ... FOR UPDATE |
SHARED_WRITE |
与 SHARED_READ, SHARED_WRITE 兼容;与 EXCLUSIVE 互斥 |
ALTER TABLE / DROP TABLE / TRUNCATE |
EXCLUSIVE (排他锁) |
与所有其他 MDL 锁全部互斥!(改表结构大魔王) |
② 生产环境 MDL 大坑与全过程死锁演绎
很多开发者常在业务高峰期执行 ALTER TABLE user ADD COLUMN age INT;,引发整张表甚至全库瘫痪,其物理死锁演变过程如下:
text
【事故时间线演绎】:
1. 10:00:00 ➔ 事务 A (慢查询):执行 SELECT * FROM user WHERE status = 1;
- 状态:需要 30 秒,持有 MDL 共享读锁 (SHARED_READ),事务未提交。
2. 10:00:02 ➔ 程序员执行:ALTER TABLE user ADD COLUMN age INT;
- 状态:申请 MDL 排他锁 (EXCLUSIVE)。
- 物理效果:由于 EXCLUSIVE 与 SHARED_READ 互斥,ALTER TABLE 被强制【在门外挂起死等】!
3. 10:00:03 ➔ 全网高并发请求涌入:后续所有新的 SELECT / UPDATE / INSERT 申请 SHARED_READ / SHARED_WRITE 锁
- 灾难效果:因为排在被挂起的 ALTER TABLE 后面,全被【串联阻塞挂起】!
4. 10:00:05 ➔ 几秒钟内数据库连接池(Max Connections)被瞬间塞爆,全站所有读写业务瘫痪崩溃!
③ 生产环境 3 大工业级修改表结构(ALTER TABLE)解法
解法 1:低谷期 + 调短锁超时时间(lock_wait_timeout 防卡死)
在凌晨 3:00~5:00 业务低谷期执行,并在执行 ALTER TABLE 前设置当前会话的 MDL 锁超时:
sql
-- 将当前会话的 MDL 锁等待超时时间设置短为 3 秒(默认是整整 1 年!)
SET lock_wait_timeout = 3;
-- 执行表结构修改
ALTER TABLE user ADD COLUMN age INT;
- 物理防死锁机制 :如果刚好有长事务未提交,
ALTER TABLE拿不到 MDL 排他锁 3 秒自动报错退回(Fast Fail),绝对不会挂死在数据库里塞爆连接池。
解法 2:MySQL 5.6+ 原生 Online DDL(ALGORITHM=INPLACE)
- 从 MySQL 5.6 开始,
ALTER TABLE只在开始和结束一瞬间获取 MDL 排他锁,中间漫长的结构变更过程 MDL 自动降级,允许全网并发执行INSERT/UPDATE/DELETE/SELECT!修改过程中的增量数据由row log日志暂存后合并。
解法 3:千万/亿级大表无锁改表架构(影子表 gh-ost / pt-online-schema-change)
对于千万/亿级特大表,使用 gh-ost 或 pt-online-schema-change 工具实现 "影子表原子替换":
text
【老表 user (线上持续并发读写)】 ──(Binlog / 触发器实时同步)──> 【影子表 _user_new (执行加字段,零业务干扰)】
│
【数据追平瞬间执行原子重命名】:RENAME TABLE user TO _user_old, _user_new TO user; (微秒级完成切换!)
-
暗中建影子表 :工具在后台偷偷建一张结构相同的临时表
_user_new; -
空表改结构 :在空影子表上执行
ALTER TABLE加字段(空表修改,瞬间完成); -
流式追平数据:监听 Binlog 将老表存量与并发增量流式复制到影子表;
-
瞬间原子替换 :数据追平瞬间,执行微秒级重命名指令:
sqlRENAME TABLE user TO user_old, user_new TO user;- 物理效果 :在 MySQL 引擎底层,
RENAME TABLE仅仅是交换了文件名指针,耗时 < 0.001 秒 。应用程序上一微秒查老表,下一微秒查加完字段的新表。24小时不停机、全过程 0 阻塞、0 卡顿、0 锁表!
- 物理效果 :在 MySQL 引擎底层,
3. 意向锁(Intention Lock - IS / IX)
① 意向锁解决的物理痛点
在 InnoDB 中,若没有意向锁,事务 B 申请表锁(LOCK TABLES t WRITE)时,必须从第 1 行逐行扫描到第 1,000,000 行去检查是否有行锁 ( O(N) 灾难)。有了意向锁,事务 A 加行锁时自动在表大门挂上意向锁标识,事务 B 抬眼看表大门标识即可完成校验( O(1) 秒级判断)!
② 意向锁分类与兼容互斥矩阵
- 意向共享锁 (IS) :
SELECT ... LOCK IN SHARE MODE自动添加。 - 意向排他锁 (IX) :
INSERT/UPDATE/DELETE/SELECT ... FOR UPDATE自动添加。
意向锁与意向锁之间(IS 与 IS、IS 与 IX、IX 与 IX)100% 完全兼容! 意向锁仅与手动加的【表锁(
LOCK TABLES)】产生互斥:
| 意向锁类型(表大门标识) | 与手动表共享锁 (LOCK TABLES ... READ) |
与手动表排他锁 (LOCK TABLES ... WRITE) |
意向锁与意向锁之间兼容性 |
|---|---|---|---|
| 意向共享锁 (IS) | 兼容放行(大家一起看) | 互斥挂起(表写锁霸道独占) | 100% 兼容 |
| 意向排他锁 (IX) | 互斥挂起(防数据被修改) | 互斥挂起(双写互斥) | 100% 兼容 |
三、 行级锁全景解析(Row-level Lock)
行级锁每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。主要应用于 InnoDB 存储引擎中。
【物理核心原则】:InnoDB 的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对物理记录加的锁!如果更新/查询未命中索引,行锁会退化升级为表锁!
1. 共享行锁 (S) 与排他行锁 (X) 兼容性矩阵
- 共享锁 (S - Share Lock) :允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁(如
SELECT ... LOCK IN SHARE MODE)。 - 排他锁 (X - Exclusive Lock) :允许获得排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁(如
UPDATE、DELETE或SELECT ... FOR UPDATE)。
行锁 S/X 兼容性矩阵表:
| 当前已被持有的行锁类型 | 申请 S (共享行锁) | 申请 X (排他行锁) | 物理判定逻辑 |
|---|---|---|---|
| S (共享行锁) | 兼容(允许并行读取) | 冲突(强制阻塞等待) | S 与 S 兼容,S 与 X 冲突 |
| X (排他行锁) | 冲突(强制阻塞等待) | 冲突(强制阻塞等待) | X 锁与任何行锁均冲突 |
2. 行级锁三大物理分类 (Record / Gap / Next-Key)
- 行锁 (Record Lock) :锁定单个索引行记录,防止其他事务对此行进行
UPDATE和DELETE。RC 与 RR 隔离级别均支持。 - 间隙锁 (Gap Lock) :锁定索引记录之间的间隙(不含记录本身),确保间隙不变,防止其他事务在这个间隙中进行
INSERT插入新行,杜绝幻读。仅在 RR 隔离级别下支持。 - 临键锁 (Next-Key Lock) :行锁 + 间隙锁的套餐组合拳 。同时锁住某条记录本身及其前面的索引间隙 Gap。左开右闭区间
(A, B],为 RR 隔离级别下默认加锁算法。
3. MVCC 与 行锁、间隙锁、临键锁的终极物理协同矩阵
在 InnoDB RR(可重复读)隔离级别下,MVCC 与行级三锁形成了完美的分工协作:
| 数据库操作类型 | 读取/写入类型 | 依赖的核心物理机制 | 是否触发 Record 行锁 | 是否触发 Gap 间隙锁 / Next-Key 临键锁 | 解决的核心并发异象 |
|---|---|---|---|---|---|
普通 SELECT |
快照读 | MVCC (Read View + Undo) | 否 | 否 | 脏读、不可重复读、快照读下的幻读 |
点对点 UPDATE / DELETE / FOR UPDATE |
当前读 | 行锁 (Record Lock) | 是 | 降级为否 | 写写冲突、丢失更新 |
范围扫描 UPDATE / DELETE / FOR UPDATE |
当前读 | 临键锁 (Next-Key Lock) | 是 | 是 (锁定索引间隙防 INSERT) | 当前读场景下的幻读 |
四、 间隙锁与临键锁加锁与退化三大规则
在可重复读(REPEATABLE READ)隔离级别下,InnoDB 使用 Next-Key Lock 进行搜索和索引扫描以防止幻读。在具体查询执行时,锁会根据索引类型(唯一索引 vs 普通索引)以及查询条件(等值 vs 范围)发生精准的物理退化优化。
【红字核心原则】:间隙锁的唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁绝对不会阻止另一个事务在同一间隙上采用间隙锁!
┌─────────────────────────────────────────────────────────────────────────────┐
│ 间隙锁与临键锁退化三大规则 │
│ ├─ 规则 1:唯一索引等值查询 ➔ 给不存在的记录加锁,降级退化为【间隙锁】 │
│ ├─ 规则 2:普通索引等值查询 ➔ 向右遍历到不满足条件的第一个值时退化为【间隙锁】│
│ └─ 3. 唯一索引范围查询 ➔ 扫描访问到不满足条件的第一个值为止 │
└─────────────────────────────────────────────────────────────────────────────┘
1. 规则一:唯一索引等值查询(给不存在的记录加锁 ➔ 退化为间隙锁)
当对主键或唯一索引 进行等值查询,且查询的目标记录在表中物理不存在 时,Next-Key Lock 会自动降级退化为 间隙锁(Gap Lock)。
sql
-- 假设唯一索引 id 列已有物理记录值为:1, 3, 8
-- 事务 A 执行:查询不存在的 id = 5 并加排他锁
SELECT * FROM stu WHERE id = 5 FOR UPDATE;
-- 物理锁分析:
-- 1. id = 5 记录不存在;
-- 2. 原本的 Next-Key Lock 范围 (3, 8] 降级退化为纯【间隙锁 Gap Lock】:开区间 (3, 8);
-- 3. 物理效果:锁定了 3 到 8 之间的空白缝隙,防止其他事务执行 INSERT id 值为 4, 5, 6, 7 的新记录!
2. 规则二:普通索引等值查询(向右遍历退化为间隙锁)
当对普通二级索引 (非唯一索引)进行等值查询时,为了防止在索引值相同的区间插入新行,InnoDB 会向右扫到第一个不满足条件的索引值,并将该值上的 Next-Key Lock 退化为 间隙锁(Gap Lock)。
sql
-- 假设普通二级索引 age 列已有物理记录值为:10, 15, 20
-- 事务 A 执行:查询 age = 15 并加排他锁
SELECT * FROM stu WHERE age = 15 FOR UPDATE;
-- 物理锁分析:
-- 1. 首先对包含 15 的区间加临键锁 Next-Key Lock:(10, 15];
-- 2. 普通索引值可能重复,因此必须继续向右遍历扫描到不满足条件的第一个值 age = 20;
-- 3. 在 age = 20 节点上的 Next-Key Lock (15, 20] 会自动降级退化为【间隙锁 Gap Lock】:开区间 (15, 20);
-- 4. 最终物理锁定总区间为:临键锁 (10, 15] + 间隙锁 (15, 20)。
3. 规则三:唯一索引范围查询(访问到不满足条件的第一个值为止)
当对主键或唯一索引 进行范围查询(如 > / >= / < / <=)时,InnoDB 会沿着 B+ 树索引链表向后扫描,直到访问到不满足条件的第一个值为止,期间遇到的每一个索引节点均会被加上 Next-Key Lock 或行锁。
sql
-- 假设唯一索引 id 列已有物理记录值为:1, 3, 8, 12, 20
-- 事务 A 执行:范围查询 id >= 8 并加排他锁
SELECT * FROM stu WHERE id >= 8 FOR UPDATE;
-- 物理锁分析:
-- 1. 首先对 id = 8 这一行精确加【行锁 Record Lock】:id = 8;
-- 2. 向右范围扫描:对 id = 12 节点加【临键锁 Next-Key Lock】:(8, 12];
-- 3. 对 id = 20 节点加【临键锁 Next-Key Lock】:(12, 20];
-- 4. 对正无穷大 Suprenum 加【临键锁】:(20, +∞);
-- 5. 最终物理锁定区间为:[8, +∞)。在此区间内的任何修改与 INSERT 插入全线被卡住!
4. 电影院座位通俗比喻全景总结
- 行锁 (Record Lock) ➔ 锁【已有座位】,防止别人动座位上的人;
- 间隙锁 (Gap Lock) ➔ 锁【空白过道】,防止别人塞新座位插队;
- 临键锁 (Next-Key Lock) ➔ 【过道 + 座位】组合包
(A, B],两者一起封!
五、 悲观锁 (Pessimistic Locking) 与 乐观锁 (Optimistic Locking) 深度全景解析
在数据库高并发开发中,防范数据并发写冲突(如超卖、数据覆盖)主要有 悲观锁 与 乐观锁 两大核心思想。
1. 核心思想与概念定义
- 悲观锁(Pessimistic Locking) :
- 核心思想 :假定并发冲突发生的概率极高("悲观"),每次在读取/操作数据前,强制显式加物理锁 (如
SELECT ... FOR UPDATE锁住目标行)。 - 物理本质:依靠数据库底层行锁/表锁强加排他锁,强行让其他并发写事务在锁外排队死等。
- 核心思想 :假定并发冲突发生的概率极高("悲观"),每次在读取/操作数据前,强制显式加物理锁 (如
- 乐观锁(Optimistic Locking) :
- 核心思想 :假定并发冲突发生的概率极低("乐观"),在读取数据时不加任何锁,而是在更新数据时校验数据版本(如版本号
version或 CAS 条件)。 - 物理本质 :完全不依赖数据库物理锁挂起,通过应用层逻辑或 SQL
WHERE条件原子校验完成并发防护。
- 核心思想 :假定并发冲突发生的概率极低("乐观"),在读取数据时不加任何锁,而是在更新数据时校验数据版本(如版本号
2. 三大落地实现手段与代码实战
① 悲观锁实现:SELECT ... FOR UPDATE(强行提前占坑)
sql
START TRANSACTION;
-- 1. 查询的同时强行上行级排他锁 (X 锁),其他事务试图加锁或修改将被挂起死等
SELECT stock FROM goods WHERE id = 100 FOR UPDATE;
-- 2. 程序校验 stock > 0 后执行扣减
UPDATE goods SET stock = stock - 1 WHERE id = 100;
COMMIT; -- 提交事务,释放物理行锁
- 适用场景:写冲突极频繁、强一致性、复杂多表状态校验场景。
② 乐观锁实现 1:版本号机制(CAS 版本控制 - 大厂最推荐)
sql
-- 1. 普通查询(走 MVCC 快照读,完全不加锁)
SELECT balance, version FROM account WHERE id = 1; -- 假设查出 balance=100, version=5
-- 2. 修改时带着查出来的 version 进行校验,更新成功时版本号 + 1
UPDATE account
SET balance = balance - 50, version = version + 1
WHERE id = 1 AND version = 5;
- 物理机制 :若别人先一步更新了数据,
version变成了6,本次UPDATE的影响行数(Affected Rows)会返回0。程序拿到0判定为版本冲突,选择自动重试或向用户报错提示。
③ 乐观锁实现 2:单条 SQL 原子扣减(高并发秒杀极速方案)
sql
-- 零排队、零死锁,直接在 UPDATE 本身的 WHERE 条件里做物理校验!
UPDATE goods SET stock = stock - 1 WHERE id = 100 AND stock >= 1;
- 物理机制 :
UPDATE语句底层自动触发当前读并加行锁,如果stock已经由前人改成了0,WHERE stock >= 1条件不成立,返回影响行数0。程序拿到0表示抢购失败。零长事务、零死锁风险,高并发吞吐量极致!
3. 悲观锁 vs 乐观锁 工业级对比矩阵
| 维度指标 | 悲观锁 (Pessimistic Locking) | 乐观锁 (Optimistic Locking) |
|---|---|---|
| 核心机制 | 数据库底层物理锁 (FOR UPDATE) |
应用程序逻辑控制 / CAS 条件 (WHERE version = x) |
| 加锁时机 | 读取数据时立即强行加锁 | 读取时不加锁,仅在提交更新时校验 |
| 高并发性能 | 偏低(其他事务需排队挂起,占用连接池) | 极高(零阻塞,读写并行) |
| 死锁风险 | 存在死锁风险(多行交叉加锁) | 零死锁风险 |
| 适合业务场景 | 写冲突极其频繁、长事务/人工审核锁定 | 读多写少、高并发扣库存、状态机流转(大厂标配) |
六、 深度剖析:为什么单靠 MVCC 无法彻底解决幻读?
很多初学者甚至面试者常误以为"在 RR 隔离级别下,有了 MVCC 就彻底解决了幻读"。这是完全错误的!单靠 MVCC 无法 100% 杜绝所有场景下的幻读!
1. 核心物理成因一句话总结
MVCC 只能管得住"快照读(普通的 SELECT)",但管不住"当前读(加锁 SELECT ... FOR UPDATE 或 UPDATE)"。在混合操作下,UPDATE 语句会将别人刚刚插入的新记录"盖上自己的事务 ID",从而强行导致 MVCC 快照失效,让幻读破功!
2. MVCC 幻读破功实战演绎(写操作赋能旧快照)
假设数据库表 stu 中现有 id = 1, 3, 8 三条数据,运行在 可重复读(RR) 隔离级别下:
ini
【事务 A】 【事务 B】
───────────────────────────────────────────── ─────────────────────────────────────────────
1. 开启事务,执行快照读:
SELECT * FROM stu WHERE id = 5;
➔ 【查到 0 条记录】(生成初始 Read View)
2. 开启事务,插入新数据并提交:
INSERT INTO stu (id, name) VALUES (5, '张三');
COMMIT;
➔ (此时 id=5 的 DB_TRX_ID 为 事务B 的 ID)
3. 事务 A 执行更新:
UPDATE stu SET name = '李四' WHERE id = 5;
➔ 【当前读物理生效】:UPDATE 去物理页上成功修改了 事务B 提交的 id=5 这一行!
➔ 【物理副作用】:id=5 记录的 DB_TRX_ID 被自动改为了【事务 A 自身的 ID】!
4. 事务 A 再次执行快照读:
SELECT * FROM stu WHERE id = 5;
➔ 【发生幻读灾难】:竟凭空查到了 1 条记录(id=5, name='李四')!
物理破功原理分析:
- 步骤 1 中,事务 A 生成了 Read View,由于
id=5是事务 B 插入的,原本对事务 A 是不可见的; - 但步骤 3 中,事务 A 执行了
UPDATE(当前读),直接物理修改了id=5这行,把它的DB_TRX_ID盖上了事务 A 自己的名字; - 步骤 4 中,事务 A 再次
SELECT时,MVCC 检查id=5的DB_TRX_ID发现是事务 A 自己的 ID,根据规则 1(自己的修改直接可见),MVCC 乖乖把这一行吐了出来,幻读瞬间产生!
3. 终极解法:MySQL 彻底防幻读"双轨制"
为了彻底杜绝幻读,InnoDB 采用了 MVCC 快照读 + Next-Key Lock 间隙锁当前读 的双轨机制:
sql
┌── 1. 快照读(普通 SELECT) ➔ 依靠【MVCC Read View】杜绝绝大部分幻读
【MySQL 防幻读双轨制】 ──┤
└── 2. 当前读(FOR UPDATE/UPDATE)➔ 依靠【Next-Key Lock 间隙锁】锁定缝隙,物理禁止 INSERT 插队!
- 普通快照读:靠 MVCC 拍照看静态历史;
- 当前读与写操作 :必须依靠 Next-Key Lock(临键锁 = 行锁 + 间隙锁) ,在读取/修改的同时物理封死
(3, 8)的过道缝隙,直接把事务 B 的INSERT拦截挂起死等,从而在当前读场景下也彻底杜绝幻读!
七、 锁演进与物理退化矩阵总结
1. 锁演进与退化规律矩阵表
| 场景与查询类型 | 索引类型 | 目标数据物理状态 | 最终加锁结果 | 核心工程效果 |
|---|---|---|---|---|
| 等值查询 | 唯一索引 / 主键 | 记录存在 | 行锁 (Record Lock) | 精准只锁定该单行 |
| 等值查询 | 唯一索引 / 主键 | 记录不存在 | 间隙锁 (Gap Lock) | 退化为锁开区间 (A, B),防插入 |
| 等值查询 | 普通二级索引 | 记录存在 | 临键锁 (A, B] + 间隙锁 (B, C) |
向右扫描不满足条件值,退化锁后续间隙 |
| 范围查询 | 唯一索引 / 主键 | 范围扫描 | 行锁 + 临键锁 (A, +∞) |
持续下潜锁定直到不满足条件处 |
2. 【终极一句话速记】
"全局 FTWRL 全只读,表锁粒度大 MDL 防改表,意向锁表头挂标识;S 锁兼容 X 锁冲突,MVCC 负责快照读;悲观锁强行先占坑,乐观锁 CAS 防超卖;UPDATE 盖印 MVCC 破功引发幻读,Next-Key 锁死缝隙方保绝对安全!"