不会。mysql执行limit offset时并非跳过前n行,而是必须从索引起点逐行扫描、计数并丢弃前offset行,导致i/o和cpu开销与offset值线性正相关,即使有索引也无法规避该固有行为。

MySQL执行LIMIT OFFSET时真会“跳过”前N行吗?
不会。MySQL没有“跳过”能力,只有“扫描+计数+丢弃”这一种方式。比如LIMIT 100000, 20,它必须从索引起点开始,逐条读取、比较、计数,直到累计扫描满100020条,再扔掉前100000条——被丢弃的那10万条,依然要走完整IO和CPU路径。
常见错误现象:EXPLAIN显示rows值等于OFFSET + LIMIT,且Extra里出现Using index或Using filesort,说明扫描量已失控。
- 即使有主键索引,只要ORDER BY字段没覆盖WHERE条件,仍可能触发全索引扫描
- 如果排序字段无索引,
Using filesort会让深分页直接卡死,尤其在SSD写放大或内存不足时 -
SELECT *配合大OFFSET会导致大量回表,随机IO飙升,比顺序扫描更慢
为什么加了索引也救不了大OFFSET?
索引能加速定位范围,但无法绕过“扫描前N行”这个硬性流程。例如WHERE user_id = 10001 ORDER BY id DESC LIMIT 20 OFFSET 500000,哪怕建了idx_user_id_id联合索引,MySQL仍需在该user_id的所有记录中,按id倒序扫出前500020条,再丢弃前50万条。
关键点在于:索引只减少WHERE筛选后的数据集大小,不改变OFFSET本身的线性扫描成本。
- EXPLAIN中
key显示用了索引,但rows仍高达50万+,就是典型“索引有效但OFFSET无效” - 复合索引字段顺序必须匹配查询中的WHERE+ORDER BY顺序,否则
ORDER BY部分可能失效 - 如果
user_id基数极低(如全员都属同一租户),索引区分度差,实际扫描行数仍接近全表
用WHERE id > ?替代OFFSET真的可行吗?
可行,但仅限于主键或唯一递增字段+无并发写入场景。本质是把“第N页”转化为“比上一页最后一条id大的下一批”。例如上一页最后返回id = 100500,下一页就查WHERE id > 100500 ORDER BY id LIMIT 20。
这种写法让MySQL直接利用B+树索引定位起点,扫描行数恒定为20,性能不再随页码增长而衰减。
- 必须确保排序字段(如
id)全局唯一且单调,否则漏数据或重复 - 不能支持“跳转到任意页”,只能顺序翻页;用户点击第100页时,得先翻99次才能拿到书签
- 高并发写入下,新插入记录可能挤在中间,导致某页数据被跳过(如按
id DESC翻页时,新记录id更大但时间更早)
游标分页(Keyset Pagination)落地要注意什么?
游标分页不是简单把OFFSET换成WHERE id > ?,而是用排序字段组合值当“书签”。例如ORDER BY create_time DESC, id DESC,游标就得传两个值:last_create_time和last_id,查询条件写成WHERE (create_time, id) 。
这是解决非单调字段(如时间戳)并发写入错乱的核心手段,但实现稍复杂。
- WHERE条件必须严格对应ORDER BY字段顺序和方向,否则索引无法命中
- 游标值必须原样透传,不能做任何格式转换(如时间转字符串再解析),否则精度丢失
- 前端分页控件要禁用“跳页输入框”,只保留“下一页”按钮,否则游标链断裂
真正容易被忽略的是:游标值本身必须来自上一页结果集的**最后一条原始记录**,而不是SELECT出来的计算字段或别名。哪怕只是SELECT UNIX_TIMESTAMP(create_time),也会导致游标失效。











