[面试题] MySQL 中深度分页(如 LIMIT 1000000, 100)为什么会特别慢?底层原因是什么?
📖 版块 A:知识讲解版 (温故知新)
深度分页执行过程解析(以 LIMIT 1000000, 100 为例):
- 搜寻 ID:存储引擎通过非聚簇索引树(这里我们说的是极端情况,有时候也可能对聚簇索引进行分页),按顺序向后扫描,找出前 1,000,100 条记录的主键 ID。
- 疯狂回表:因为是 SELECT *,存储引擎必须拿着这 1000100 个主键 ID,去聚簇索引中逐一进行回表查询,把完整的行数据提取出来交还给 Server 层。
- 无效 I/O:拿到全部数据后,MySQL Server 层会将前 1,000,000 条数据直接丢弃,只把最后 100 条数据返回给客户端。
核心慢原因总结:MySQL 为了这 100 条有用的数据,做了一百万次无用的回表查询。磁盘随机 I/O 被严重透支,导致查询极慢。
🎤 版块 B:面试实战版 (高手逐字稿)
Q:MySQL 中深度分页(如 LIMIT 1000000, 100)为什么会特别慢?底层原因是什么?
🧠 破局思路:这道题单纯回答“因为扫描的数据量大”只能拿到基本分。面试官真正想听的是你对“MySQL 存储引擎层与 Server层的交互机制”以及“B+树回表代价”的深刻理解。高手应该精准点出“大量的无效回表”这一核心痛点,并在结尾主动抛出优化思路,展现架构思维。
💬 高手逐字稿:
深度分页之所以特别慢,核心根本原因可以用一句话概括:大量无效的回表操作,造成了极其严重的磁盘二次 I/O 浪费。
我们可以推演一下它的底层执行过程。假设我们执行 SELECT * FROM t_order ORDER BY create_time DESC LIMIT 1000000, 100。
首先,MySQL 的存储引擎会走 create_time 这个非聚簇索引树,按顺序扫描出 1,000,100 条记录的主键 ID。由于我们写的是 SELECT *,而非聚簇索引的叶子节点只存了索引字段和主键 ID,缺乏其他的列数据。所以,存储引擎必须拿着这 1,000,100 个主键ID,去主键树(也就是聚簇索引)里面逐一进行“回表”查询,把一百多万条完整的行记录提取出来,然后一股脑地交给 MySQL 的 Server 层。Server 层拿到这 1,000,100 条完整数据后,会把前 1,000,000 条数据无情地直接丢弃掉,最终只保留最后 100 条数据返回给客户端。
所以您看,在这个过程中,MySQL 为了仅仅 100 条有用数据,在底层做了整整一百万次无用的回表查询,因为回表往往是随机I/O,这种巨大的开销直接拖垮了查询性能。
了解了这个底层痛点,我们在实际业务中做深度分页优化时,核心思路就是**“减少甚至消除无效回表”**。比如通过“延迟关联(INNER JOIN子查询)”,在子查询里利用覆盖索引只查出主键ID;或者使用“标签记录法(游标法)”,记录上一页最后一条的标识,直接带入 WHERE 条件向后查。
核心目的都是从物理层面砍掉那一百万次的无效回表动作。