LIMIT 是什么
LIMIT 限定返回的行数,OFFSET 指定跳过的行数,两者共同实现分页与 Top N。MySQL 支持 LIMIT n、LIMIT offset, n 与 LIMIT n OFFSET offset;PostgreSQL 用 LIMIT n OFFSET m,也支持标准的 FETCH FIRST n ROWS ONLY。LIMIT 位于执行计划的最后一步,作用在排序之后,因此它不是过滤条件:带 LIMIT 的聚合仍然按全量数据计算。深度翻页时 OFFSET 需要扫描并丢弃前面所有行,代价随页码线性增长,生产上改用键集分页。
基本信息
| 作用 | 截断返回行数与跳过偏移,在排序之后执行 |
|---|---|
| MySQL 语法 | LIMIT n / LIMIT offset, n / LIMIT n OFFSET offset |
| PostgreSQL 语法 | LIMIT n OFFSET m,标准写法 FETCH FIRST n ROWS ONLY |
| 执行位置 | 输出截断阶段,不是过滤条件 |
| 顺序依赖 | 没有 ORDER BY 时取哪几行不确定 |
| 深分页代价 | OFFSET 越大越慢,需要扫描并丢弃前面的行 |
典型使用场景
- 列表分页:每页 20 条,翻页时传偏移量
- Top N 排行:按销售额倒序取前 10
- 大表探查:确认表里有没有符合条件的数据,加 LIMIT 1 即可
- 批量处理分片:按主键区间加 LIMIT 逐批拉取,避免一次拉全表
- 导出限量:给后台导出加最大行数上限,防止拖垮实例
- 存在性判断:LIMIT 1 比 COUNT(*) 更省资源
常用写法
常用写法
-- 分页:第 3 页每页 20 条,offset = (页码 - 1) * 每页条数
SELECT id, name FROM users ORDER BY id LIMIT 20 OFFSET 40;
-- MySQL 的另一种写法:偏移量在前,条数在后
SELECT id, name FROM users ORDER BY id LIMIT 40, 20;
-- Top N
SELECT id, total FROM orders ORDER BY total DESC LIMIT 10;
-- 存在性判断,找到一条就够
SELECT 1 FROM orders WHERE user_id = 100 LIMIT 1;键集分页
键集分页
-- 用上一页最后一条的排序键作为下一页起点
SELECT id, created_at FROM orders
WHERE (created_at, id) < ('2024-05-01 10:00:00', 8801)
ORDER BY created_at DESC, id DESC
LIMIT 20;
# 深度翻页代价恒定,不受页码影响
# 排序键要唯一,或补主键做次级键,否则可能漏行或重复分页方式对比
| 方式 | 写法 | 特点 |
|---|---|---|
| LIMIT / OFFSET | LIMIT 20 OFFSET 40 | 简单直观,深翻页线性变慢 |
| 键集分页 | WHERE id > 上一页最大 id | 代价稳定,但不能跳页 |
| 游标分页 | 服务端保存游标 | 前台体验好,实现复杂度高 |
| LIMIT n, m | MySQL 专有,偏移量在前 | 与标准写法顺序相反,容易写反 |
| 先查主键再回表 | 子查询取 id 后 JOIN 原表 | 减少回表数据量,深分页常用 |
LIMIT 不改变聚合结果:SELECT COUNT(*) FROM t LIMIT 10 仍返回一行总数,想限制统计范围要把条件写进 WHERE。
不加 ORDER BY 直接用 LIMIT 分页,两次请求可能返回重复或缺失的行,因为行顺序不确定。
深分页的另一种优化:先用覆盖索引取出主键,再回表取完整行,减少大量无效回表,写法是 JOIN 一个只含 id 的子查询。
PostgreSQL 还可以用 FETCH FIRST n ROWS ONLY / OFFSET m ROWS,这是 SQL 标准写法,MySQL 不支持。
分页实现要点
- 偏移量由业务算:偏移 = (页码 - 1) × 每页条数,页码从 1 还是 0 开始要和前端约定清楚
- 每页条数必须设上限:不限制时客户端传一个很大的 size 会一次性拉走全表
- 总数与列表分两次查:总数走覆盖索引或缓存,列表查只取当页数据
- 翻页必须带稳定排序键:只按非唯一列排序时补主键做次级键,否则顺序不确定
- 键集分页不能跳页:适合无限滚动与导出分批,不适合带页码跳转的后台列表
- 导出场景用主键区间切片:按 id 区间分批拉取,中断后可以从上次位置续传
LIMIT 与 OFFSET 的代价不对称:LIMIT 改小几乎不影响速度,OFFSET 变大才是瓶颈,因为前面的行即使不返回也要被读取并丢弃。
跨库分页优先写 LIMIT n OFFSET m,MySQL 与 PostgreSQL 都认;MySQL 专有的 LIMIT offset, n 虽然更短,但迁移时容易与标准写法混淆。
深分页的另一种常见优化是缓存总数:后台列表的总条数变化不频繁时,按条件缓存几分钟即可,不必每次请求都做一次精确统计。