Appearance
LIMIT 100000,20
这篇写给踩过坑的人:页面翻到第 5000 页突然卡死,数据库 CPU 还飙上去了。
LIMIT 100000, 20 慢,不是因为 MySQL “不会分页”,而是因为它要先跳过前 100000 行,再拿后 20 行。 偏移量越大,浪费越多。
LIMIT 100000, 20
如果是线上列表页,优先改成游标分页(也叫 Keyset Pagination)。
你写了这条 SQL:
SELECT id, user_id, amount, create_time FROM orders WHERE status = 1 ORDER BY create_time DESC LIMIT 100000, 20;
看起来只拿 20 条,但数据库实际工作量是:
如果还伴随回表、filesort,成本会更高。
核心思路:不用 offset,改用“上次最后一条记录”作为下一页起点。
SELECT id, user_id, amount, create_time FROM orders WHERE status = 1 ORDER BY create_time DESC, id DESC LIMIT 20;
记住本页最后一条的 (create_time, id),比如:
(create_time, id)
last_create_time = '2026-03-30 10:15:30'
last_id = 9527
SELECT id, user_id, amount, create_time FROM orders WHERE status = 1 AND ( create_time < '2026-03-30 10:15:30' OR (create_time = '2026-03-30 10:15:30' AND id < 9527) ) ORDER BY create_time DESC, id DESC LIMIT 20;
这样每一页都从“游标位置”继续扫,不需要跳过几十万行。
这个场景建议索引:
CREATE INDEX idx_orders_status_ctime_id ON orders(status, create_time DESC, id DESC);
为什么是这三个列:
status
create_time, id
也不是说 offset 全不能用,下面两类可以接受:
但如果是 feed 流、订单流水、日志列表,建议尽早上游标分页。
create_time + id
cursor
hasMore
create_time
如果多条记录时间一样,翻页可能重复或漏数据。 解决:加第二排序键 id。
id
比如用 update_time 做游标,数据一更新就会“漂移”。 解决:优先用不变且单调的键(如创建时间 + 主键)。
update_time
游标分页擅长“下一页”,不擅长随机跳页。 如果业务必须跳页,可以保留一个受限的 offset 查询作为兜底。
深分页慢的本质是“跳过成本太高”。 把 LIMIT offset 换成游标分页,通常能稳定把大偏移分页从“高风险慢查询”变成“可控常规查询”。
LIMIT offset
一句话:列表越大,越要尽早改游标分页。