LIMIT OFFSET在千万级数据下失效,是因为MySQL需扫描并丢弃前offset行,导致I/O和CPU开销线性增长;应改用主键游标分页(如WHERE id > last_id),配合索引实现高效范围查询。

不能用 LIMIT + OFFSET 做深分页,尤其当偏移量超过 10 万行时,查询会越来越慢,甚至拖垮数据库。
为什么 LIMIT OFFSET 在千万级数据下会失效
MySQL 执行 LIMIT 1000000, 1000 时,并不是跳过前 100 万行再取 1000 行,而是:先扫描并排序全部匹配行(可能上千万),再丢弃前 100 万,只返回后 1000 行。这个过程会触发 Using filesort 和大量回表,I/O 和 CPU 开销爆炸式增长。
- 执行计划里出现
Using filesort或Using temporary就是危险信号 - 即使有索引,
OFFSET越大,MySQL 越倾向放弃索引走全表扫描 - 并发执行多个深分页查询,极易耗尽连接数和缓冲池
用主键游标替代 OFFSET:稳定且可预测
核心思路是“记住上一批最后一条的 id,下一批查 id > last_id”,避免跳过行为。
- 必须确保
ORDER BY字段是主键或带索引的单调字段(如自增id、created_time) - SQL 示例:
SELECT * FROM orders WHERE id > 1234567 ORDER BY id LIMIT 10000 - 首次查询需先获取最小
id:SELECT MIN(id) FROM orders - 每批结果必须按顺序取最大
id作为下一批起点,不能用MAX(id)全局值——那会漏数据
时间范围分片:适合日志、订单类业务表
当表有高频更新且主键不连续(如 UUID、雪花 ID),或业务天然按时间切分时,优先用时间字段分段。
- 例如按天切:
SELECT * FROM events WHERE create_time BETWEEN '2026-06-01' AND '2026-06-01 23:59:59' LIMIT 50000 - 必须在
create_time上建索引,否则分片失去意义 - 注意时区一致性,避免因
NOW()或应用层时区导致边界错位 - 若某天数据暴增(如促销日),需动态降级为该日内再按主键游标细分
批量读取时容易忽略的三个硬约束
分批本身不能解决所有问题,应用层必须配合控制节奏。
-
fetch_size设置无效:JDBC 的setFetchSize()对 MySQL 驱动多数版本只是提示,实际仍可能一次性拉完;真正起作用的是服务端游标(需useCursorFetch=true+statement.setFetchSize()) - 事务隔离级别影响一致性:长事务中分批读可能看到中间态数据;建议用
READ COMMITTED,避免REPEATABLE READ下 MVCC 版本堆积 - 网络与内存双瓶颈:单批 10 万行 × 平均 2KB = 200MB 网络传输量,Java 应用需调大
-Xmx并启用堆外缓冲(如 Netty),Python 则推荐用stream=True+ 迭代器逐行解析
游标分页不是银弹——它要求主键单调、无删除、无频繁重写;时间分片依赖业务语义;而任何分批方案都绕不开应用层对连接、事务、内存的精细管控。最常被跳过的其实是“每批处理完立刻提交事务并释放连接”,而不是等全部跑完再 commit。


















