做后台开发或者写接口的时候,用 LIMIT 做分页再常见不过了。比如 MySQL 里写 LIMIT 10, 20 拿第 2 页的数据。可一旦表里数据上百万,翻到后面几页,比如第 50000 页,页面卡得像老电脑开 PS,等好几秒才出结果。
为什么大数据量下 LIMIT 越往后越慢?
问题不在 LIMIT 本身,而在它的执行方式。MySQL 查 LIMIT 100000, 10 的时候,得先从头扫 100010 条记录,再扔掉前 100000 条,只留最后 10 条。这就像你让快递员从仓库第一箱开始数,数到第十万零一箱才给你拿货,前面十万箱白搬。
传统分页写法的问题
常见的写法:
SELECT * FROM orders ORDER BY id DESC LIMIT 100000, 10;
即使有索引,OFFSET 越大,扫描的行数越多,IO 和 CPU 消耗直线上升。用户多翻几页,数据库直接拖垮。
改用“游标分页”避开深翻
别再用 OFFSET,换成基于上一页最后一条数据的位置继续查。比如按 ID 递增排序,上一页最后一条的 ID 是 99990,那下一页就从大于这个值的地方开始拿:
SELECT * FROM orders WHERE id > 99990 ORDER BY id ASC LIMIT 10;
这样不用跳过任何数据,直接走索引查找,速度飞起。就像你知道快递在哪个货架,直接过去拿,不用从门口一路数过来。
适用于时间线类场景
如果你做的是订单列表、日志查询、朋友圈动态这类按时间展示的内容,完全可以拿时间戳当游标。比如上一页最后一条是 “2024-06-10 14:30:25”,下一页查:
SELECT * FROM logs WHERE create_time > '2024-06-10 14:30:25' ORDER BY create_time ASC LIMIT 10;
前提是 create_time 上有索引,并且能保证唯一性(或配合主键去重)。
前端怎么传?简单改造就行
原来前端传 page=50001,现在改成传 last_id=99990。后端判断有没有 last_id,没有就查第一页,有就按游标方式查。接口返回数据的同时,带上当前页最后一条的 ID,前端存着,点“下一页”时带上。
不能用游标?试试延迟关联
如果非得保留页码,又不想卡,可以用“延迟关联”优化。先只查主键,再关联原表拿数据:
SELECT o.* FROM orders o INNER JOIN (
SELECT id FROM orders ORDER BY id DESC LIMIT 100000, 10
) AS tmp ON o.id = tmp.id;
子查询只走索引查 ID,速度快;外层再用这些 ID 回表取完整数据。虽然不如游标快,但比直接 LIMIT 100000,10 强太多。
小结:别让 LIMIT 成性能黑洞
数据量小的时候怎么写都行,量一大,LIMIT 后面跟个大数字就是隐患。能换游标就换,不能换就用延迟关联顶一阵。别等到用户投诉“翻个页要等十秒”,才想起来查数据库。