MySQL高频面试题-02
这一篇的主题:日志双写机制、深分页瓶颈,以及死锁怎么查。
上次和大家聊了 B+ 树和 MVCC,今天这篇我们直接上硬菜。
在社招或者大厂面试中,面试官往往不满足于只问你“什么是索引”,他们更喜欢切入高并发、大数量、分布式的真实场景。今天复盘的 3 个高频硬核面试题,不讲虚的,全是底层逻辑和线上实战。
一、 日志双写:Redo Log 和 Binlog 的“两阶段提交”到底在干嘛?
面试官最喜欢抛出这个连环炮:“既然有了 Binlog,为什么 InnoDB 还要搞个 Redo Log?”“执行一条 UPDATE 语句,底层的日志是怎么写入的?”
面试官的潜台词:我想看看你对高可用、崩溃恢复(Crash-Safe)和分布式一致性的理解到底在哪一层。
1. 为什么必须用两个日志?
别死记硬背,看下分工:
Binlog(归档日志):属于MySQL Server 层。不管你用什么存储引擎(InnoDB、MyISAM),只要发生数据变更,它就会记录。它是追加写的,用来做主从复制和数据恢复。
Redo Log(重做日志):属于InnoDB 引擎层。它是循环写的(空间固定,会覆盖),专门用来实现Crash-Safe。哪怕数据库忽然断电,重启后也能靠它把没刷到磁盘的数据“救回来”。
2. 什么是两阶段提交(2PC)?
如果执行一句话:UPDATE users SET age = 20 WHERE id = 1;,MySQL 是这样分两步写日志的:
[准备阶段] InnoDB 修改内存数据 -> 记录 Redo Log (状态设为 prepare) ↓ [提交阶段] Server 层生成 Binlog 写入磁盘 -> InnoDB 修改 Redo Log 状态为 commit为什么这么费劲?如果不搞两阶段,直接先写 A 再写 B 会怎么样?
情况一(先写 Redo,再写 Binlog):刚写完 Redo Log,系统断电了。重启后,InnoDB 靠 Redo Log 把数据恢复成了 age = 20。但 Binlog 没来得及写。后面主从同步或者用 Binlog 恢复备库时,备库里还是老数据。主备不一致!
情况二(先写 Binlog,再写 Redo):刚写完 Binlog 机器挂了。重启后,InnoDB 发现没有 Redo Log,觉得这次事务失败了,数据还是原样。但 Binlog 已经发出去了,从库执行了这条 Binlog 变成了新数据。又是主备不一致!
老鸟总结:两阶段提交,说白了就是分布式事务的简化版。通过把 Redo Log 拆成 prepare 和 commit 两个状态,卡住中间的 Binlog,确保两边要么一起成功,要么一起失败。
二、 痛入骨髓的 LIMIT 1000000, 10:深分页怎么优化?
面试官:“你们系统数据量大了之后,做分页查询越来越慢,你怎么解决?”
避坑:别一上来就说分库分表。面试官想听的是你在单表上千万时,怎么通过 SQL 优化把性能拉回来。
1. 为什么深分页(比如 LIMIT 1000000, 10)慢到爆炸?
很多人以为 MySQL 遇到了 LIMIT 1000000, 10,是直接跳过前 100 万条,只查最后的 10 条。完全错了!
MySQL 的真实做法是:老老实实扫描前1000010条数据,如果是 SELECT *,它还要吐血地做1000010 次回表,最后把前 100 万条丢掉,只给你留最后 10 条。CPU 和磁盘 I/O 早就被回表给冲垮了。
2. 老鸟拿捏深分页的两种标准姿势
方案 A:延迟关联(覆盖索引法)
既然回表成本高,那我就先不回表。利用覆盖索引,先在二级索引树上把这 10 个主键 id 找出来,再去回表拿完整数据。
-- 优化前 SELECT * FROM orders ORDER BY create_time LIMIT 1000000, 10; -- 优化后(延迟关联) SELECT * FROM orders o JOIN ( SELECT id FROM orders ORDER BY create_time LIMIT 1000000, 10 ) t ON o.id = t.id; 原理:内层的子查询只拿 id,刚好触发了覆盖索引,不需要回表,速度极快。拿到 10 个 id 后,外层只需要做 10 次精确的回表,性能直接提升几个数量级。
方案 B:标签记录法(游标分页)
如果你的业务允许(比如手机 App 往下滑动刷新,不需要直接跳到第 100 页),可以记录上一次查询的最后一条数据的 ID。
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10; 原理:直接走主键索引定位到具体位置,一秒都不耽误。
三、 线上死锁:发生 Deadlock 了,你作为主导怎么排查?
面试官:“线上突然报警提示 Deadlock found when trying to get lock...,你怎么排查是哪两段代码冲突了?”
面试官的潜台词:我要听的不是死锁的定义(互斥、请求保持啥的),我要看你的线上应急能力和日志分析能力。
老鸟的标准排查 SOP
1. 第一步:抓现场
立刻登录生产数据库(或者看监控日志),执行核心命令:
SHOW ENGINE INNODB STATUS; 在这个命令输出的长篇大论里,找到LATEST DETECTED DEADLOCK这一栏。这里详细记录了最近一次发生死锁的全部罪证。
2. 第二步:看懂死锁日志(拆解罪证)
日志里通常包含两段核心信息:
WEITING FOR THIS LOCK TO BE GRANTED:事务 A 持有了锁 X,正在等待锁 Y。
HOLDS THE LOCK(S) 加上 WAITING FOR THIS LOCK TO BE GRANTED:事务 B 持有了锁 Y,正在等待锁 X。
你会清晰地看到两个事务分别执行的是哪句 SQL,以及它们分别在抢哪张表的哪个索引(是主键索引还是二级索引,是记录锁还是间隙锁)。
3. 第三步:对齐业务代码并复盘
拿到这两句 SQL 后,去代码里搜,通常是因为两个并发业务更新物资或者数据的顺序不一致导致的。
业务 A 的代码逻辑是:先更新商品 1,再更新商品 2。
业务 B 的代码逻辑是:先更新商品 2,再更新商品 1。 当两个业务同时跑,各执行完第一步时,死锁就成了必然。
怎么防范?
让团队开发规范死死卡住:所有并发业务,更新多表/多行数据的顺序必须保持绝对一致。
尽量缩短事务的长度,把不需要事务的逻辑(比如调外部接口)移出事务块。
总结
面试时聊到这些深水区问题,多用“线上排查”和“主备一致”的视角去切入,别把自己局限在写代码的工位上,要把自己放在架构复盘的角度。面试官听了绝对眼前一亮。
觉得有用的话,别忘了点赞、收藏,下期想看什么技术内幕,在评论区留言!
