⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Fight/mianshiguantantan-InnoDB-zhongdebiaojisuoyejisuoxingjisuo/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

这道题面试出现频率极高,但 90% 的人答得像背书——把三种锁的定义念一遍就完了。面试官想听的根本不是定义。

面试官到底在考什么

三个层次,一个比一个深:

  1. 基础认知:三种锁粒度的并发性和开销权衡——知道有哪些锁、各自什么场景用
  2. InnoDB 深度:行锁什么情况下会”意外”变成锁表——这是生产事故高发区,答出来说明你踩过坑
  3. 锁兼容性:S 锁和 X 锁的冲突矩阵,意向锁在多粒度锁定中的角色——说清楚意向锁,说明你真读过源码级资料

下面按这三层来讲。答面试时也建议按这个顺序展开——先给结论表格,再逐个深入。

三种锁粒度:一张表终结混乱

锁类型 锁什么 并发能力 加锁代价 谁在用
行级锁 单行记录 最高 最大(内存开销) InnoDB 默认
页级锁 一个 16KB 数据页 中等 中等 BDB(已废弃)
表级锁 整张表 最低 最小 MyISAM、DDL 操作

核心结论:InnoDB 选行级锁,牺牲锁管理开销换来高并发——这是它能撑住 OLTP 场景的根本原因。MyISAM 选表级锁,管理简单但并发度拉胯,所以只适合读多写少的场景。

三种锁粒度对比示意图:行锁只锁一行允许其他行并发;页锁锁一个数据页;表锁锁整张表

行级锁:InnoDB 能扛住高并发的原因

InnoDB 的行级锁不是只有一种,而是一套组合拳:

锁类型 干什么的 什么时候触发
Record Lock 记录锁 精确锁定一条索引记录 等值查询命中索引
Gap Lock 间隙锁 锁索引记录之间的”空隙” RR 级别防幻读
Next-Key Lock 临键锁 记录锁 + 间隙锁 RR 级别默认加锁方式
S 锁 共享锁 允许别人读,不允许写 SELECT ... FOR SHARE
X 锁 排他锁 读写都不允许 FOR UPDATE / DML

S 和 X 的兼容矩阵

S锁和X锁兼容性矩阵:S+S兼容,S+X冲突,X+X冲突

说人话:

  • 两个事务同时读同一行 → 没问题,S 锁兼容
  • 一个读一个写同一行 → 等着,S 和 X 冲突
  • 两个事务同时写同一行 → 必须排队,X 和 X 冲突
-- 共享锁:我读这行,别人也能读,但不能改
SELECT * FROM orders WHERE id = 1001 FOR SHARE;

-- 排他锁:我要改这行,别人读写都得等
SELECT * FROM orders WHERE id = 1001 FOR UPDATE;

-- DML 语句自动加 X 锁
UPDATE orders SET status = 'PAID' WHERE id = 1001;

Gap Lock 存在的意义:防止别的事务在你查询范围内插入新行(幻读)。只在 RR 隔离级别生效。如果你用 RC 级别,Gap Lock 不加,幻读你自己兜着。

表级锁:意向锁才是你该讲的重点

InnoDB 的表级锁里,面试真正要考的是意向锁——大部分人知道有这东西,但说不清它解决什么问题。

锁 说明 怎么加的
IS 意向共享锁 “我要对某些行加 S 锁” InnoDB 自动加
IX 意向排他锁 “我要对某些行加 X 锁” InnoDB 自动加
表 S 锁 锁定整张表做读取 LOCK TABLES t READ
表 X 锁 锁定整张表做写入 LOCK TABLES t WRITE
MDL 元数据锁 保护表结构不被 DDL 改掉 DDL 自动加

意向锁到底解决什么问题

意向锁工作原理:事务在获取行锁前先在表级加意向锁,其他事务判断表锁兼容性时只需检查表级意向锁

场景:事务 A 锁住了第 500 行(X 锁),事务 B 想对整张表加 S 锁。B 怎么知道表里有行正在被锁?难道遍历 100 万行逐一检查?

意向锁的方案:事务 A 加行锁之前,InnoDB 自动在表级别挂一个 IX 标记。B 想加表锁时,只需要看表上有没有意向锁,不需要逐行扫描。O(1) 判断。

关键细节:意向锁之间永远兼容(IS 和 IX 不冲突)——因为它们只是”声明意图”,真正的冲突检测在行锁层面做。

表锁兼容性矩阵

表级锁兼容性矩阵:IS/IX之间兼容,但与表级S/X锁有冲突规则

行锁升表锁:生产事故重灾区

这是面试最爱挖的坑,也是线上出事最多的地方。

行锁升级为表锁的触发条件:WHERE无索引或索引失效导致全表扫描

核心规则:InnoDB 的行锁是加在索引上的。如果你的 WHERE 条件没走索引(无索引字段、索引失效、类型隐式转换),InnoDB 执行全表扫描,每一行都会被加锁——效果等同于锁表。

-- ❌ 灾难:create_time 没索引,锁全表
-- 1000 万行的表,每行都被 X 锁锁住,其他事务全部等待
UPDATE orders SET status = 'CLOSED'
WHERE create_time < '2024-01-01';

-- ✅ 正确做法:先用索引查出 id,再按主键精确更新
SELECT id FROM orders
WHERE create_time < '2024-01-01' LIMIT 1000;

UPDATE orders SET status = 'CLOSED'
WHERE id IN (1, 2, 3, ...);

验证方法:用 EXPLAIN 看执行计划确认走没走索引;出事了用下面的 SQL 看锁等待:

-- MySQL 8.0+ 查看当前持有的锁
SELECT * FROM performance_schema.data_locks;

-- 查看锁等待关系
SELECT * FROM performance_schema.data_lock_waits;

常见触发场景:

  1. WHERE 字段没建索引
  2. 索引列发生隐式类型转换(WHERE phone = 13800138000,phone 是 varchar)
  3. 联合索引没遵循最左前缀
  4. OR 条件导致索引失效

页级锁:一句话带过就行

页级锁在InnoDB中的位置:属于引擎内部的latch保护机制,用户不直接操作

InnoDB 内部用 latch(闩锁) 保护 B+ 树的数据页,持有时间是微秒级,跟事务锁完全两码事。面试问到页级锁,说一句”BDB 引擎用的,已废弃,InnoDB 用行级锁”就够了。别在这浪费时间。

面试追问三连:答到这个程度就够了

Q1:为什么 InnoDB 不用表级锁?

OLTP 场景的核心诉求是高并发短事务。表级锁一锁就是整张表,100 个事务改不同行也得排队——彻底废掉并发能力。InnoDB 选行级锁,允许不同事务同时操作不同行,代价是锁管理内存开销大一点,但对于高并发场景这个交换完全值得。

Q2:FOR UPDATE 什么时候会锁表?

WHERE 条件无法使用索引时。InnoDB 行锁加在索引上,没索引就没有”定位到具体行”的能力,只能全表扫描逐行加锁。面试答这一句就够了,但最好补一个真实案例——比如 varchar 字段传了 int 值导致隐式转换索引失效。

Q3:Next-Key Lock 和 Gap Lock 什么关系?

Next-Key Lock = Record Lock + Gap Lock。RR 隔离级别下 InnoDB 默认用 Next-Key Lock——既锁住当前记录,又锁住前面的间隙,防止别的事务在这个范围插入新行(防幻读)。如果查询精确命中唯一索引,InnoDB 会优化为只加 Record Lock(因为唯一索引保证了不会有新行插进来)。

文章目录
  1. 1. 面试官到底在考什么
  2. 2. 三种锁粒度:一张表终结混乱
  3. 3. 行级锁:InnoDB 能扛住高并发的原因
    1. 3.0.1. S 和 X 的兼容矩阵
  • 4. 表级锁:意向锁才是你该讲的重点
    1. 4.0.1. 意向锁到底解决什么问题
    2. 4.0.2. 表锁兼容性矩阵
  • 5. 行锁升表锁:生产事故重灾区
  • 6. 页级锁:一句话带过就行
  • 7. 面试追问三连:答到这个程度就够了