LIMIT 是什么
LIMIT 用来限制返回行数并指定偏移,有两种写法:LIMIT count 和 LIMIT offset, count(也可写成 LIMIT count OFFSET offset)。它作用在排序之后,是分页查询的最后一步。真正的坑在深翻页:LIMIT 1000000, 20 并不是只读二十行,MySQL 必须先扫描并丢弃前一百万行,代价随偏移线性上升。键集分页(记住上一页最后一条的游标值,用 WHERE id > ? 继续取)和延迟关联是两种常用替代方案。
基本信息
| 用途 | 限制返回行数与分页 |
|---|---|
| 语法 | LIMIT count | LIMIT offset, count | LIMIT count OFFSET offset |
| 执行位置 | 排序之后、返回之前 |
| 深翻页问题 | 偏移越大越慢,扫过的行数与偏移成正比 |
| 替代方案 | 键集分页(游标)、延迟关联(先取主键再回表) |
| 配套要求 | 分页必须搭配确定性的 ORDER BY |
典型使用场景
- 列表接口分页,前端传入页码与每页条数
- 运维排查时只看前若干行,避免把大结果集拉回本地
- TopN 统计(最大金额、最慢语句),排序后取前 N 条
- 批量任务分段处理:每次取一批主键,处理完再取下一批
- 深翻页改造现场:把偏移分页换成游标式分页
- 大表导出时按主键范围分批拉取,避免一次性加载
基础分页
基础分页
SELECT id, sku FROM orders ORDER BY id LIMIT 20; # 第一页
SELECT id, sku FROM orders ORDER BY id LIMIT 20 OFFSET 20; # 第二页,等价 LIMIT 20, 20
SELECT id, sku FROM orders ORDER BY id DESC LIMIT 10; # 最新十条,反向扫描主键
SELECT COUNT(*) FROM orders WHERE status='paid'; # 列表页常要的配套总数,大表宜用估算深翻页优化
深翻页优化
# 键集分页:记住上一页最后一条的 id,从它之后继续取
SELECT id, sku FROM orders WHERE id > 100020 ORDER BY id LIMIT 20;
# 延迟关联:先在索引上取出主键,再回表拿需要的列
SELECT o.id, o.sku FROM orders o
JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 20) t ON t.id = o.id; # 必须保留页码时用这种折中写法分页写法对照
| 写法 | 代价 | 适用场景 |
|---|---|---|
| LIMIT 20 | 扫到满足二十行即停 | 首页与结果集小的查询,成本最低 |
| LIMIT 20 OFFSET 20 | 读取并丢弃前二十行 | 前几页,业务上通常够用 |
| LIMIT 1000000, 20 | 读取并丢弃一百万行 | 深翻页,迟早要改造 |
| WHERE id > 游标 | 直接从索引位置起读 | 深翻页首选,要求排序键唯一且单调 |
| 延迟关联 | 先取主键再回表 | 必须保留跳页能力时的折中方案 |
| 无 ORDER BY 的 LIMIT | 顺序不保证 | 只适合抽样查看,不做业务用途 |
没有 ORDER BY 的 LIMIT 返回顺序不保证稳定,重复执行可能得到不同结果;分页、导出、对账这类场景必须显式排序。
OFFSET 的成本在于读了再丢,与返回行数无关。若排序键唯一且单调,键集分页可以直接从索引定位起读位置,耗时基本恒定,这是深翻页改造的首选思路。
带 LIMIT 的 UPDATE 或 DELETE 语义容易被误解:命中的是排序后的前若干行,排序不确定时命中的行也会变,写操作前先用相同条件 SELECT 计数核对。
分页可用性检查
- 排序键是否唯一且单调:不满足就先补唯一列,再考虑键集分页
- 首屏是否真的需要总数:多数列表页可用估算或滚动加载省掉计数
- 是否有并发写入:有插入时偏移分页会出现条目漂移,键集分页更稳定
- 每页条数是否过小:几十条一页会让请求次数成倍上升
- 是否必须支持随机跳页:强跳页场景保留偏移分页并限制最大页码,其余改游标
- 批量任务每批取完后是否记录游标:不记录就无法在中断后续跑
偏移分页在数据持续插入时会出现条目漂移:翻页过程中新记录插到了前面,第二页可能重复展示第一页见过的内容,业务上要么用游标分页,要么按固定时间快照查询。