一、索引原理与结构(M01–M08)
M01. InnoDB 的聚簇索引(主键索引),其 B+ 树叶子节点存储的是?
考点:聚簇索引的叶子节点
A. 主键值 B. 完整的行记录数据 C. 指向行数据的物理地址指针 D. 索引列 + 主键值
答案:B
【考点】聚簇索引与二级索引(非聚簇索引)的存储差异。
【结论】 聚簇索引的叶子节点存的是完整的行记录 —— InnoDB 是索引组织表,数据行就长在 B+ 树的叶子层。选 B。
【逐项辨析】
- A 主键值:错。主键值只是叶子页里用于排序的键,整行数据同样在这一层,题干问的是叶子存的数据。
- B 完整的行记录数据:正确。 聚簇索引的叶子节点就是数据页,完整数据行直接放在叶子层,按主键查一次 B+ 树即可拿到整行。
- C 指向行数据的物理地址指针:错。错在“物理地址”四个字 —— 这是 MyISAM 非聚簇索引的做法(索引与数据分离),InnoDB 不存在这种分离。
- D 索引列 + 主键值:错。错在把二级索引的叶子内容当成了聚簇索引。
【知识点】 聚簇索引(clustered index)的定义:索引与数据在同一棵 B+ 树中,索引的叶子节点就是数据页。InnoDB 的表因此被称为“索引组织表(IOT)”;若未定义主键,InnoDB 会选用一个非空唯一索引,都没有时再隐式生成 6 字节的 ROWID 作聚簇索引键。
| 索引类型 | 叶子节点存什么 | 查完整行需几步 |
|---|---|---|
| 聚簇索引(主键索引) | 完整行记录 | 1 步(查到即拿到行) |
| 二级索引(普通 / 唯一 / 联合) | 索引列 + 主键值 | 2 步(先取主键,再回表) |
| MyISAM 索引(非聚簇) | 行记录的物理地址 | 2 步(按地址读数据文件) |
推导链条:叶子存整行 → 按主键查天然只需一次 B+ 树查找;二级索引叶子只有“索引列 + 主键” → 要取其余列就必须回表;MyISAM 的索引与数据是两个文件 → 叶子只能存地址。叶子放什么,直接决定查询要几步。
【记忆锚点】 “主键索引带全行,二级索引带主键”。
【易混对比】
- 聚簇索引 vs 二级索引:前者叶子 = 整行,后者叶子 = 索引列 + 主键值。
- InnoDB vs MyISAM:MyISAM 索引叶子一律存行地址,没有聚簇索引。
- 换问法:问“二级索引叶子存什么”,答“索引列 + 主键值”;问“MyISAM 索引叶子存什么”,答“行地址”。三者互为镜像,成对记。
【自测】 表 t(id 主键, name, age) 在 name 上建普通索引,SELECT * FROM t WHERE name = 'a' 一共要查几棵 B+ 树?
答:两棵。先查
name二级索引拿主键id,再回聚簇索引取整行,这就是“回表”。与 M03、M04 连考;大厂面试高频。
【知识关联】
- 补题关联:补-22(Buffer Pool 缓存的就是数据页/索引页)、补-25(ICP 在二级索引层过滤,减少回表)。
- 面试/工程:主键查询快的根因就是“数据即索引”;二级索引膨胀往往也是因为叶子要带主键,所以主键类型(自增 vs UUID)会连带拖累所有二级索引(见 M22/M23)。
- 面试追问:① 没有主键也没有唯一索引的 InnoDB 表,聚簇索引键是什么? ② 二级索引叶子上的主键值在什么场景下会成为瓶颈?
【拓展延伸】
- 变式问法:问“MyISAM 索引叶子存什么”答物理地址;问“InnoDB 二级索引叶子存什么”答索引列+主键;问“为什么主键不宜过长”答二级索引叶子都要冗余一份主键。
- 参数/命令:
SHOW INDEX FROM t可看索引与主键关系;information_schema.INNODB_BUFFER_PAGE能看到 Buffer Pool 中缓存的聚簇/二级索引页。主键缺失时可观察DB_ROW_ID(6 字节隐式 ROWID)。
M02. MySQL InnoDB 选择 B+ 树而非 B 树作为索引结构,下列不属于其原因的是?
考点:为什么选 B+ 树
A. B+ 树非叶子节点不存数据,扇出更大、树更矮,磁盘 IO 次数更少 B. B+ 树叶子节点用链表相连,更适合范围查询与排序 C. B+ 树支持更高效的点查询,因为它可以在非叶子节点提前命中目标 D. B+ 树每次查询都要走到叶子节点,因此查询性能更稳定(耗时接近)
答案:C
【考点】B 树与 B+ 树的结构差异及其对磁盘 IO 的影响。
【结论】 C 恰好说反了:能在非叶子节点提前命中目标的是 B 树;B+ 树数据只在叶子、非叶子只有索引键,不存在“提前命中”,故 C 不属于 B+ 树胜出的原因。选 C。
【逐项辨析】
- A 非叶子节点不存数据、扇出更大、树更矮、IO 更少:属正确原因。非叶子只存“键 + 指针”,一页能塞更多键,树高随之降低。
- B 叶子节点用链表相连、更适合范围查询与排序:属正确原因。InnoDB 的叶子节点是双向链表,范围扫描沿链表顺序走即可。
- C 支持更高效的点查询,因为可以在非叶子节点提前命中目标:正确(即“不属于原因”)。 这句话描述的是 B 树;B+ 树的非叶子节点只有索引键、没有数据,无从提前命中,点查询必到叶子。
- D 每次查询都要走到叶子节点、性能更稳定:属正确原因。所有路径长度都等于树高,耗时接近,无抖动。
【知识点】 结构差异一句话:B 树的数据散布在所有节点,B+ 树的数据只在叶子、非叶子纯索引。
| 对比项 | B 树 | B+ 树 |
|---|---|---|
| 数据存放位置 | 所有节点都可能存数据 | 仅叶子节点 |
| 非叶子节点内容 | 键 + 指针 + 数据 | 键 + 指针(无数据) |
| 叶子节点连接 | 无链表 | 双向链表相连 |
| 扇出 / 树高 | 扇出小、树高 | 扇出大、树矮 |
| 点查询耗时 | 不稳定(可能提前命中) | 稳定(必到叶子) |
| 范围查询 | 需中序遍历、多次回溯 | 沿叶子链表顺序扫描 |
扇出推导(InnoDB 默认页 16 KB):非叶子节点每项 = 键值 + 6 字节页指针,一页约可放 1170 项;叶子节点存整行,假设一行 1 KB 则一页约 16 行。于是 3 层 B+ 树可索引 1170 × 1170 × 16 = 21,902,400 ≈ 2190 万行(口头常说「约 2000 万」,写算式时按实数) —— 查任意一行最多 3 次磁盘 IO,这是 B+ 树成为 InnoDB 默认索引结构的根本原因。补充:哈希索引点查询复杂度为 O(1),但不支持范围查询与排序,故只用于 MEMORY、自适应哈希等场景。
【记忆锚点】 “B+ 树数据全在叶子,叶子串成链表”。
【易混对比】
- B 树 vs B+ 树:B 树点查可能更快(提前命中),B+ 树点查更稳定、范围查强得多 —— 数据库选 B+ 树是综合权衡。
- B+ 树 vs 哈希索引:哈希
O(1)但不支持范围、排序与最左前缀。 - B+ 树 vs 红黑树:红黑树是二叉,树高约
log₂N,千万级数据要 20 多层、20 多次 IO,扇出太小。 - 换问法:若题干问“下列哪项是 B+ 树的优点”,C 就成了错误选项。
【自测】 MySQL 为什么不用哈希索引作为默认索引结构?
答:哈希索引不支持范围查询、排序与最左前缀匹配,冲突多时性能还会退化;B+ 树叶子有序且成链,点查与范围查兼顾。与 M01、M05 连考。
【知识关联】
- 补题关联:补-22(Buffer Pool 缓存页命中率与树高直接相关)。
- 面试/工程:3 层 B+ 树约索引两千万行,是“亿级表点查仍可毫秒级”的结构基础;对比 MongoDB/B+ 类存储、LSM 树(写友好读放大)可加深选型理解。
- 面试追问:① 为什么不用二叉搜索树/红黑树做磁盘索引? ② 哈希索引在什么场景仍优于 B+ 树?
【拓展延伸】
- 变式问法:正向问“B+ 树优点”时 C 会变成错误描述;问“B 树相对 B+ 树的可能优势”才对应“非叶子可提前命中”。
- 参数/命令:InnoDB 默认页大小
innodb_page_size=16384;扇出≈页大小/(键长+指针)。自适应哈希innodb_adaptive_hash_index是点查加速,不是替代 B+ 树。 - 完整推导补充:非叶子项≈14B(8B 主键+6B 指针,按 14B 算)→ 16384/14≈1170;叶子若行 1KB 则约 16 行/页 → 1170×1170×16≈2190 万;层数=⌈log_扇出(N/每页行数)⌉。
M03. 表 t(id 主键, name, age),在 name 上建有普通索引。执行 SELECT * FROM t WHERE name = 'a' 的查找过程是?
考点:回表
A. 直接查 name 索引即可拿到整行,无需其他操作 B. 放弃索引,直接全表扫描 C. 先在 name 索引中查到主键 id,再用 id 到聚簇索引中取出整行(回表) D. 只查聚簇索引即可,name 索引不参与
答案:C
【考点】二级索引 → 回表 的两步查找过程。
【结论】 idx_name 的叶子只存 name 与主键 id,取不到 age,因此必须“先查二级索引拿主键、再回聚簇索引取整行” —— 这就是回表。选 C。
【逐项辨析】
- A 直接查
name索引即可拿到整行:错。错在“即可”二字 —— 二级索引叶子只有name与id,age不在其中。 - B 放弃索引、直接全表扫描:错。优化器只在回表代价超过全表扫描(选择性极差、命中行数占比很高)时才如此;本题按
name = 'a'定位,标准过程是走索引。 - C 先在
name索引中查到主键id,再用id到聚簇索引中取出整行(回表):正确。 完整描述了“二级索引定位 → 主键回表”这两步。 - D 只查聚簇索引、
name索引不参与:错。聚簇索引按id有序,name上没有任何顺序信息,只能全表扫描,白白浪费索引。
【知识点】 回表的定义:通过二级索引找到主键值后,再回到聚簇索引中按主键取出完整行记录的过程。它包含两次 B+ 树查找,第二次按主键随机定位,属随机 IO,是二级索引查询的主要代价。
① 在 idx_name 的 B+ 树中定位 name = 'a' → 得到主键 id = 7
② 在聚簇索引的 B+ 树中定位 id = 7 → 取出整行| 形态 | 触发条件 | 是否回表 | EXPLAIN 的 Extra |
|---|---|---|---|
| 回表 | 查询所需列不全在索引中 | 是 | 常出现 Using where |
| 覆盖索引 | 查询所需列全在索引中 | 否 | Using index |
| 索引下推(ICP) | 条件列在索引中但无法用于定位 | 视返回列而定 | Using index condition |
代价推导:命中 n 行时,磁盘 IO ≈ 二级索引扫描页数 + n 次随机回表。n 越大,随机 IO 越多,性能下降越明显 —— 这就是“SELECT * 比只取需要的列慢”的根因,优化方向是覆盖索引(见 M04)。
【记忆锚点】 “二级索引只给主键,要整行就回表”。
【易混对比】
- 回表 vs 覆盖索引:回表是“不得不回”,覆盖索引是“列全在索引里、压根不用回”。
- 回表 vs 索引下推:回表是取数据的第二步;索引下推是在索引层提前过滤、减少回表行数。
- InnoDB 回表 vs MyISAM 按地址读:MyISAM 按叶子里的物理地址直接读数据文件,没有第二次 B+ 树查找。
- 换问法:把
SELECT *改成SELECT name,答案就从“回表”变成“覆盖索引、无需回表”。
【自测】 表 t(id 主键, name, age),name 上有普通索引。SELECT name FROM t WHERE name = 'a' 需要回表吗?SELECT age FROM t WHERE name = 'a' 呢?
答:前者不需要(
name就是索引列,覆盖索引);后者需要(age不在idx_name里,必须回表)。与 M01、M04 连考。
【知识关联】
- 补题关联:补-25(索引下推在回表前过滤,减少回表次数)。
- 面试/工程:
SELECT *慢查询的头号原因就是回表放大;压测时用EXPLAIN看Using indexvsUsing where可立刻判断是否回表。 - 面试追问:① 回表一定是随机 IO 吗?按主键有序回表能否变顺序? ② 什么情况下优化器会直接放弃二级索引改全表扫描?
【拓展延伸】
- 变式问法:把
SELECT *改为只查索引列 → 覆盖索引;把条件改成范围且命中行很多 → 优化器可能选全表。 - 参数/命令:
EXPLAIN FORMAT=JSON/EXPLAIN ANALYZE(8.0.18+)可看实际回表次数;optimizer_switch中index_condition_pushdown控制 ICP。 - 完整推导补充:代价 ≈ 二级索引扫描页数 + N×随机回表;当 N/总行数 超过约 20%(经验值,非硬阈值)时常改全表。
M04. 表 t(id 主键, name, age) 上建有联合索引 idx_name_age(name, age)。下列哪个查询最稳妥地用到覆盖索引、无需回表(即使表以后新增列也不回表)?
考点:覆盖索引
A. SELECT * FROM t WHERE name = 'a' B. SELECT age FROM t WHERE name = 'a' C. SELECT id, name, age FROM t WHERE age = 20 D. SELECT name FROM t WHERE age = 20
答案:B
【考点】覆盖索引的定义:查询涉及的列全部包含在索引中。
【结论】 只有 B 的条件列 name 与返回列 age 都在联合索引 (name, age) 中,且 name 满足最左前缀,因此无需回表。选 B。
【逐项辨析】
- A
SELECT * FROM t WHERE name = 'a':在本题表仅 3 列、二级索引叶子含主键的条件下,id/name/age恰好全在索引中,EXPLAIN 也会显示Using index;但SELECT *依赖“列恰好凑齐”,表一旦加列就必然回表,不是覆盖索引的最佳实践,故不选。(若题干问“本题数据下能否覆盖”,A 也成立;本题强调可靠写法,标准答案为 B。) - B
SELECT age FROM t WHERE name = 'a':正确。 条件列name、返回列age恰好是联合索引(name, age)的两列,name又满足最左前缀,全程只查索引。 - C
SELECT id, name, age FROM t WHERE age = 20:错。错在条件列age不满足最左前缀 —— 跳过name无法定位,该索引用不上,与“覆盖”无关。 - D
SELECT name FROM t WHERE age = 20:错。同 C,WHERE age = 20跳过最左列name,索引无法定位。
【知识点】 覆盖索引(covering index)的定义:一条查询所涉及的条件列与返回列全部包含在同一索引中,因此不需回表,直接从索引叶子取数即可返回结果。它是 MySQL 最常用的优化手段之一 —— 因为二级索引比聚簇索引小得多,扫描代价更低。
判定方法:EXPLAIN 的 Extra 出现 Using index 即覆盖索引;出现 Using index condition 是索引下推;出现 Using where 常意味着需回表后再过滤。
| 查询 | 条件列 | 返回列 | 是否覆盖 | 原因 |
|---|---|---|---|---|
SELECT age FROM t WHERE name='a' | name | age | 是 | 两列都在 (name, age) 中,且 name 满足最左前缀 |
SELECT * FROM t WHERE name='a' | name | 全部列 | 是(仅本题表成立) | 本题表只有 id/name/age,而二级索引叶子天然含主键,故 SELECT * 也落在索引内、EXPLAIN 显示 Using index;但这是「列恰好凑齐」的巧合,表一加列就退化为回表,所以不算覆盖索引的推荐写法(【逐项辨析】A 项按此判) |
SELECT id, name, age FROM t WHERE age=20 | age | id, name, age | 否 | 条件列不满足最左前缀,索引根本用不上 |
SELECT name FROM t WHERE age=20 | age | name | 否 | 同上 |
三条推论:① 条件列不满足最左前缀 → 索引根本用不上,与“覆盖”无关;② 二级索引叶子天然含主键列,故 SELECT id 也算被覆盖;③ 表越宽越难覆盖,显式列出所需列才可靠。
【记忆锚点】 “条件和返回,全在索引里” —— SELECT * 是覆盖索引的头号反面写法。
【易混对比】
- 覆盖索引 vs 回表:分水岭是“列是否全在索引中”。
- 覆盖索引 vs 最左前缀:最左前缀管“能不能用这个索引”,覆盖管“用了之后要不要回表”。C、D 栽在最左前缀上,不是栽在覆盖上 —— 必须分清。
- 覆盖索引 vs 索引下推:前者省掉回表,后者减少回表行数(
Using indexvsUsing index condition)。
【自测】 联合索引 idx(a, b, c),SELECT b FROM t WHERE a = 1 是否用到覆盖索引?
答:是。
a满足最左前缀可用于定位,返回列b也在索引中;若改成SELECT d FROM t WHERE a = 1就必须回表。与 M03、M05 连考。
【知识关联】
- 补题关联:补-25(ICP 与覆盖索引常一起考:ICP 省回表行数,覆盖省掉整次回表)。
- 面试/工程:大厂 SQL review 高频点:禁止
SELECT *、高频列表接口优先设计覆盖索引;统计类 SQL 尽量只取必要列。 - 面试追问:① 联合索引
(a,b)下SELECT b,a FROM t WHERE a=1是否覆盖? ② 前缀索引能否做覆盖索引?
【拓展延伸】
- 变式问法:题干改成“哪些写法能避免回表”;或给
EXPLAIN Extra=Using index反推是否覆盖。 - 参数/命令:
EXPLAIN的Extra: Using index是覆盖索引铁证;Using index condition只是 ICP,不等于覆盖。SET optimizer_switch='index_condition_pushdown=off'可做对照实验。
M05. 联合索引 idx(a, b, c),下列哪个查询条件无法利用该索引?
考点:最左前缀原则
A. WHERE a = 1 B. WHERE a = 1 AND b = 2 C. WHERE b = 2 AND c = 3 D. WHERE a = 1 AND c = 3
答案:C
【考点】联合索引的最左前缀匹配规则。
【结论】 联合索引按 (a, b, c) 的顺序逐列建树,必须先匹配最左列 a;C 从 b 开始、跳过了 a,无法定位索引。选 C。(两条版本口径:① MySQL 8.0.13+ 的 Skip Scan 在 a 列基数很低时可能允许 C 这类条件走索引,属优化器特例,标准答案仍按最左前缀取 C;② 若 SELECT 的列全在索引里,C/D 都可能退化成 index-only 全索引扫描——那叫“扫索引”,不叫“用索引定位”,与本题问法不同。)
【逐项辨析】
- A
WHERE a = 1:可用。命中最左列a,能定位到a = 1的区间。 - B
WHERE a = 1 AND b = 2:可用。a、b依次匹配,定位更精确。 - C
WHERE b = 2 AND c = 3:正确(即“无法利用”)。 错在跳过了最左列a—— B+ 树先按a排序,b只在a相同时才有序列,脱离a谈b毫无意义。 - D
WHERE a = 1 AND c = 3:可用。a用于索引定位,c虽不能参与定位,但可在索引层过滤(索引下推)或回表后过滤 —— 索引并没有失效。
【知识点】 最左前缀原则:联合索引 idx(a, b, c) 等价于按 (a)、(a, b)、(a, b, c) 三种前缀有序,故查询条件必须从最左列开始、连续匹配、不能跳列。
| 查询条件 | 可用于定位的列 | 能否用索引 |
|---|---|---|
a = 1 | a | 能 |
a = 1 AND b = 2 | a, b | 能 |
a = 1 AND b = 2 AND c = 3 | a, b, c | 能 |
b = 2 AND c = 3 | 无 | 不能 |
a = 1 AND c = 3 | a(c 仅过滤) | 能 |
a > 1 AND b = 2 | a(b 失效) | 能(仅 a) |
两条细节:① 范围查询会截断后续列 —— a = 1 AND b > 2 AND c = 3 中,c 因前面出现范围条件而无法参与定位(b > 2 之后 c 全局无序);② ORDER BY 同样遵守最左前缀,ORDER BY a, b 可借索引免排序,ORDER BY b, c 不行。
【记忆锚点】 “从左往右、不能跳列;一旦范围,后面全废”。
【易混对比】
- 跳列 vs 断列:
(a, c)是跳列,还能用到a;(a > 1, b)是断列,b用不上。跳列不一定全废,范围之后必然全废。 - 最左前缀 vs 覆盖索引:前者管“索引能不能用”,后者管“用了要不要回表”。
- 列顺序:
(a, b)与(b, a)是两个不同索引,建索引时应把等值条件列、区分度高的列放前面。 - 换问法:若改成
WHERE a = 1 AND b > 2 AND c = 3,可用于定位的只剩a、b两列(c失效)。
【自测】 联合索引 idx(a, b, c),WHERE a = 1 AND c = 3 与 WHERE b = 2 AND c = 3,哪一个能用上索引?
答:前者能(用到
a,c只能过滤);后者不能(跳过了最左列a)。与 M04、M06 连考;大厂面试高频。
【知识关联】
- 补题关联:补-25(最左前缀满足后 ICP 才有意义)。
- 面试/工程:联合索引列顺序设计口诀:等值高区分在前、范围在后、排序列最后;线上“索引建了却不用”多半是最左前缀或类型问题。
- 面试追问:①
a=1 AND b>2 AND c=3能用到几列? ②ORDER BY a,b与ORDER BY b,a哪个能免 filesort?
【拓展延伸】
- 变式问法:给出
EXPLAIN key=idx_abc但只用了ref=a,判断 b/c 是否参与定位;或问“跳列与断列区别”。 - 参数/命令:
EXPLAIN中key_len可反推实际用了几列(受字符集、是否可空影响);8.0 的EXPLAIN ANALYZE能看到实际过滤位置。
M06. 下列哪种写法通常会导致索引失效?
考点:索引失效(函数操作)
A. WHERE name = 'abc' B. WHERE name LIKE 'abc%' C. WHERE LEFT(name, 3) = 'abc' D. WHERE name > 'abc'
答案:C
【考点】索引失效的常见情形之一:对索引列使用函数或表达式。
【结论】 B+ 树里存的是列的原始值,LEFT(name, 3) 让 MySQL 无法用原值去索引中定位,索引因此失效。选 C。
【逐项辨析】
- A
WHERE name = 'abc':可用。标准的等值匹配,直接定位到'abc'。 - B
WHERE name LIKE 'abc%':可用。前缀匹配(通配符在尾部)可转化为区间扫描['abc', 'abd'),仍走索引。 - C
WHERE LEFT(name, 3) = 'abc':正确(即“会导致索引失效”)。 错在把函数套在了索引列上 —— 索引按name的原值排序,函数结果没有对应的顺序信息。 - D
WHERE name > 'abc':可用。范围查询能定位到'abc'之后的区间。
【知识点】 索引失效的本质只有一句话:任何让“索引列原值”无法直接用于定位的写法,都会失效。
| 失效情形 | 例子 | 失效原因 |
|---|---|---|
| 索引列上用函数 | LEFT(name,3)='abc'、DATE(create_time)='2026-09-15' | 索引按原值有序,函数结果无序 |
| 索引列上做运算 | id + 1 = 10 | 同上,应改写为 id = 9 |
| 隐式类型转换 | phone = 13800138000(phone 是 varchar) | 等价于对列套了转换函数(见 M07) |
前置通配 LIKE(% 在开头) | name LIKE '%abc' | 无可用前缀,无法定位区间 |
OR 两侧有一侧无索引 | a = 1 OR b = 2(b 无索引) | 一侧需全表扫描,整体退化为全表扫描 |
| 不满足最左前缀 | 联合索引 (a,b) 上写 WHERE b = 2 | 跳过了最左列(见 M05) |
需分情况看待的几种:!= / NOT IN 在低选择性列上通常全表扫描,高选择性列上仍可能走索引;IS NULL 是否走索引取决于列是否允许 NULL、NULL 占比与优化器代价估算;ORDER BY 与索引顺序不一致会产生 Using filesort(不算失效,但同样变慢)。
改写技巧:把函数从列侧挪到常量侧 —— DATE(create_time) = '2026-09-15' 改成 create_time >= '2026-09-15 00:00:00' AND create_time < '2026-09-16 00:00:00'。
【记忆锚点】 “列上带函数,索引全白建;通配符在头,索引也不走”。
【易混对比】
LIKE 'abc%'vsLIKE '%abc':前者是尾随通配(%在末尾)、前缀确定可走 range;后者是前置通配(%在开头)、无单调区间故失效。- 索引失效 vs 索引选不上:前者是写法问题(必然失效),后者是优化器按代价估算主动放弃,不一定是写法错。
【自测】 create_time 上有索引,WHERE DATE(create_time) = '2026-09-15' 会走索引吗?如何改写?
答:不会(列上套了
DATE()函数)。改写为create_time >= '2026-09-15 00:00:00' AND create_time < '2026-09-16 00:00:00'即可生效。与 M05、M07 连考;大厂面试高频。
【知识关联】
- 补题关联:补-25(失效后无法下推,直接全表)。
- 面试/工程:日期查询写成
DATE(col)=...是最常见生产事故;ORM 自动生成 SQL 时也要警惕对列套函数。 - 面试追问:① 为什么
LIKE 'abc%'可以走索引而LIKE '%abc'不行? ② MySQL 8.0 函数索引/表达式索引如何解决“列上函数”问题?
【拓展延伸】
- 变式问法:正向题给“能走索引”的写法;或比较
WHERE id+1=10与WHERE id=9。 - 参数/命令:8.0 支持函数索引:
ALTER TABLE t ADD INDEX ((CAST(create_time AS DATE)));LIKE走索引还受collation与前缀选择性影响。
M07. phone 字段类型为 varchar(20) 且建有索引,执行 SELECT * FROM t WHERE phone = 13800138000(传入数字,不带引号)会?
考点:隐式类型转换导致索引失效
A. 正常走索引,效率与带引号一致 B. 发生隐式类型转换(字符串列被转为数字比较),导致索引失效、全表扫描 C. 直接报语法错误,无法执行 D. 走索引,但返回结果错误
答案:B
【考点】隐式类型转换与索引失效的因果关系。
【结论】 字符串列与数字比较时,MySQL 把列的值转成数字再比较,等价于对索引列施加了函数,索引失效、退化为全表扫描。选 B。
【逐项辨析】
- A 正常走索引、效率与带引号一致:错。错在“效率一致” —— 转换发生在列侧,索引按字符串原值排序,按转换后的数字无法定位。
- B 发生隐式类型转换(字符串列被转为数字比较),导致索引失效、全表扫描:正确。 准确描述了转换方向与后果。
- C 直接报语法错误、无法执行:错。这是运行期的隐式转换,不是语法错误,语句能正常执行,只是变慢(可能伴随精度截断警告)。
- D 走索引,但返回结果错误:错。索引实际会失效;且本例中列值都能正常转成数字,结果通常仍正确,代价只是性能。
【知识点】 MySQL 比较运算的类型转换规则:两个操作数一个是字符串、一个是数字时,把字符串转换为数字(向“数值类型”看齐),而不是把数字转成字符串。于是:
| 列类型 | 传入参数 | 转换落在哪一侧 | 是否走索引 |
|---|---|---|---|
varchar | 数字 13800138000 | 列侧(相当于 CAST(phone AS DOUBLE)) | 失效,全表扫描 |
varchar | 字符串 '13800138000' | 无需转换 | 走索引 |
int | 字符串 '1' | 常量侧('1' → 1) | 仍走索引 |
int | 数字 1 | 无需转换 | 走索引 |
判定口诀:看转换落在谁头上 —— 落在列上就失效,落在常量上不影响。因为索引 B+ 树是按列的原值(字符串)排序的,一旦要按“转换后的数字”比较,原值顺序就派不上用场,优化器只能逐行读出、转换后再比较,即全表扫描。
附带风险:varchar 列与数字比较时,非数字开头的值会被转成 0 并产生警告;字符串按数字语义比较也会反直觉('10' > '9' 为假,10 > 9 为真)。规范做法是参数类型与列类型严格一致,字符串常量一律加引号。
【记忆锚点】 “字符串列遇数字,列被转换、索引作废;数字列遇字符串,常量被转换、索引无碍”。
【易混对比】
phone = 13800138000vsid = '1':前者失效(转列),后者可用(转常量)。同是类型不匹配,结论相反 —— 最经典的送分陷阱。- 隐式转换 vs 显式函数:
WHERE phone = 13800138000与WHERE CAST(phone AS DOUBLE) = 13800138000对索引的破坏完全等价。 - 换问法:若题干改成
WHERE id = '100'(id是int),答案变成“仍走索引”。
【自测】 订单号 order_no 是 varchar(32) 且建有索引。WHERE order_no = 20260915001 与 WHERE order_no = '20260915001' 性能有何差别?
答:前者索引失效、全表扫描(列被隐式转成数字);后者正常走索引。修复只需给常量加引号。与 M06 连考;线上排障高频。
【知识关联】
- 补题关联:补-24(金额用 DECIMAL,避免浮点与隐式转换坑)。
- 面试/工程:订单号、手机号、身份证一律字符串;MyBatis
#{}会保留类型,${}与手写拼接更容易踩隐式转换。 - 面试追问:①
int列写WHERE id='100'为何仍走索引? ② 隐式转换除了索引失效还会带来哪些正确性风险?
【拓展延伸】
- 变式问法:给
phone varchar与id int两组对比;或问“转换发生在列侧还是常量侧”。 - 参数/命令:观察
SHOW WARNINGS可见截断/转换警告;EXPLAIN中type=ALL且无可用 key 是典型信号。JDBC URL 建议统一字符集characterEncoding=utf8mb4。
M08. 对 varchar(255) 的 email 列建索引时,更合理的做法是?
考点:前缀索引与索引选择性
A. 直接建全列索引,不考虑长度 B. 把 email 改成 text 再建索引 C. 不建索引,靠全表扫描 D. 使用前缀索引 INDEX(email(10)),在节省空间与保证选择性之间取平衡
答案:D
【考点】前缀索引与索引选择性的权衡。
【结论】 长字符串列建全列索引会让索引体积膨胀,而前缀索引 email(10) 只索引前 10 个字符,在索引体积与选择性之间取得平衡。选 D。
【逐项辨析】
- A 直接建全列索引、不考虑长度:错。错在“不考虑长度” ——
varchar(255)的全列索引体积大、页数多、树高、IO 增加;utf8mb4 下索引键长达 1020 字节,部分行格式下还会触及单列索引长度上限。 - B 把
email改成text再建索引:错。错在“改成text” ——TEXT列建索引必须指定前缀长度,且大文本会存到溢出页、读写更慢,反而更差。 - C 不建索引、靠全表扫描:错。错在“不建索引” —— 这是用性能换空间,方向反了;邮箱通常是高频查询条件。
- D 使用前缀索引
INDEX(email(10)),在节省空间与保证选择性之间取平衡:正确。 既压缩了索引体积,又能保住大部分区分度。
【知识点】 前缀索引(prefix index)的定义:对字符串列只取前 n 个字符建立索引,即 INDEX(col_name(n))。
| 对比项 | 全列索引 | 前缀索引 col(n) |
|---|---|---|
| 索引体积 | 大(含完整值) | 小(只存前 n 个字符) |
| 选择性 | 最高 | 可能降低(前缀相同的值区分不开) |
| 能否做覆盖索引 | 能 | 不能(不含完整列值,无法直接返回) |
能否用于该列的 ORDER BY / GROUP BY | 能 | 不能(索引中不是完整值,无法保证有序) |
| 回表比对 | 无需额外比对 | 前缀相同者需回表比对完整值 |
n 怎么定?选择性(区分度) 的定义是 COUNT(DISTINCT col) / COUNT(*),越接近 1 越好,逐步试出区分度接近全列索引的最小 n:
SELECT COUNT(DISTINCT LEFT(email, 7)) / COUNT(*) AS sel7,
COUNT(DISTINCT email) / COUNT(*) AS sel_full
FROM t;再加大 n 收益很小,却白白增大索引体积。
【记忆锚点】 “前缀索引省空间,代价是选择性、覆盖索引和排序能力”。
【易混对比】
- 前缀索引 vs 联合索引:前者是“同一列截前
n个字符”,后者是“多列组合”;前者丢列内的后缀信息,后者丢列间的组合信息。 - 前缀索引 vs 覆盖索引:前缀索引天然不能做覆盖索引(索引里没有完整列值),也不能用于该列的
ORDER BY/GROUP BY。 - 选择性高 vs 选择性低:
email、订单号适合建索引;性别、状态选择性极低,建索引往往不如不建。
【自测】 email 列 100 万行,COUNT(DISTINCT email) = 100 万,COUNT(DISTINCT LEFT(email, 8)) = 99.9 万,选 email(8) 划算吗?
答:划算。区分度已达 99.9%,几乎不损失选择性,索引体积却大幅下降;代价是不能做覆盖索引、不能用于
ORDER BY/GROUP BY。与 M04、M05 连考。
【知识关联】
- 补题关联:补-24(长字段类型选择)、补-25(索引体积影响扫描代价)。
- 面试/工程:邮箱、URL、JSON 摘要列常用前缀索引;但报表“按邮箱排序/覆盖查”就不能再用前缀索引。
- 面试追问:① 如何用 SQL 选最小前缀长度 n? ② 前缀索引对
ORDER BY email有何影响?
【拓展延伸】
- 变式问法:问“前缀索引的代价”而非“是否该建”;或对比全文索引/倒排在搜索场景的替代。
- 参数/命令:InnoDB 索引键长上限相关:DYNAMIC 行格式下前缀可达 3072 字节;
innodb_large_prefix(旧版本)已废弃。选择性 SQL:COUNT(DISTINCT LEFT(email,n))/COUNT(*)。
M09. MySQL InnoDB 存储引擎默认的事务隔离级别是?
考点:默认隔离级别
A. READ UNCOMMITTED(读未提交) B. READ COMMITTED(读已提交) C. SERIALIZABLE(可串行化) D. REPEATABLE READ(可重复读)
答案:D
【考点】四种隔离级别的定义与 MySQL 默认值。
【结论】 MySQL InnoDB 的默认隔离级别是 REPEATABLE READ(可重复读,RR),并在 RR 下用间隙锁 + Next-Key Lock 在很大程度上避免了幻读。选 D。
【逐项辨析】
- A READ UNCOMMITTED:错。最低级别,允许脏读,并发问题最多,不可能作默认。
- B READ COMMITTED:错。错在把“别人的默认”当成了 MySQL 的默认 —— RC 是 Oracle / SQL Server / PostgreSQL 的默认值。
- C SERIALIZABLE:错。串行化用加锁把事务变成串行执行,并发度最低,仅用于特殊场景。
- D REPEATABLE READ:正确。 MySQL InnoDB 的默认值,且额外解决了标准 RR 允许的幻读问题。
【知识点】 SQL 标准定义了四种隔离级别,级别越高一致性越强、并发度越低:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 默认使用者 |
|---|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 | 极少使用 |
| READ COMMITTED | 不会 | 可能 | 可能 | Oracle / SQL Server / PostgreSQL |
| REPEATABLE READ | 不会 | 不会 | 标准允许,InnoDB 基本避免 | MySQL InnoDB |
| SERIALIZABLE | 不会 | 不会 | 不会 | 特殊场景 |
InnoDB 在 RR 下的实现:快照读(普通 SELECT)靠 MVCC + ReadView 提供一致性读,ReadView 在事务首次快照读时创建,故同事务内反复读结果一致;当前读(SELECT ... FOR UPDATE、UPDATE、DELETE)靠 Next-Key Lock(记录锁 + 间隙锁) 阻塞范围内插入,从而避免幻读。查看当前值:MySQL 8.0 用 SELECT @@transaction_isolation;,5.7 及以前用 SELECT @@tx_isolation;。
【记忆锚点】 “MySQL 默认 RR,Oracle 默认 RC”。
【易混对比】
- RR vs RC:RR 的
ReadView只建一次,反复读结果不变;RC 每次快照读都新建ReadView,能读到别人已提交的新数据。 - 标准 RR vs InnoDB RR:标准下 RR 允许幻读,InnoDB 用 Next-Key Lock 基本避免 —— 最常考的差异点。
【自测】 Oracle 上要求“同一事务内两次查询结果完全一致”,隔离级别要调成什么?
答:REPEATABLE READ。Oracle 默认 READ COMMITTED 会出现不可重复读,需显式提升到 RR。与 M10 连考;国企/互联网笔试高频。
【知识关联】
- 补题关联:补-17(隔离性与持久性)、补-18(2PL 保证可串行化)。
- 面试/工程:互联网业务多用 RC + 合理索引换取并发;金融账务偶用 RR;切级别要评估“幻读是否可接受”与锁行为差异。
- 面试追问:① 为什么很多公司将 MySQL 从 RR 改成 RC? ② RC 下还能用间隙锁吗?
【拓展延伸】
- 变式问法:问 Oracle 默认级别(RC);问“标准 RR 允许幻读但 InnoDB 如何处理”。
- 参数/命令:8.0:
SET GLOBAL transaction_isolation='READ-COMMITTED';或SET SESSION ...;查看SELECT @@transaction_isolation;。5.7 用tx_isolation。
M10. “同一事务内两次执行同一条范围查询,第二次读到了其他事务新插入的行”,这属于哪种并发问题?
考点:幻读的识别
A. 脏读 B. 不可重复读 C. 幻读 D. 丢失更新
答案:C
【考点】脏读 / 不可重复读 / 幻读的区分标准。
【结论】 题干是“同一事务内两次执行同一条范围查询,第二次多出别人新插入的行”,即结果集行数变了,这是幻读。选 C。
【逐项辨析】
- A 脏读:错。脏读的关键字眼是“未提交” —— 读到了别的事务尚未提交、随后可能回滚的数据,而题干读到的是已提交的新插入行。
- B 不可重复读:错。不可重复读针对“同一行的值被
UPDATE改掉”,是值的改变;题干是行数的增加。 - C 幻读:正确。 判定标志是“同一范围两次查询,结果集行数因其它事务
INSERT/DELETE而变化”。 - D 丢失更新:错。丢失更新是两个事务基于同一旧值各自
UPDATE、后者覆盖前者,属写冲突而非“读现象”,也不是隔离级别能解决的问题。
【知识点】 三个“读现象”只用一把尺子区分 —— 第二次读到了什么变化:
| 现象 | 读到的变化 | 触发操作 | 关键词 |
|---|---|---|---|
| 脏读 | 别的事务未提交的修改 | UPDATE | “未提交” |
| 不可重复读 | 同一行的值被改了 | UPDATE | “行、值变” |
| 幻读 | 同一范围的行数变了 | INSERT / DELETE | “范围、行数变” |
记忆顺序:脏读看“提交没提交”,不可重复读看“同一行的值”,幻读看“同一范围的行数”。
补充:丢失更新不属于 SQL 标准定义的读现象,隔离级别也解决不了,需靠悲观锁(SELECT ... FOR UPDATE 先锁行再更新)或乐观锁(UPDATE ... SET v = v + 1 WHERE id = ? AND version = ?)处理。InnoDB 在 RR 下,快照读由 MVCC 保证同事务内读结果一致,当前读由 Next-Key Lock 阻止范围内插入,幻读基本被避免;考试按标准定义判定即可。
【记忆锚点】 “脏读看未提交,不可重复读看值,幻读看行数”。
【易混对比】
- 不可重复读 vs 幻读:值变 → 不可重复读(
UPDATE);行数变 → 幻读(INSERT/DELETE)。看的是“改行内”还是“改行数”。 - 幻读 vs 丢失更新:幻读是“读”的问题,由隔离级别解决;丢失更新是“写”的问题,由锁 / 版本号解决。
- RR 下的幻读:快照读走 MVCC,当前读走 Next-Key Lock,两者共同压制幻读(见 M09)。
- 换问法:若题干改成“第二次读到的行值与第一次不同”,答案就变成不可重复读。
【自测】 事务 A 内两次执行 SELECT COUNT(*) FROM t WHERE age > 20,第一次 5 行、第二次 8 行;期间事务 B 提交了 3 条 age = 30 的插入。这属于什么现象?
答:幻读(同一范围、行数由 5 变 8,由
INSERT引起)。若 B 只是把某行age从 18 改成 30,则属不可重复读。与 M09 连考;大厂面试高频。
【知识关联】
- 补题关联:补-17(隔离性)、补-18(2PL)、补-19(S/X 相容)——幻读是“范围+插入”型异常。
- 面试/工程:识别题的关键是看“两次查询结果集是否多出行”。不可重复读是同一行值变了(UPDATE),幻读是行数变多了(INSERT)。互联网面试常连着问“RR 真的完全防住幻读了吗”——InnoDB RR + Next-Key 对当前读防得较好,快照读靠 MVCC;极端混合读写仍有讨论空间。
- 面试追问:① 不可重复读与幻读能否只用行锁区分?(不能,插入需要锁区间/唯一约束,故要 Gap/Next-Key) ② RC 下有幻读吗?(有,因为无普通间隙锁)
【拓展延伸】
- 变式问法:给出四段场景(脏读/不可重复读/幻读/都能避免)选级别;或直接给 SQL 序列问异常类型。
- 参数/命令:
SELECT @@transaction_isolation;SET TRANSACTION ISOLATION LEVEL;SHOW ENGINE INNODB STATUS的锁等待段;压测复现用两个会话交错事务。
M11. InnoDB 在 REPEATABLE READ 级别下,主要依靠什么机制避免幻读?
考点:InnoDB 如何避免幻读
A. 间隙锁(Gap Lock)与 Next-Key Lock 锁住范围,配合 MVCC 快照读 B. 表级排他锁 C. 只靠 MVCC,不需要加锁 D. 强制升级为串行化执行
答案:A
【考点】当前读与快照读的区分,以及间隙锁在避免幻读中的作用。
【结论】 选 A —— InnoDB 在 REPEATABLE READ 下靠两条腿避免幻读:快照读走 MVCC(读历史版本,看不到新插入的行),当前读走间隙锁 / Next-Key Lock(锁住区间,让别人插不进来)。
【逐项辨析】
- A 间隙锁与 Next-Key Lock 锁住范围,配合 MVCC 快照读:正确。 两条腿都写到了:MVCC 管快照读,间隙锁管当前读,缺一不可。
- B 表级排他锁:错。表级 X 锁确实能挡住幻读,但那是“串行化”的思路,InnoDB 的 RR 并不靠它,题干问的是“主要依靠什么机制”。
- C 只靠 MVCC,不需要加锁:错。MVCC 只管快照读;
SELECT ... FOR UPDATE这类当前读若不锁区间,别的事务插入新行后本事务会读到多出来的行,仍会幻读。 - D 强制升级为串行化执行:错。那是 SERIALIZABLE 的做法,RR 无需升级,说“强制升级”是错的。
【知识点】 把“两种读”讲清楚,幻读问题就迎刃而解。
| 读法 | 典型语句 | 读的是什么 | 靠什么防幻读 |
|---|---|---|---|
| 快照读(一致性读) | 普通 SELECT | 事务首次读时生成的 ReadView 对应的历史版本 | MVCC:ReadView 生成后新插入的行 trx_id 超出可见范围,天然读不到 |
| 当前读(锁定读) | SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE、UPDATE、DELETE、INSERT | 最新已提交版本 | Next-Key Lock:锁住区间,别的事务插不进来 |
Next-Key Lock = Record Lock + Gap Lock,加锁区间是左开右闭 (a, b](常见于非唯一索引或范围查询)。例如非唯一索引上有 5、10 两个值,执行范围锁定时,InnoDB 可能锁住 (5, 10] 这一段。注意:若 id 为唯一索引(主键)且做等值匹配、目标行不存在(如 WHERE id = 7 FOR UPDATE),InnoDB 只加 Gap Lock,锁定开区间 (5, 10),不会对记录 10 再加 Record Lock。无论哪种,别的事务都无法在 5 与 10 之间插入 id = 6、7、8 的新行 → 幻读被堵住。
推导:幻读的定义是“同一事务内两次相同的范围查询,第二次多出了行”。多出来的行必然来自其他事务的插入,所以防幻读的本质就是“别让人在我关心的区间里插入” —— 这正是间隙锁的职责。而快照读读的是历史版本,新插入的行对它不可见,因此无需加锁。
两个必须记住的边界:① 间隙锁只在 REPEATABLE READ 及以上生效,READ COMMITTED 下没有间隙锁,所以 RC 无法避免幻读;② 间隙锁只在当前读时加,普通 SELECT 不加锁。
【记忆锚点】 “快照读走 MVCC,当前读走间隙锁” —— 两条腿走路,缺一条就漏幻读。
【易混对比】
- 幻读 vs 不可重复读:不可重复读是“同一行的值被改了”,幻读是“结果集的行数变了”(多了 / 少了行);前者靠行锁 + MVCC,后者必须靠间隙锁。
- 间隙锁 vs Next-Key Lock:间隙锁只锁“缝”
(a, b),Next-Key Lock 是“记录 + 缝”(a, b];RC 下退化为 Record Lock。 - RR 是否完全避免幻读:快照读下完全避免,当前读下靠间隙锁避免;但当前读与快照读混用时(先普通
SELECT、再FOR UPDATE),仍可能看到此前已提交的插入行,这是“看起来像幻读”的经典现象。 - 换问法:若题目问“哪个隔离级别下没有间隙锁”,答案是 READ COMMITTED。
【自测】 事务 A 在 RR 下先执行 SELECT * FROM t WHERE id > 10(普通查询),事务 B 插入 id = 15 并提交,事务 A 再执行同一条普通查询。A 会看到 id = 15 吗?若第二次改成 SELECT * FROM t WHERE id > 10 FOR UPDATE 呢?
答:第一次不会看到(快照读走 MVCC,ReadView 在首次查询时已确定);第二次会看到(当前读读最新数据,而间隙锁在第二次查询时才加,锁不住已经提交的插入)。与 M12、M18 连考;大厂面试高频。
【知识关联】
- 补题关联:补-19(锁相容)、补-18(2PL 与隔离级别映射)。
- 面试/工程:RR + Next-Key 是“默认更强”的卖点,但锁范围更大,热点更新更容易锁等待;不少团队改 RC 正是为了减少锁冲突。
- 面试追问:① 什么情况下 RR 下仍可能出现幻读? ②
WHERE id=1(唯一索引等值)会加间隙锁吗?
【拓展延伸】
- 变式问法:问“快照读靠什么避免幻读”(MVCC)vs“当前读靠什么”(Next-Key Lock)。
- 参数/命令:
innodb_locks_unsafe_for_binlog(旧)曾影响间隙锁;8.0 用performance_schema.data_locks查看锁详情。等值命中唯一键后间隙锁会退化为记录锁。
M12. InnoDB 的 MVCC(多版本并发控制)主要依赖哪些要素实现?
考点:MVCC 的实现要素
A. 仅 ReadView 即可 B. redo log + binlog C. 表级锁 + 行级锁 D. undo log + 隐藏列(trx_id、roll_pointer)+ ReadView
答案:D
【考点】MVCC 的三大组成要素及其协作方式。
【结论】 选 D —— MVCC 靠隐藏列(DB_TRX_ID + DB_ROLL_PTR)+ undo log 版本链 + ReadView 可见性判断三者协作实现;redo log 与 binlog 都不参与。
【逐项辨析】
- A 仅 ReadView 即可:错。ReadView 只是一张“可见性判据表”,没有 undo log 中的历史版本就无从比对,错在这个“仅”字。
- B redo log + binlog:错。redo log 管崩溃恢复(持久性),binlog 管主从复制与归档,二者都不参与版本可见性判断。
- C 表级锁 + 行级锁:错。MVCC 的设计目标恰恰是“读不加锁、读写不阻塞”,锁与 MVCC 是两条并列的并发控制路线。
- D undo log + 隐藏列(
trx_id、roll_pointer)+ ReadView:正确。 三者缺一不可:隐藏列提供“版本指针”,undo log 提供“旧版本数据”,ReadView 提供“哪些版本可见”的判据。
【知识点】 MVCC 要解决的问题是:同一条记录被多个事务并发读写时,读的人如何在不加锁的前提下看到“自己该看到的那一版”。解法是“每条记录保留多个历史版本,读的时候按规则挑一版”。
| 要素 | 具体内容 | 作用 |
|---|---|---|
| 隐藏列 | DB_TRX_ID(6 字节,最后修改该行的事务 ID)、DB_ROLL_PTR(7 字节,回滚指针,指向 undo log 中的旧版本)、DB_ROW_ID(6 字节,仅在无主键且无唯一非空索引时才有) | 每行自带的“版本戳”与“上一版指针” |
| undo log | 记录反向操作,通过回滚指针串成版本链 | 提供历史版本数据 |
| ReadView | 四个字段:m_ids(生成时的活跃事务 ID 列表)、min_trx_id、max_trx_id、creator_trx_id | 提供可见性判据 |
版本链的形成:每次修改一行,旧值先写入 undo log,新行的 DB_ROLL_PTR 指向这条 undo 记录,于是形成 当前行 → 旧版本 → 更旧版本 → … 的单向链表。
可见性判断规则(沿版本链从新到旧逐个比对,取第一个可见版本):
设该版本的 trx_id = T,ReadView 的活跃事务列表为 m_ids
① T == creator_trx_id → 可见(本事务自己改的)
② T < min_trx_id → 可见(该事务早已提交)
③ T >= max_trx_id → 不可见(该事务在 ReadView 之后才开启)
④ min_trx_id <= T < max_trx_id → 若 T 在 m_ids 中则不可见(尚未提交),否则可见RC 与 RR 的差别只在 ReadView 的生成时机:
| 隔离级别 | ReadView 生成时机 | 效果 |
|---|---|---|
| READ COMMITTED | 每次 SELECT 都重新生成 | 能读到别人刚提交的数据 → 产生不可重复读 |
| REPEATABLE READ | 第一次 SELECT 时生成,整个事务复用 | 每次读都是同一张快照 → 可重复读 |
【记忆锚点】 “一行两列一条链,一张快照定可见” —— 隐藏列是“戳”,undo 链是“旧版本仓库”,ReadView 是“裁判”。
【易混对比】
- undo log vs redo log:undo 管“回滚与旧版本”(逻辑日志,反向操作),redo 管“崩溃恢复”(物理日志,正向重做),一个向后、一个向前。
DB_TRX_IDvsDB_ROLL_PTR:前者回答“谁改的”(判可见性),后者回答“旧版在哪”(找上一版)。- MVCC vs 锁:MVCC 让“读”不加锁,锁让“写”互斥;RR 下当前读仍会加 Next-Key Lock(见 M11)。
- 换问法:若题目问“ReadView 包含哪些字段”,答案就是
m_ids/min_trx_id/max_trx_id/creator_trx_id。
【自测】 事务 D 在 RR 下第一次 SELECT 后,事务 B 修改了同一行并提交,事务 D 再次 SELECT 该行。D 看到的是 B 修改后的值吗?
答:看不到。RR 下 ReadView 在 D 第一次
SELECT时生成并复用,B 的trx_id落在 D 的m_ids(活跃列表)中 → 判为不可见 → D 沿版本链找到自己该看的那一版。与 M11、M13、M15 连考;大厂面试高频。
【知识关联】
- 补题关联:补-17(隔离性实现)、补-20(崩溃恢复与 undo 的关系)。
- 面试/工程:长事务导致 undo 链过长、回滚段膨胀、purge 滞后,是大促后数据库变慢的经典根因。
- 面试追问:① ReadView 何时创建?RC 与 RR 差在哪? ② 当前读会不会走 MVCC?
【拓展延伸】
- 变式问法:问“哪些列参与可见性判断”(trx_id、roll_pointer、ReadView);问“删除在 MVCC 里如何表示”(delete mark)。
- 参数/命令:
information_schema.INNODB_TRX看活跃事务;SHOW ENGINE INNODB STATUS的 TRANSACTIONS 段;避免autocommit=0长时间挂起。
M13. 关于 InnoDB 的 redo log,下列说法正确的是?
考点:redo log
A. 是 InnoDB 的物理日志,记录数据页的物理修改,用于崩溃恢复,保证持久性 B. 是 MySQL Server 层的逻辑日志,主要用于主从复制 C. 用于事务回滚,保存数据的旧版本 D. 记录的是原始 SQL 语句文本
答案:A
【考点】redo log(重做日志)的所属层级、日志类型、写入方式与职责。
【结论】 选 A —— redo log 是 InnoDB 引擎层的物理日志,记录数据页的物理修改,采用固定大小、循环写,靠 WAL 机制保证崩溃后已提交事务不丢(持久性)。
【逐项辨析】
- A InnoDB 的物理日志,记录数据页的物理修改,用于崩溃恢复,保证持久性:正确。 四个要点全对:引擎层、物理日志、崩溃恢复、持久性。
- B MySQL Server 层的逻辑日志,主要用于主从复制:错。这是 binlog 的描述,redo log 在引擎层且是物理的。
- C 用于事务回滚,保存数据的旧版本:错。这是 undo log 的职责,且方向恰好相反 —— undo 是“反向撤销”,redo 是“正向重做”。
- D 记录的是原始 SQL 语句文本:错。那是 binlog 的
STATEMENT格式;redo log 记的是“某表空间某页某偏移处改成了什么”,不含 SQL 文本。
【知识点】 三个日志必须一起记,先看总表:
| 日志 | 所属层 | 类型 | 记录内容 | 写入方式 | 主要职责 |
|---|---|---|---|---|---|
| redo log | InnoDB 引擎层 | 物理日志 | 数据页的物理修改 | 固定大小、循环写 | 崩溃恢复 → 持久性(D) |
| undo log | InnoDB 引擎层 | 逻辑日志 | 反向操作(旧版本) | 随事务增长,由 purge 清理 | 回滚 → 原子性(A);MVCC 版本链 |
| binlog | MySQL Server 层 | 逻辑日志 | 语句或行变更 | 追加写,持续增长 | 主从复制、基于时间点的恢复 |
redo log 的核心机制是 WAL(Write-Ahead Logging):修改数据时先写 redo log、再改内存中的数据页,脏页由后台线程异步刷盘。这样即使刷脏页之前断电,重启后也能用 redo log 把已提交的修改“重做”出来。
写盘节奏由 innodb_flush_log_at_trx_commit 控制:
| 取值 | 行为 | 数据安全性 |
|---|---|---|
1(默认) | 每次事务提交都 fsync 到磁盘 | 最安全,不丢已提交事务,但 IO 开销最大 |
0 | 每秒 fsync 一次 | 可能丢 1 秒内提交的事务 |
2 | 每次提交写入 OS cache,不 fsync | MySQL 进程崩溃不丢,机器断电可能丢 |
推导:redo log 是固定大小、循环覆盖写的(总容量由 innodb_log_file_size × innodb_log_files_in_group 决定),因此它不能长期归档;而“崩溃后靠重做恢复”只要求日志在脏页落盘之前不丢失,循环写的容量恰好够用 —— 这就是它必须“小、快、循环”的原因。
【记忆锚点】 “redo 重做保持久、undo 回滚保原子、binlog 归档保复制” —— 三个日志三件事,别串门。
【易混对比】
- redo log vs binlog(核心三差异):① 层级(引擎层 vs Server 层);② 内容(物理 vs 逻辑);③ 写入(循环写 vs 追加写),详见 M14。
- redo log vs undo log:redo 是“向前重做”(崩溃后把已提交的改回来),undo 是“向后回滚”(撤销未提交的改)。
- redo log 的边界:它只保证已提交事务不丢;未提交事务的撤销要靠 undo log。
- 换问法:若题目问“哪个日志是 Server 层、追加写、用于主从复制”,答案换成 binlog。
【自测】 把 innodb_flush_log_at_trx_commit 从 1 改成 2,事务提交后机器突然断电,已提交的事务会丢吗?
答:可能丢。取值
2时提交只写入 OS cache 未 fsync,机器断电会导致 OS cache 内容丢失;但若只是 MySQL 进程崩溃而机器未断电,则不会丢。与 M14、M16 连考;生产调优高频。
【知识关联】
- 补题关联:补-20(日志+检查点恢复)。
- 面试/工程:redo 让数据库具备 crash-safe;SSD 下
innodb_flush_log_at_trx_commit=1的延迟已可接受,金融场景不应为吞吐改为 2。 - 面试追问:① redo 是物理日志还是逻辑日志? ② 为什么 redo 要循环写、空间有限也能工作?
【拓展延伸】
- 变式问法:对比 redo vs binlog 的“是否崩溃恢复必需”“是否引擎无关”。
- 参数/命令:
innodb_log_file_size、innodb_log_files_in_group(8.0.30+ 改为innodb_redo_log_capacity)、innodb_flush_log_at_trx_commit(0/1/2)。
M14. 关于 binlog(归档日志),下列说法正确的是?
考点:binlog
A. 是 InnoDB 引擎层日志,记录数据页的物理修改 B. 采用固定大小循环写,写满即覆盖 C. 是 MySQL Server 层日志,记录逻辑变更(语句或行变更),主要用于主从复制与数据恢复 D. 是实现 MVCC 的核心
答案:C
【考点】binlog 的所属层级、日志格式、写入方式,以及与 redo log 的三大区别。
【结论】 选 C —— binlog 是 MySQL Server 层的逻辑日志,记录语句或行的逻辑变更,采用追加写,用于主从复制与基于时间点的数据恢复。
【逐项辨析】
- A InnoDB 引擎层日志,记录数据页的物理修改:错。这是 redo log 的描述;binlog 属 Server 层,与存储引擎无关(MyISAM 也有 binlog)。
- B 采用固定大小循环写,写满即覆盖:错。“循环写、会覆盖”是 redo log 的特征;binlog 是追加写,文件只增不减,靠过期清理策略删除旧文件(8.0 用
binlog_expire_logs_seconds,5.7 用expire_logs_days)。 - C MySQL Server 层日志,记录逻辑变更,用于主从复制与数据恢复:正确。 层级、内容、用途三项全对。
- D 是实现 MVCC 的核心:错。MVCC 靠隐藏列 + undo log + ReadView(见 M12),binlog 完全不参与。
【知识点】 binlog 是逻辑日志,“逻辑”的含义是:它记的是“发生了什么变更”,而不是“哪个页的哪个字节变成了什么”。三种格式对照:
| 格式 | 记录内容 | 优点 | 缺点 |
|---|---|---|---|
STATEMENT | 原始 SQL 语句 | 日志量小 | 含不确定函数(NOW()、UUID())时主从可能不一致 |
ROW(生产推荐) | 每行数据的前后值 | 主从一致性强,可精准恢复 | 日志量大(尤其批量 UPDATE) |
MIXED | 自动在两者间切换 | 兼顾体积与一致性 | 行为依赖 MySQL 判断,不易预测 |
binlog 与 redo log 的三大区别(高频考点):
| 对比项 | redo log | binlog |
|---|---|---|
| 所属层级 | InnoDB 引擎层 | MySQL Server 层(所有引擎共用) |
| 日志内容 | 物理日志(数据页修改) | 逻辑日志(语句 / 行变更) |
| 写入方式 | 固定大小、循环写 | 追加写,持续增长 |
推导:正因为 binlog 是 Server 层的、追加写的、跨引擎通用的,它才能充当“全局操作流水账”——从库重放这份流水账就能追上主库;也正因为文件不会被覆盖,才能用它做“恢复到任意时间点”(先还原全量备份,再重放 binlog 到目标时刻)。
崩溃恢复中的分工:redo log 决定“已提交的事务要不要提交”,binlog 决定“这份变更要不要发给从库”,二者的衔接靠两阶段提交(见 M16)。
【记忆锚点】 “redo 是引擎层物理循环,binlog 是 Server 层逻辑追加” —— 三层三词,一字不差。
【易混对比】
- binlog vs redo log:见上表三差异;考试最爱把“循环写、写满覆盖”安到 binlog 头上(本题 B 项)。
- 用途对照:binlog → 主从复制 + 时间点恢复;redo log → 崩溃恢复(持久性)。
STATEMENTvsROW:前者记“做了什么操作”,后者记“数据变成了什么样”;涉及不确定函数、触发器、无ORDER BY的LIMIT时,ROW更安全。- 换问法:若题目问“哪个日志可用于基于时间点的恢复”,答案仍是 binlog。
【自测】 生产环境用 ROW 格式的 binlog,某次误执行 UPDATE t SET status = 0 影响 100 万行,binlog 会记录 100 万行变更还是 1 条 SQL?
答:100 万行变更。
ROW格式逐行记录前后值,日志体积会暴涨 —— 这也是大事务必须拆批的原因之一。与 M16 连考;生产运维高频。
【知识关联】
- 补题关联:补-20(恢复依据)。
- 面试/工程:主从复制、数据订阅(Canal)、时间点恢复(PITR)都靠 binlog;误删数据后的标准动作是停写 + 从备份与 binlog 做 PITR。
- 面试追问:① binlog 三种格式如何选? ② 为什么推荐 ROW?
【拓展延伸】
- 变式问法:问“redo 与 binlog 谁先写谁后写”(两阶段提交耦合);问“从库拿什么重放”。
- 参数/命令:
binlog_format=ROW|STATEMENT|MIXED;binlog_row_image=FULL|MINIMAL|NOBLOB;PURGE BINARY LOGS;mysqlbinlog --base64-output=DECODE-ROWS -v。
M15. undo log 的主要作用是?
考点:undo log
A. 崩溃恢复时重做已提交的修改 B. 事务回滚(原子性)+ 为 MVCC 提供历史版本(快照读) C. 主从复制的数据传输 D. 记录慢查询语句
答案:B
【考点】undo log 的两大职责,以及它与 redo log 的分工。
【结论】 选 B —— undo log 承担两大职责:事务回滚保证原子性,以及为 MVCC 提供历史版本支撑快照读。
【逐项辨析】
- A 崩溃恢复时重做已提交的修改:错。这是 redo log 的职责,方向是“重做”而非“撤销”。
- B 事务回滚(原子性)+ 为 MVCC 提供历史版本(快照读):正确。 两大职责缺一不可,这也是 undo log 最容易被漏记的地方。
- C 主从复制的数据传输:错。主从复制靠 binlog 传输变更,undo log 不参与。
- D 记录慢查询语句:错。慢查询记录在慢查询日志(
slow_query_log)中,与 undo log 无关。
【知识点】 undo log 是逻辑日志,记录的是“反向操作”:
| 原操作 | undo log 中记录的反向操作 |
|---|---|
INSERT | DELETE(回滚时删掉这条新插入的记录) |
DELETE | INSERT(回滚时把删掉的记录插回去) |
UPDATE | 反向 UPDATE(把旧值写回) |
职责一 · 事务回滚(原子性):事务执行过程中每一步修改都先写 undo log;一旦出错或显式 ROLLBACK,就按 undo log 逆序执行反向操作,把数据恢复到事务开始前的状态 —— 这就是“要么全做,要么全不做”的实现。
职责二 · MVCC 快照读:每行记录的 DB_ROLL_PTR 指向 undo log 中的旧版本,多个旧版本串成版本链,快照读时沿链找到符合自己 ReadView 的那一版(见 M12)。所以 undo log 并非“回滚完就删”,它必须多活一段时间。
两类 undo log 与生命周期:
| 类型 | 产生于 | 提交后能否立即删除 |
|---|---|---|
| insert undo log | INSERT | 可以,提交后即无用(回滚已不可能) |
| update undo log | UPDATE / DELETE | 不可以,需保留供 MVCC 快照读,由 purge 线程在确认无事务需要时清理 |
一个易漏的关键点:undo log 本身也受 redo log 保护。因为 undo 记录写在 undo 表空间的数据页上,对这些页的修改同样要记 redo log,否则崩溃后 undo log 丢失,回滚与 MVCC 都无从谈起。这也解释了“长事务为什么危险”:长事务导致 undo 版本链越拉越长,purge 无法清理,undo 表空间持续膨胀,同时拖慢快照读。
【记忆锚点】 “undo 管回滚与旧版本,redo 管重做与恢复” —— 一个向后撤销,一个向前重做。
【易混对比】
- undo log vs redo log:职责(回滚 / 恢复)、方向(撤销 / 重做)、类型(逻辑 / 物理)全不同;但 undo 页的修改又由 redo 保护,二者并非完全独立。
DB_TRX_IDvsDB_ROLL_PTR:前者判可见性,后者找旧版本,MVCC 中二者配合使用。- 长事务的三重危害:① undo 表空间膨胀;② 版本链过长拖慢快照读;③ 锁持有时间长、易死锁。
- 换问法:问“哪个日志保证原子性”,答案是 undo log;问“哪个保证持久性”,答案是 redo log。
【自测】 事务 T 执行 UPDATE t SET a = 100 WHERE id = 1(原值 a = 10)后主动 ROLLBACK。undo log 里记录的是什么?回滚由谁触发?
答:记录的是反向操作“把 a 改回 10”(逻辑日志,不记物理页变化);回滚由事务的
ROLLBACK触发,按 undo log 逆序执行。与 M12、M13、M16 连考;大厂面试高频。
【知识关联】
- 补题关联:补-17(事务 ACID 与隔离性实现)、补-20(日志+检查点恢复,undo 参与回滚段与历史版本)。
- 面试/工程:长事务 / 大事务会把 undo 撑大:历史版本链变长 → purge 追不上 → 回滚段与
history list length膨胀 → Buffer Pool 命中率下降、统计信息漂移。大促后“数据库突然变慢”常先查INNODB_TRX找长事务。在线 DDL、回滚大事务、闪回(flashback)都依赖 undo。 - 面试追问:① undo 是否参与崩溃恢复?(参与:未提交事务靠 undo 回滚;已提交但 purge 未完成的版本也可在恢复中清理) ② RR 的可重复读为什么离不开 undo?(快照读要沿 roll_pointer 重建“事务开始时”的版本,版本链就挂在 undo 上)
【拓展延伸】
- 变式问法:问“undo 与 redo 分工”→ undo 保证原子性/支撑 MVCC,redo 保证持久性;问“MVCC 版本链存哪”→ undo 页;问“delete 在 MVCC 里如何表示”→ delete mark,真正物理删除由 purge 完成。
- 参数/命令:
innodb_undo_tablespaces(8.0 默认 2,可独立 truncate);innodb_max_undo_log_size+innodb_undo_log_truncate;观察SHOW ENGINE INNODB STATUS的 History list length、information_schema.INNODB_METRICS的 purge 相关计数;INNODB_TRX.trx_started排查长事务;业务侧拆大事务、及时提交。
M16. MySQL 通过什么机制保证 redo log 与 binlog 的一致性?
考点:两阶段提交
A. 两阶段提交(prepare → 写 binlog → commit) B. 只要写完 redo log 即可,binlog 无关紧要 C. 只要写完 binlog 即可 D. 用表锁把两个日志的写入串起来
答案:A
【考点】内部 XA 两阶段提交(2PC)的执行流程与崩溃恢复判断规则。
【结论】 选 A —— redo log 与 binlog 是两套独立日志,MySQL 用两阶段提交(redo 写 prepare → 写 binlog → redo 置 commit)保证二者一致,进而保证主从一致。
【逐项辨析】
- A 两阶段提交(prepare → 写 binlog → commit):正确。 三步顺序与状态名完全正确。
- B 只要写完 redo log 即可,binlog 无关紧要:错。binlog 决定“这份变更是否发给从库”;若 redo 有而 binlog 无,主库提交了、从库收不到 → 主从不一致。
- C 只要写完 binlog 即可:错。反过来若 binlog 有而 redo 无,从库重放了、主库却回滚了 → 同样主从不一致。
- D 用表锁把两个日志的写入串起来:错。两阶段提交是日志状态机的协调机制,不是靠加锁;加锁既解决不了“崩溃后如何判断”,还会严重损害并发。
【知识点】 问题的根源是:redo log 与 binlog 是两套独立日志,无法用一次原子写完成。任何“先写一个再写另一个”的顺序,在两步之间崩溃都会产生不一致:
| 写入顺序 | 崩溃时机 | 后果 |
|---|---|---|
| 先 redo 后 binlog | redo 写完、binlog 未写 | 主库提交、从库收不到 → 主从不一致 |
| 先 binlog 后 redo | binlog 写完、redo 未写 | 从库重放、主库回滚 → 主从不一致 |
两阶段提交的执行流程:
① 写 redo log,标记为 prepare 状态(事务尚未真正提交)
② 写 binlog,并 fsync 落盘
③ 写 redo log,标记为 commit 状态(事务真正提交)崩溃恢复的判断规则(重启后扫 redo log):
| redo log 状态 | binlog 是否完整 | 处理 |
|---|---|---|
| 已 commit | — | 直接提交(事务已完成) |
| prepare | 完整 | 提交(binlog 已落盘,从库能收到,主从一致) |
| prepare | 不完整 | 回滚(binlog 残缺,若提交则主从必然不一致) |
推导要点:判断的锚点是 binlog 是否完整。只要 binlog 完整落盘,从库就一定能把这份变更重放出来,那么主库也必须提交才能与从库对齐;反之 binlog 残缺,从库无从重放,主库只能回滚。
配套优化:MySQL 5.6 引入 binlog 组提交(group commit),把多个事务的 binlog fsync 合并为一次,缓解两阶段提交带来的额外 IO 开销。
【记忆锚点】 “prepare → binlog → commit,崩了看 binlog 全不全” —— 三步两状态,判据只有一个。
【易混对比】
- 两阶段提交(2PC)vs 两阶段锁(2PL):前者是日志一致性机制,后者是并发控制机制(加锁 / 释放锁两阶段),同名不同物,考试常设混淆项。
- 内部 XA vs 外部 XA:这里的 2PC 是 MySQL 内部用于协调 redo 与 binlog 的;外部 XA 是跨数据库的分布式事务。
- 双 1 配置:
innodb_flush_log_at_trx_commit = 1与sync_binlog = 1同时开启,才能保证崩溃后两阶段提交的判断可靠。 - 换问法:若题目问“崩溃后发现 redo 处于 prepare、binlog 不完整,该怎么办”,答案是回滚。
【自测】 事务提交过程中,写 redo log(prepare)成功、写 binlog 成功,但把 redo log 置为 commit 时断电。重启后该事务会提交还是回滚?
答:提交。此时 redo 处于 prepare 且 binlog 完整,按规则判为提交 —— 否则主库回滚、从库却已重放,造成主从不一致。与 M13、M14 连考;大厂面试高频。
【知识关联】
- 补题关联:补-20(恢复时 redo/binlog 如何对齐)。
- 面试/工程:组提交(group commit)优化的是 2PC 的 fsync 次数;半同步复制也建立在 binlog 落盘之上。
- 面试追问:① 2PC 阶段失败分别怎么恢复? ② 为什么 redo 已写、binlog 未写时必须回滚?
【拓展延伸】
- 变式问法:给出崩溃点(prepare 后 / binlog 写后 / commit 后)判断事务是否提交。
- 参数/命令:
sync_binlog(0/1/N)、innodb_support_xa(历史参数)、binlog_order_commits;恢复内部用 XID 把 redo 与 binlog 对齐。
M17. InnoDB 中,对没有索引的列作为 WHERE 条件执行 UPDATE 时,会发生什么?
考点:锁退化(无索引更新)
A. 只锁住匹配的行,效率很高 B. 不加任何锁,直接更新 C. 由于无法通过索引定位,会退化为锁住全表(大量行锁),并发性能急剧下降 D. 直接报错,拒绝执行
答案:C
【考点】行锁的加锁依赖索引;无索引更新导致锁范围放大。
【结论】 行锁加在索引记录上,不是加在行上;WHERE 列没有索引 → 定位不到记录 → 逐行扫全表加锁,退化成锁全表。
【逐项辨析】
- A 只锁匹配的行、效率很高:错。这是“有索引”时才会出现的效果,而本题的前提恰恰是没索引。
- B 不加任何锁、直接更新:错。
UPDATE必然加 X 锁,区别只在锁的范围。 - C 退化为锁住全表(大量行锁):正确。 定位不到索引记录,只能扫聚簇索引上的每一条记录并逐一加锁。
- D 直接报错、拒绝执行:错。MySQL 不会因此报错,它会“默默”把锁放大 —— 这才是最危险的地方。
【知识点】 关键在“行锁加在索引上”这一句。InnoDB 加锁的对象是索引记录(index record),所以:
WHERE 条件 | 加锁方式 | 实际锁范围 |
|---|---|---|
主键等值(id = 5) | 主键索引定位到 1 条记录 | 只锁该行 |
| 二级索引等值 | 二级索引定位 + 回表锁主键行 | 少量行 |
| 无索引列 | 扫聚簇索引全部记录、逐条加锁 | ≈ 全表 |
推论:既然锁加在索引上,索引失效 = 锁范围放大。WHERE 列上套函数、隐式类型转换、OR 一侧无索引(见 M06、M07)都会让“本来有索引”的语句退化成全表加锁 —— 这是线上并发雪崩与死锁的常见根因。
再补一个易漏的边界:隔离级别影响锁的释放时机。REPEATABLE READ 下,扫描过但不匹配的行也会一直持锁到事务结束;READ COMMITTED 下 InnoDB 会对不匹配的行提前释放锁(与 semi-consistent read 相关),锁范围相对小一些 —— 但全表扫描的代价依然存在,所以“建索引”仍是唯一正解。
【记忆锚点】 “行锁靠索引,索引一失效、行锁变表锁” —— 判据是“能不能用索引定位到记录”。
【易混对比】
- 无索引 → 锁全表(本题) vs 索引失效 → 同样锁全表(M06、M07):两者后果相同,只是成因不同。
- 锁范围 vs 隔离级别:锁范围由索引决定,不由隔离级别决定;隔离级别只决定“加什么锁(记录锁 / 间隙锁)”以及“锁何时释放”。
- 表锁 vs 行锁退化:前者是显式
LOCK TABLES或引擎不支持行锁(MyISAM);后者是 InnoDB 因无索引而“被迫”逐行加锁,本质仍是行锁,只是数量等于全表行数。 - 换问法:若题干改成“
WHERE列有索引但用了函数”,答案仍是“锁范围放大”。
【自测】 表 t(id 主键, name, age),name 有索引、age 无索引。事务 A 执行 UPDATE t SET age = 1 WHERE age = 20,事务 C 执行 UPDATE t SET age = 2 WHERE id = 1。C 会被阻塞吗?
答:会被阻塞。A 的
WHERE列age无索引 → 逐行扫全表加 X 锁 →id = 1这行也在锁范围内。与 M06、M07、M23 连考;大厂面试高频。
【知识关联】
- 补题关联:补-19(S/X)、补-23(死锁后牺牲事务)。
- 面试/工程:无索引
UPDATE/DELETE是把整表锁住的高危操作;上线前必须EXPLAIN确认访问路径。 - 面试追问:① RC 下无索引更新的锁范围与 RR 有何不同? ② 如何用锁等待诊断“整表卡死”?
【拓展延伸】
- 变式问法:问“DELETE 全表无 WHERE”比“UPDATE 无索引”更危险吗(还可能更慢更锁久)。
- 参数/命令:
SHOW PROCESSLIST、performance_schema.data_lock_waits;innodb_lock_wait_timeout(默认 50s);避免在业务高峰做无索引批量更新,改按主键分批。