logo 范 · 拾光录
网址收集 关于作者 Github Gitee
杂文随笔4
Hexo博客:基础使用Hexo博客:Next主题Hexo博客:Next进阶使用Hexo博客:Next高级配置
前端编程3
前端基础知识16
UniApp15
Vue框架19
Java编程7
Java基础知识14
SpringBoot31
SpringMVC14
MyBatis8
SpringCloud15
RocketMQ1
MyCat1
数据库4
MySQL6
Redis7
MongoDB10
H21
Java技术栈4
知识整理分布式锁的实现方案MySQL事务MySQL的锁
软件工具箱14
IDEAGitMavenGradleNginx安装Nginx配置JMeter压测OllamaPicGoRustFSVSCodeDockerObsidianObs录制
Linux知识11
树莓派安装及使用ArchLinux:常用软件ArchLinux:基础系统安装ArchLinux:深度优化ArchLInux:图形化界面安装ArchLinux:Nirifrp内网穿透Jar启动脚本Linux常用命令VirtualBox安装CentOSVirtualBox安装Ubuntu
Python编程6
Python基础知识Python语法yolo目标检测OpenCV的使用及树莓派平台condauv
创意设计2
Blender:入门知识UI设计基础知识
AI相关9
Claude CodeHermes AgentOpenAI基本使用OpenAI工具调用OpenAI记忆管理OpenAI推理执行OpenAI开发框架Langchainllama.cpp

一、锁的分类

MySQL 锁按不同维度划分:

维度 分类
粒度 全局锁、表级锁、页级锁(BDB)、行级锁
机制 共享锁(S锁)、排他锁(X锁)
思想 悲观锁(SELECT FOR UPDATE)、乐观锁(版本号/CAS)
加锁方式 隐式锁(自动加)、显式锁(手动 SQL)

InnoDB 是最常用的引擎,下面主要围绕 InnoDB 展开。

二、全局锁

对整个数据库实例加锁,让整个库只读。

FLUSH TABLES WITH READ LOCK;   -- 加全局读锁
UNLOCK TABLES;                  -- 释放

典型场景:全库逻辑备份(mysqldump 加 --single-transaction 可替代,避免锁库)。

注意:加了全局锁后任何 DML/DDL 都会被阻塞,生产慎用。

三、表级锁

1. 表锁(Table Lock)

LOCK TABLES t1 READ;            -- 共享表锁
LOCK TABLES t1 WRITE;           -- 排他表锁
UNLOCK TABLES;

MyISAM 默认用表锁,开销小但并发差。InnoDB 一般不用显式表锁。

2. 元数据锁(MDL)

MySQL 5.5 引入,自动隐式加锁,用于保证 DDL 和 DML 的冲突:

常见坑:一个慢查询持有 MDL 读锁不释放,后续 ALTER TABLE 尝试加 MDL 写锁被阻塞,然后写锁后面的所有 CRUD 也排队等 MDL 读锁→ 整个表"卡死"。

排查:SELECT * FROM performance_schema.metadata_locks;

3. 意向锁(Intention Lock)

InnoDB 特有,隐式自动加锁,解决表锁和行锁的冲突判断:

意向共享锁(IS) 意向排他锁(IX)
表 S 锁 ✅ 兼容 ❌ 冲突
表 X 锁 ❌ 冲突 ❌ 冲突

记忆:意向锁之间永远兼容(IS/IS、IS/IX、IX/IX 均兼容),它只和表级锁冲突。

4. AUTO-INC 锁

自增主键插入时使用,保证 AUTO_INCREMENT 值的连续性。MySQL 8.0 默认用轻量级互斥量替代。

四、行级锁(InnoDB)

1. 记录锁(Record Lock)

锁定单条索引记录

SELECT * FROM t WHERE id = 1 FOR UPDATE;   -- 对 id=1 加 X 锁

关键:行锁是加在索引上的,不是加在数据行上。如果查询条件不走索引,会退化为表锁。

2. 间隙锁(Gap Lock)

锁定索引记录之间的间隙,不包含记录本身。只在 RR 隔离级别下存在。

索引值:  5    10    15    20
间隙:   (-∞,5) (5,10) (10,15) (15,20) (20,+∞)
SELECT * FROM t WHERE id BETWEEN 10 AND 15 FOR UPDATE;
-- 锁定 (5,10)、(10,15)、(15,20) 三个间隙 + 10、15 两条记录

作用:防止幻读 —— 间隙锁阻止其他事务在间隙中 INSERT 新数据。

3. 临键锁(Next-Key Lock)

记录锁 + 间隙锁的组合,锁定"左开右闭"区间。InnoDB RR 级别的默认行锁算法。

锁住的是 (5, 10] 区间:间隙锁 (5,10) + 记录锁 [10]

效果:既防止当前记录被修改,也防止间隙插入,彻底杜绝幻读。

4. 插入意向锁(Insert Intention Lock)

INSERT 前自动加的间隙锁的一种,多个事务对同一间隙的插入意向锁互相兼容(只要不插入相同位置),提升并发插入性能。

5. 加锁规则

两个原则 + 两个优化 + 一个 bug:

原则

  1. 加锁的基本单位是 Next-Key Lock(左开右闭)
  2. 查找中访问到的对象才加锁

优化

  1. 唯一索引等值查询且记录存在 → 退化为 Record Lock
  2. 等值查询向右遍历到第一个不满足条件的值 → 最后一个间隙锁退化为 Gap Lock(右开)

Bug(MySQL 8.0.18 前):唯一索引范围查询会多锁一个 Next-Key Lock

五、死锁

产生条件(四要素,缺一不可)

  1. 互斥:资源不能被共享
  2. 持有并等待:持有已获得的锁,同时等待其他锁
  3. 不可剥夺:锁只能由持有者主动释放
  4. 循环等待:事务间形成等待环路

典型场景

-- 事务 A
UPDATE t SET v = 1 WHERE id = 1;   -- 获得 id=1 的 X 锁
UPDATE t SET v = 2 WHERE id = 2;   -- 等待 id=2 的锁 → 被 B 持有

-- 事务 B(并发)
UPDATE t SET v = 3 WHERE id = 2;   -- 获得 id=2 的 X 锁
UPDATE t SET v = 4 WHERE id = 1;   -- 等待 id=1 的锁 → 被 A 持有 → 死锁

排查

SHOW ENGINE INNODB STATUS;                          -- 查看最近死锁详情
SELECT * FROM information_schema.INNODB_TRX;        -- 当前事务
SELECT * FROM information_schema.INNODB_LOCKS;      -- 当前锁(8.0 废弃)
SELECT * FROM performance_schema.data_locks;        -- 当前锁(8.0+)

避免策略

  1. 固定加锁顺序(所有事务按相同顺序访问资源)
  2. 拆分大事务,缩短锁持有时间
  3. 合理使用索引(减少锁范围)
  4. 降低隔离级别(RC 去掉间隙锁,减少锁冲突)
  5. 死锁检测(innodb_deadlock_detect=ON)→ 自动回滚代价最小的事务

六、MVCC 与锁

MVCC(多版本并发控制)是 InnoDB 实现读不加锁的核心机制,与锁体系互补:

对比 锁机制 MVCC
读操作 当前读(加锁) 快照读(不加锁)
实现 行锁 + 间隙锁 + 临键锁 undo log + ReadView
隔离级别 所有级别 RC + RR(RU 无,序列化退化为锁)

当前读(加锁):SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEUPDATEDELETEINSERT

快照读(无锁):普通 SELECT

MVCC 通过 DB_TRX_ID(事务ID)、DB_ROLL_PTR(回滚指针)、DB_ROW_ID(行ID)三个隐藏字段 + undo log 版本链实现。

七、常见面试题

  1. MySQL 有哪些锁? → 全局锁、表锁(表锁/MDL/意向锁/AUTO-INC)、行锁(Record/Gap/Next-Key/插入意向)

  2. RR 级别如何解决幻读? → Next-Key Lock(记录锁+间隙锁)阻止其他事务在间隙 INSERT

  3. 为什么 RR 级别下仍然有幻读? → 快照读无幻读(MVCC),但当前读(FOR UPDATE)如果不用 Next-Key Lock,仍可能幻读

  4. 行锁是加在数据行上的吗? → 不是,加在索引上;不走索引退化为表锁

  5. SELECT FOR UPDATE 和 LOCK IN SHARE MODE 区别? → FOR UPDATE 是 X 锁(排他,阻塞读写),LOCK IN SHARE MODE 是 S 锁(共享,阻塞写不阻塞读)

  6. RC 和 RR 的锁有什么区别? → RC 没有间隙锁,只用 Record Lock;RR 有完整 Gap Lock + Next-Key Lock

  7. 死锁了怎么办? → MySQL 自动检测并回滚最小事务;应用层加锁失败重试即可

一、锁的分类
二、全局锁
三、表级锁
1. 表锁(Table Lock)
2. 元数据锁(MDL)
3. 意向锁(Intention Lock)
4. AUTO-INC 锁
四、行级锁(InnoDB)
1. 记录锁(Record Lock)
2. 间隙锁(Gap Lock)
3. 临键锁(Next-Key Lock)
4. 插入意向锁(Insert Intention Lock)
5. 加锁规则
五、死锁
产生条件(四要素,缺一不可)
典型场景
排查
避免策略
六、MVCC 与锁
七、常见面试题