Menu

SQLite LIMIT 与 OFFSET:分页与截取结果集

SQLite 中 LIMIT 和 OFFSET 的用法:限制行数、跳过行、安全地分页,以及在大表上要避开的性能陷阱。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

LIMIT 限制返回的行数

LIMIT 是 SQL 里最简单的一个开关:它告诉 SQLite“最多给我这么多行”。把它放在 SELECT 的末尾,你最多会拿回这么多条结果,不会更多;如果表里的行不够,返回的就会更少。

你拿到了前三行。可到底是哪三行?问题就在这里:没有 ORDER BY 时,SQLite 会按它觉得方便的顺序返回。今天可能是插入顺序;明天经过一次更新或索引变动后,就未必如此了。如果只是“给我看几条样例”,单独用 LIMIT 没问题,但只要顺序重要,你就得把顺序明确写出来。

OFFSET 跳过前面的行

把 LIMIT 和 OFFSET 搭配起来,就能取出结果集中间的一段。OFFSET k 丢掉前 k 行,然后 LIMIT n 从剩下的行里最多返回 n 行。

意思就是“跳过两行,返回接下来的两行”,也就是排序后结果的第 3 行和第 4 行。可以这样理解:WHERE 负责过滤,ORDER BY 负责排序,OFFSET 负责跳过,LIMIT 负责截断。它们按这个顺序执行,每一步都很重要。

分页一定要有 ORDER BY

LIMIT 和 OFFSET 最常见的用途是分页:把一个长列表拆成若干页,比如每页 20 行。第 1 页是 LIMIT 20 OFFSET 0,第 2 页是 LIMIT 20 OFFSET 20,以此类推。

有两点要注意。第一,ORDER BY 必不可少:没有它,“第 2 页”就没有确定的含义,每次加载页面时行的顺序都可能被打乱。第二,排序键里加入了 id 作为平局时的决胜列。如果两篇文章的 created_at 相同,你需要一个唯一列来给它们确定的顺序,否则它们的位置可能互换,某一行就可能串到别的页上。

经验法则:按唯一的列排序,或者按你的排序列再加上一个唯一的决胜列排序。

简写形式:LIMIT n, m

为了兼容 MySQL,SQLite 支持一种较老的逗号写法:LIMIT offset, count。它和 LIMIT count OFFSET offset 的含义相同,只是顺序反过来,很容易看错。

-- These two are equivalent:
SELECT * FROM books LIMIT 10 OFFSET 20;
SELECT * FROM books LIMIT 20, 10;     -- offset first, then count

第二种写法更简洁,但常常坑到那些以为第一个数字是行数的人。坚持使用 LIMIT n OFFSET k:意思明确,从左往右读也顺。

不带 LIMIT 的 OFFSET:LIMIT -1 技巧

OFFSET 不能单独使用,SQLite 的语法要求它必须跟在 LIMIT 后面。那要怎么表达“跳过前 10 行,把后面的全部给我”呢?惯用写法是 LIMIT -1,SQLite 会把它理解为“没有上限”。

任何负数的 LIMIT 效果都一样,但 -1 是约定俗成的写法。你最常在逐页读取结果的脚本里见到它,用来在最后一批写一个“把剩下的都给我”的查询。

OFFSET 的性能陷阱

有件事很少有人提,直到你真的踩到坑:OFFSET 并不会让 SQLite 少干活,它只是让 SQLite 少输出。为了返回第 10,001 到第 10,020 行,引擎在内部仍然要先走过前一万行,然后才开始输出结果。偏移量小的时候几乎没有代价;到了几万甚至几十万,就会明显变慢。

对于翻得很深的分页,标准解法是键集分页(keyset pagination):不再说“跳过 N 行”,而是记住上一页最后一行的排序键,然后请求“这一行之后的行”。

每一页都是一次走索引的查找,而不是扫过前面所有的行。代价是:你不能直接跳到“第 47 页”,只能沿着数据往后翻。对于无限滚动的信息流和 API 游标来说,这正是你想要的。

基于 OFFSET 的分页适合后台管理表格和较小的结果集。对于会无限增长的数据,请改用键集分页。

完整示例

把前面的内容组合起来:一个带过滤、排序和确定性决胜列的分页查询。

先筛选出办公用品,按价格升序排序、价格相同时按名称排序,再取前两条。把 OFFSET 0 改成 OFFSET 2 就是第 2 页。查询很短,但每个子句都有它的作用。

下一篇:DISTINCT

LIMIT 控制返回多少行;DISTINCT 控制是否返回重复行。它是 SELECT 工具箱里的下一个子句,看似简单,却很容易用错,这就是下一页的内容。

常见问题

SQLite 中的 LIMIT 有什么作用?

LIMIT n 把 SELECT 返回的行数限制为最多 n 行。它在 WHERE、GROUP BY 和 ORDER BY 之后执行,所以限制的是最终结果集,而不是查询扫描的行。SELECT * FROM users LIMIT 10 最多返回十行。

SQLite 中 OFFSET 如何与 LIMIT 配合使用?

OFFSET k 会在 LIMIT 开始计数之前跳过结果的前 k 行。所以 LIMIT 10 OFFSET 20 返回第 21 到第 30 行。SQLite 在内部仍然要逐行走过那些被跳过的行,这就是偏移量很大时查询会变慢的原因。

SQLite 中可以不写 LIMIT 只用 OFFSET 吗?

不能直接这样写,OFFSET 只能作为 LIMIT 子句的一部分出现。变通办法是 LIMIT -1 OFFSET k,其中 -1 表示“没有上限”,于是 SQLite 跳过 k 行并返回之后的全部行。这个小怪癖值得记住。

为什么分页查询需要 ORDER BY?

没有 ORDER BY 时,SQLite 可以按任意顺序返回行,而且每次查询的顺序都可能不同。分页就会出问题:同一行可能同时出现在第 1 页和第 3 页,也可能彻底消失。LIMIT/OFFSET 一定要搭配一个按稳定、唯一的列排序的 ORDER BY。

Coddy 编程语言插图

和 Coddy 一起学编程

开始