这是《MySQL 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 MySQL 面试通常不是考背诵,而是从一道基础题开始不断追问,直到暴露你对索引、事务、日志、复制和故障处理的理解边界。本章把高频问题整理成“问题、核心答案、追问、易错点”的形式,帮助你把前 33 章的知识转成自己的表达。
34.1 答题框架
回答技术问题建议使用四步:
1. 定义:它是什么
2. 原理:为什么这样工作
3. 场景:什么时候有效,什么时候失效
4. 实践:生产中怎么验证和治理
例如被问到索引为什么快,不要只回答“B+Tree”。更好的回答是:
InnoDB 主键是聚簇 B+Tree,数据按主键顺序存放;
查询从根节点到叶子节点,访问次数与树高相关;
二级索引叶子保存主键,必要时回表;
索引能减少扫描,但写入和维护有代价;
生产上要看执行计划、扫描行数、回表和排序成本。
这种回答既展示知识,也展示工程判断。
34.2 基础与架构
问题 1:一条 SELECT 语句的执行流程是什么?
客户端连接
-> 连接器认证与权限检查
-> 解析器生成语法树
-> 预处理做语义检查
-> 优化器选择执行计划
-> 执行器调用存储引擎接口
-> InnoDB 读取 Buffer Pool 或磁盘页
-> 返回结果集
常见追问:
- 权限在哪里检查?连接时会做全局权限检查,语句执行前还会按对象检查;
- Server 层和存储引擎层怎么分工?Server 层负责解析、优化和执行流程,InnoDB 负责数据、事务、锁、MVCC 和持久化;
- MySQL 8.0 为什么移除 Query Cache?命中率不稳定,锁竞争和失效代价高,收益有限。
问题 2:InnoDB 和 MyISAM 有什么区别?
| 维度 | InnoDB | MyISAM |
|---|---|---|
| 事务 | 支持 | 不支持 |
| 锁粒度 | 行级锁,并有表级意向锁等 | 表锁 |
| 外键 | 支持 | 不支持 |
| 崩溃恢复 | Redo / Undo 支持 | 依赖修复工具 |
| MVCC | 支持 | 不支持 |
| 存储组织 | 主键聚簇 B+Tree | 索引和数据分离 |
| 现代默认 | MySQL 5.5 后默认 | 不建议新业务使用 |
加分点:不要说 InnoDB “只有行锁没有表锁”。DDL、AUTO-INC 锁、意向锁和元数据锁仍可能影响整表。
问题 3:为什么新表使用 utf8mb4?
历史 utf8 实际是最长三字节的 utf8mb3,无法完整保存 Emoji、部分生僻字和补充字符;utf8mb4 才是完整 UTF-8 编码。
实践建议:
- 新表统一
utf8mb4; - 显式指定排序规则;
- 关注排序规则混用导致的隐式转换;
- 老表迁移前评估索引长度和存储变化。
34.3 索引与执行计划
问题 4:为什么 InnoDB 选择 B+Tree?
B+Tree 的特点:
- 非叶子节点只保存键和指针;
- 叶子节点保存数据或主键;
- 叶子节点形成链表;
- 等值和范围查询都友好;
- 树高低,减少磁盘页访问次数。
哈希索引适合等值查询,但不适合范围查询、排序和最左前缀匹配。InnoDB 的自适应哈希索引是内部机制,不是用户手工创建的普通哈希索引。
问题 5:什么是回表、覆盖索引和最左前缀?
示例:
CREATE INDEX idx_user_status_created
ON orders (user_id, status, created_at);
SELECT order_no, amount
FROM orders
WHERE user_id = 10001
AND status = 'PAID';
回答:
- 二级索引叶子节点保存
user_id、status、created_at和主键; - 查询还需要的
order_no、amount不在索引中,需要用主键回表; - 如果查询只选择索引列和主键,就是覆盖索引;
- 联合索引遵循最左前缀原则;
- 单独按
status查询无法完整使用该索引。
追问:以下条件分别怎么使用索引?
WHERE user_id = 1 AND status = 'PAID';
WHERE user_id = 1 AND created_at > '2026-08-01';
WHERE status = 'PAID' AND created_at > '2026-08-01';
WHERE user_id > 100;
前三个可能使用索引的不同部分,最后一个只能按 user_id 做范围访问。最终以执行计划和统计信息为准。
问题 6:EXPLAIN 最应该看什么?
优先看:
type:const、eq_ref、ref、range、index、ALL等;key和possible_keys;rows预估扫描行数;filtered过滤比例;Extra是否出现Using filesort、Using temporary、Using index;id与select_type,判断多表和子查询顺序。
MySQL 8.0 可以使用:
EXPLAIN ANALYZE
SELECT order_no, amount
FROM orders
WHERE user_id = 10001
AND status = 'PAID';
EXPLAIN ANALYZE 会真实执行并展示每步耗时和行数,适合验证优化效果,但不要在高峰对大查询随意执行。
问题 7:索引失效的常见原因?
常见原因:
- 对索引列做函数或表达式计算;
- 隐式类型转换;
- 隐式字符集或排序规则转换;
- 前导模糊匹配;
- 不满足联合索引最左前缀;
OR连接了无索引条件;- 优化器认为全表扫描代价更低;
- 统计信息过期;
- 分区裁剪失效;
- 索引被隐藏。
示例改写:
-- 可能无法使用 created_at 索引
WHERE DATE(created_at) = '2026-08-01';
-- 更容易使用范围索引
WHERE created_at >= '2026-08-01 00:00:00'
AND created_at < '2026-08-02 00:00:00';
不要把规则绝对化。不同版本和写法下优化器行为可能不同,最终必须看执行计划。
34.4 事务、MVCC 与锁
问题 8:ACID 分别靠什么实现?
| 特性 | 含义 | 主要机制 |
|---|---|---|
| Atomicity | 全部成功或全部回滚 | Undo Log |
| Consistency | 数据满足约束和业务规则 | 事务、约束、锁、应用逻辑 |
| Isolation | 并发事务互不异常干扰 | MVCC、锁、隔离级别 |
| Durability | 提交后不丢 | Redo Log、Binlog、Doublewrite |
不要说一致性只靠数据库。数据库能保证主键、唯一、外键和 CHECK 等约束,业务不变量仍需要正确的事务边界。
问题 9:四个隔离级别分别解决什么问题?
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 避免 | 可能 | 可能 |
| REPEATABLE READ | 避免 | 避免 | InnoDB 在当前读下通过 Next-Key Lock 避免大部分幻读 |
| SERIALIZABLE | 避免 | 避免 | 避免 |
InnoDB 默认是 REPEATABLE READ。很多其他数据库默认 READ COMMITTED,不要混淆。
追问为什么互联网业务常用 RC:RC 通常锁冲突更少,更容易支撑高并发;代价是同一事务内多次快照读可能看到不同版本,需要业务接受这个语义。
问题 10:MVCC 怎么工作?
核心要素:
- 行隐藏字段:
DB_TRX_ID、DB_ROLL_PTR; - Undo Log 版本链;
- ReadView;
- 活跃事务列表;
- ReadView 创建时机。
可见性判断可以概括为:
版本事务 ID 小于 ReadView 低水位:可见
版本事务 ID 大于高水位:不可见
版本事务仍在活跃列表中:不可见
版本事务不在活跃列表且已提交:可见
RR 在事务第一次快照读时创建 ReadView 并复用;RC 每次快照读都创建新的 ReadView。
问题 11:当前读和快照读的区别?
快照读:
SELECT * FROM orders WHERE id = 1;
当前读:
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
SELECT * FROM orders WHERE id = 1 LOCK IN SHARE MODE;
UPDATE orders SET status = 'PAID' WHERE id = 1;
DELETE FROM orders WHERE id = 1;
INSERT INTO orders (...);
快照读走 MVCC;当前读读取最新版本并加锁。讨论 RR 下的幻读,必须先区分这两种读。
问题 12:行锁、间隙锁和 Next-Key Lock 是什么?
Record Lock:锁索引记录
Gap Lock:锁索引记录之间的间隙,不包含记录本身
Next-Key Lock:Record Lock + Gap Lock
Insert Intention Lock:插入前表达插入意图
示例:
SELECT * FROM users
WHERE age BETWEEN 20 AND 30
FOR UPDATE;
RR 下可能锁住满足条件的记录和相关间隙,阻止其他事务插入导致幻读的新行。如果 age 没有索引,锁范围可能扩大,这是必须强调的易错点。
问题 13:死锁怎么排查和预防?
排查命令:
SHOW ENGINE INNODB STATUS\G
SELECT * FROM sys.innodb_lock_waits;
SELECT * FROM performance_schema.data_locks;
要看四件事:
- 两个事务分别执行了什么;
- 持有和等待的锁;
- 加锁顺序;
- 是否存在无索引条件、唯一冲突、间隙锁或大事务。
预防:
- 缩短事务;
- 按相同顺序访问表和行;
- 为过滤条件建立合适索引;
- 避免在事务中等待外部调用;
- 减少热点行竞争;
- 使用合理重试策略;
- 必要时评估隔离级别和锁范围。
34.5 日志、复制与高可用
问题 14:Redo、Undo、Binlog 的区别?
| 日志 | 层级 | 作用 | 特点 |
|---|---|---|---|
| Redo Log | InnoDB | 崩溃恢复,保证持久性 | 物理日志,顺序写 |
| Undo Log | InnoDB | 回滚和 MVCC 版本链 | 逻辑反操作 |
| Binlog | Server | 复制、恢复、CDC | 逻辑日志,追加写 |
两阶段提交简化流程:
1. InnoDB 写 Redo Log,进入 prepare
2. 写 Binlog
3. InnoDB 将事务置为 commit
如果 Redo 已 prepare 而 Binlog 未成功,崩溃恢复时回滚;如果 Binlog 完整,则提交。这样避免主库、Binlog 和从库数据不一致。
问题 15:主从复制流程是什么?
主库事务提交
-> 写 Binlog
-> Dump Thread 发送 Binlog
-> 从库 IO Thread 接收
-> 写 Relay Log
-> SQL Thread / Worker 回放
-> 从库数据更新
延迟常见原因:
- 主库大事务;
- 从库回放瓶颈;
- 从库硬件或磁盘差;
- 从库承担重查询;
- 表缺少主键或合适索引;
- 网络带宽不足;
- DDL 回放慢。
处理:
- 拆小事务;
- 开并行复制;
- 下线重查询;
- 补索引;
- 临时读主要谨慎并限流;
- 无法收敛时重建或切换。
问题 16:异步、半同步和 MGR 怎么选?
| 方案 | 一致性 | 延迟 | 复杂度 |
|---|---|---|---|
| 异步复制 | 可能丢事务 | 低 | 低 |
| 半同步 | 至少一个从库确认 | 中 | 中 |
| MGR | 多数派一致 | 中高 | 高 |
加分点:不要只说方案名称,要继续分析 RPO、RTO、客户端重试、切换工具、脑裂保护和数据校验。
34.6 性能与生产治理
问题 17:慢 SQL 怎么优化?
标准流程:
1. 确认 SQL、参数、耗时、扫描行数
2. 查看执行计划
3. 判断访问类型、索引、回表、排序、临时表
4. 检查表结构和统计信息
5. 改写 SQL 或调整索引
6. 用同量级数据验证
7. 观察线上耗时和资源
8. 固化慢查询治理流程
不要只回答“加索引”。索引解决扫描问题,但不能解决大结果集传输、锁等待、网络往返、N+1 查询和不合理数据模型。
问题 18:Buffer Pool 命中率低怎么办?
先看:
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
再判断:
- 工作集是否大于内存;
- 是否有大扫描污染缓存;
- 是否刚重启导致冷缓存;
- SQL 是否缺索引导致随机读;
- 实例规格是否不足;
- 其他进程是否占用内存。
处理:
- 优化扫描 SQL;
- 报表切到从库或分析库;
- 扩容 Buffer Pool;
- 拆分冷热数据;
- 滚动重启后预热;
- 继续评估内存与连接模型。
问题 19:深分页怎么优化?
慢查询:
SELECT id, order_no
FROM orders
ORDER BY id
LIMIT 1000000, 20;
游标分页:
SELECT id, order_no
FROM orders
WHERE id > 1000000
ORDER BY id
LIMIT 20;
延迟关联:
SELECT o.id, o.order_no
FROM orders o
JOIN (
SELECT id
FROM orders
WHERE user_id = 10001
ORDER BY created_at DESC, id DESC
LIMIT 1000000, 20
) AS t ON t.id = o.id;
连续列表更适合游标,后台跳页可以限制范围或使用缓存。
问题 20:高并发库存如何防超卖?
先用缓存或内存做预扣限流,MySQL 保存事实库存,并用条件更新兜底:
UPDATE sku_stock
SET available = available - 1
WHERE sku_id = 1001
AND available >= 1;
应用检查影响行数:
int affected = statement.executeUpdate();
if (affected == 0) {
throw new SoldOutException();
}
不要先 SELECT 再无条件 UPDATE,并发下会覆盖彼此结果。热点商品还可以拆行、合并写或异步化,但必须保留对账补偿。
问题 21:大表加字段要注意什么?
回答层次:
- 先确认 MySQL 版本和 DDL 类型;
- 判断能否
INSTANT; INPLACE要评估磁盘、IO、耗时和锁;- 大表考虑 gh-ost 或 pt-osc;
- 执行前处理长事务,设置
lock_wait_timeout; - 监控
Threads_running、主从延迟和磁盘; - 准备停止条件和回滚方案;
- 变更后验证表结构和业务 SQL。
易错点:Online DDL 不等于无锁、无 IO、无复制延迟。
34.7 场景题
场景 1:核心接口 P99 从 50ms 变成 2s
排查:
1. 看应用错误率和调用链
2. 确认是否只有数据库依赖慢
3. 查看 Threads_running、锁等待、慢日志
4. 找到 Top SQL 和执行计划变化
5. 检查近期发布、DDL、索引删除、参数修改
6. 检查主从延迟和读从库策略
7. 检查磁盘、CPU、网络和云资源限流
止血:
- 限流;
- 终止异常 SQL;
- 回滚发布;
- 切换实例;
- 暂停批任务;
- 恢复索引。
场景 2:主从延迟 10 分钟
先定位:
SHOW REPLICA STATUS\G
SELECT * FROM performance_schema.replication_applier_status_by_worker;
判断是否为单个大事务、Worker 不均、从库 SQL 慢、IO 高、Binlog 拉取阻塞或磁盘性能下降。处理上可以停主库大批任务、拆分事务、增加并行 Worker、下线从库重查询、补索引,无法收敛时重建或切换。
场景 3:误执行 UPDATE 少了 WHERE
正确流程:
1. 停止相关写入,保留现场
2. 记录误操作时间和 SQL
3. 不在原表直接乱改
4. 恢复最近备份到临时实例
5. 重放 Binlog 到误操作前
6. 比对受影响数据
7. 生成修复 SQL 和回滚 SQL
8. 双人确认后执行
9. 验证并复盘
面试重点不是背命令,而是体现“先止血、可回滚、可验证”的意识。
34.8 高频易错点
REPEATABLE READ能避免所有幻读:不准确,要区分快照读和当前读;- InnoDB 只有行锁:不准确,还有间隙锁、意向锁、MDL 和 DDL 场景;
- 加索引一定变快:不一定,要看选择性、写放大和回表;
EXPLAIN是最终事实:它是估算,必要时用EXPLAIN ANALYZE和真实耗时验证;- 半同步绝不丢数据:要区分确认策略、超时退化和故障场景;
- 分库分表解决一切:会引入跨片查询、分布式事务和扩容复杂度;
- 连接数越大越好:会增加内存、调度和锁竞争压力;
- Binlog 只用于主从:还用于备份恢复、CDC 和审计分析;
- 读写分离没有成本:会带来复制延迟、会话一致性和事务路由问题;
- 数据库问题只在数据库:也可能是调用次数、网络、事务边界和数据模型问题。
34.9 面试准备清单
必会命令:
SHOW FULL PROCESSLIST;
SHOW ENGINE INNODB STATUS\G
SHOW REPLICA STATUS\G
SELECT * FROM sys.innodb_lock_waits;
SELECT * FROM performance_schema.data_locks;
EXPLAIN SELECT ...;
EXPLAIN ANALYZE SELECT ...;
必画图:
B+Tree 结构
MVCC 版本链与 ReadView
Redo/Binlog 两阶段提交
主从复制链路
一条 SQL 执行流程
半同步提交链路
必讲项目:
- 业务背景;
- 问题指标;
- 分析证据;
- 方案取舍;
- 实施步骤;
- 线上验证;
- 长期治理。
本章小结
MySQL 面试的核心是把概念讲准、把原理讲深、把场景讲活。索引题要落到执行计划、回表和扫描成本;事务题要区分隔离级别、快照读和当前读;日志题要能画两阶段提交;复制题要能分析延迟和切换风险;性能题不能只回答加索引,要给出排查闭环。最有说服力的不是标准答案,而是你能讲出一次真实问题的证据链和取舍。
思考题
- 为什么“MySQL 默认 RR 能避免所有幻读”这句话不准确?
- 为“订单列表接口变慢”设计一套完整排查脚本。
- 解释半同步复制在
AFTER_SYNC下的提交顺序。 - 深分页有哪些方案?各自适合什么产品形态?
- 把你最近一次 MySQL 故障整理成面试项目,包含指标、证据、方案和验证。