limit n 是「最多取 n 行」,limit offset, n 是「跳过 offset 行再取 n 行」。
分页公式只有一条:offset = (page - 1) * size。
Demo 里每页 5 条翻了三页:第 1 页首行张伟、第 2 页首行杨帆、第 3 页只剩 2 行;
不加 order by 的 limit 这次碰巧一样,但那只是运气——标准里没有「默认顺序」这回事。
上一篇定顺序,这一篇定取几行。 两个一起用,才有「分页」这件事。
它是接口设计的底座:所有列表接口的参数都是 page 和 size,
背后就是这一句 limit offset, size。
一、limit n:只要前 n 行
LIMIT 3 就是「最多 3 行」。 是「最多」不是「正好」——表里只剩 2 行时它给 2 行,不报错。
分页最后一页常常不满,就靠这个行为。
limit 0 会返回空结果集,
但列名还在——有时候用它「只要结构不要数据」,比 where 1=0 干净。
二、limit offset, n:跳过再取
LIMIT 2, 3 的意思是「跳过前 2 行,再取 3 行」,等价于 LIMIT 3 OFFSET 2。
两个数谁在前?偏移在前、条数在后。 写反是最常见的错:LIMIT 3, 2 是「跳 3 取 2」,
结果完全不同。
推荐写 LIMIT n OFFSET m:顺序和语义一致,不用记谁在前,
MySQL 和 SQLite 都支持这种写法。
三、分页公式与那一页两行
公式:offset = (page - 1) * size。 页码从 1 开始、偏移从 0 开始,所以必须先减 1。
Demo 的账:每页 5 条,第 1 页 offset 0 取 5 行、第 2 页 offset 5 取 5 行、
第 3 页 offset 10 只剩 2 行——12 个学生正好翻三页。
总量也要一起查:SELECT COUNT(*) 拿总行数,前端才能算出总页数(ceil(total / size))。
两个查询要放在同一个事务或同一时刻,否则数字对不上。
四、limit 必须配 order by
不加 order by 的 limit,取到哪几行是不确定的。 标准里没有「默认顺序」这回事——
引擎想怎么给就怎么给。
Demo 里这次两边一样(都是 1 到 5),但那是运气:
数据量小、又刚好按主键顺序读。 换个数据量、加个索引,顺序就变了。
后果很具体:
翻页时同一行出现两次、另一行一直看不到。 这类 bug 在测试环境很难复现,上线才爆。
五、深翻页为什么慢,怎么救
LIMIT 100000, 20 这种大偏移:引擎要把前 10 万行都找出来再丢掉,越往后翻越慢,
第 5000 页可能比第 1 页慢几百倍。
救法一(最常用):记住上一页最后一行的主键,
下一页用 WHERE id > 上一页最后的值 ORDER BY id LIMIT 20。
这样每一页都一样快,代价是不能跳页。
救法二:先只查主键(走覆盖索引),再按主键回表取数据。 适合「必须支持跳页」的场景。
救法三:限制最大页数(比如只允许看前 100 页),产品上很常见,也最省事。
六、小结
教材小结:limit 限制返回行数,limit offset, count 可以跳行取,常用于分页。
本课补三条:一是分页必须配 order by;二是 LIMIT n OFFSET m 比 LIMIT m, n 好记;
三是深翻页要换成「按上一页最后的主键往下取」。
七、动手 Demo:每页五条,翻三页
Demo 五段:先 limit 3,再 limit 2, 3,然后按公式翻三页,最后对比加不加 order by。
第三段那三行是分页的全部账:offset、取到几行、首行是谁。
import _q46 as Q
c = Q.con()
c.execute("SELECT id, name FROM students ORDER BY id LIMIT 3")
c.execute("SELECT id, name FROM students ORDER BY id LIMIT 2, 3")
# 分页公式:offset = (page - 1) * size
for page in (1, 2, 3):
off = (page - 1) * 5
c.execute("SELECT id, name FROM students ORDER BY id "
"LIMIT 5 OFFSET %d" % off)
# 不加 order by 的 limit 没保证
c.execute("SELECT id FROM students LIMIT 5")
c.execute("SELECT id FROM students ORDER BY id LIMIT 5")
实跑输出:
--------------------------------------------------------------------
第十三课:limit,以及分页那条公式
--------------------------------------------------------------------
取前几行:limit n
SQL> SELECT id, name FROM students ORDER BY id LIMIT 3
id name
------------
1 张伟
2 王芳
3 李娜
(3 行)
**`limit 3` 就是「最多 3 行」。** **注意是「最多」——表里只有 2 行时,它给 2 行,不报错。**
跳过几行再取:limit offset, n
SQL> SELECT id, name FROM students ORDER BY id LIMIT 2, 3
id name
------------
3 李娜
4 刘洋
5 陈静
(3 行)
**`limit 2, 3` 的意思是「跳过前 2 行,再取 3 行」,等价于 `LIMIT 3 OFFSET 2`。**
**第一个数是偏移、第二个数是条数——顺序写反是常见错。**
分页公式:第 page 页,每页 size 条
每页 5 条,取第 1、2、3 页:
第 1 页:offset = (1-1)*5 = 0 ,取到 5 行,首行是 张伟
第 2 页:offset = (2-1)*5 = 5 ,取到 5 行,首行是 杨帆
第 3 页:offset = (3-1)*5 = 10,取到 2 行,首行是 徐明
**公式就是 `offset = (page - 1) * size`。** **页码从 1 开始,偏移从 0 开始,所以要先减 1。**
不加 order by 的 limit,结果不保证
不加 order by:[1, 2, 3, 4, 5]
加 order by: [1, 2, 3, 4, 5]
**这次两边一样,但那是运气。** **SQL 标准里没有「默认顺序」这回事,引擎想怎么给就怎么给。**
**分页必须配合 order by,否则翻页时会出现「同一行出现两次、另一行一直不出现」。**
limit 的一个硬限制
**`limit` 不能跟子查询里那种「先算再取」混着用**:
`limit` 只能出现在整条语句的最外层(或子查询自己的最外层)。
**还有一个性能常识**:`limit 100000, 20` 这种大偏移,
**引擎要先把前 10 万行找出来再丢掉,越翻越慢。**
**优化方向是「记住上一页最后一行的 id,用 where id > ? 往下取」。**
第一段 limit 3。 取前三行,1、2、3。
第二段 limit 2, 3。 跳过前两行,取到 3、4、5——和第一段的结果不重叠,这就是偏移的意义。
第三段翻页。 第 1 页 offset 0 首行张伟;第 2 页 offset 5 首行杨帆;
第 3 页 offset 10 只取到 2 行(12 个学生到底了)。
第四段不加 order by。 这次给 1 到 5,和加了 order by 一样,
但正文里说清了:这是运气,不是保证。
第五段深翻页与三个救法。
30 秒实验
LIMIT 3, 2取到的是哪几行?和LIMIT 2, 3差在哪?- (先想再跑)每页 5 条、共 12 行,一共几页?第 3 页取到几行?
LIMIT 100000, 20慢在哪一步?
常见误区
- 把
LIMIT m, n的两个数写反。 一个是偏移一个是条数,写反结果完全不同。 - 不加 order by 就分页。 顺序不保证,翻页时会出现重复行和漏行。
- 用大偏移做深翻页。 引擎要先把前面十几万行读出来再丢掉,越翻越慢。
自测三问(点开对答案)
1. `LIMIT 2, 3` 是什么意思? —— 跳过前 2 行,再取 3 行;等价于 `LIMIT 3 OFFSET 2`。 2. 分页公式是什么?为什么先减 1? —— offset = (page - 1) * size;页码从 1 开始、偏移从 0 开始,所以第一页偏移是 0。 3. 为什么 limit 一定要配 order by? —— SQL 没有默认顺序,不加排序时取到哪几行不确定,翻页会出现重复与漏行。查询的四个子句(select/where/order by/limit)都会用了,
下一篇补最后一个:去重 distinct——以及它常常暴露出来的问题。
- ☐ (本篇完,下一篇 3.6 去重处理:distinct)