1. MySQL分页查询的本质问题
在日常开发中,我们经常需要处理数据分页的场景。表面上看,使用LIMIT offset, size语法非常简单直观,但当你真正处理大数据量时,会发现一个诡异的现象:随着页码的增加,查询速度会变得越来越慢。这不是你的错觉,而是MySQL处理分页的底层机制决定的。
让我们从一个具体案例开始。假设我们有一个用户表,包含100万条记录,执行以下查询:
sql复制SELECT * FROM users ORDER BY id LIMIT 20000, 10;
这个查询看起来只是要获取第20001到20010条记录,但MySQL的实际执行过程会让你大吃一惊。它并不是直接定位到第20000条记录然后取10条,而是需要完整扫描前20010条记录,然后丢弃前20000条,只返回最后的10条。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. B+Tree索引的局限性
2.1 索引结构解析
MySQL的InnoDB引擎使用B+Tree作为索引结构。这种数据结构的特点是:
- 所有数据都存储在叶子节点
- 叶子节点之间通过指针连接形成链表
- 非叶子节点只存储键值和子节点指针
当我们执行WHERE id = 1000这样的查询时,B+Tree可以快速定位到具体记录,时间复杂度是O(logN)。但对于LIMIT 20000,10,情况就完全不同了。
2.2 为什么不能直接跳转
B+Tree索引存储的是键值到记录的映射,而不是记录的顺序编号。也就是说:
- 它可以快速回答"id=1000的记录在哪里"
- 但无法回答"第20000条记录的id是多少"
这就是为什么MySQL必须从第一条记录开始扫描,直到累计跳过offset指定的行数。这个过程就像你在一本没有页码的书中查找内容,只能一页页翻,无法直接跳转到指定位置。
3. MySQL分页的执行过程详解
3.1 有索引时的执行流程
当使用主键或索引列排序时,MySQL的执行流程如下:
- 通过索引找到第一条符合条件的记录
- 顺序扫描索引,对每条记录进行计数
- 跳过前offset条记录
- 返回接下来的size条记录
这个过程的复杂度是O(offset + size),意味着offset越大,需要扫描的记录就越多。
3.2 无索引时的灾难性表现
如
