大OFFSET
的总是会很慢。Postgres 必须对所有行进行排序并将可见行数到您的偏移量。要直接跳过所有先前的行,您可以将索引添加row_number
到表中(或创建一个MATERIALIZED VIEW
包含 said row_number
)并使用WHERE row_number > x
而不是OFFSET x
.
但是,这种方法仅适用于只读(或大部分)数据。对可以同时更改的表数据实施相同的操作更具挑战性。您需要从准确定义所需行为开始。
我建议采用不同的分页方法:
SELECT *
FROM big_table
WHERE (vote, id) > (vote_x, id_x) -- ROW values
ORDER BY vote, id -- needs to be deterministic
LIMIT n;
上一页最后一行的位置和(对于和vote_x
)。或者如果向后导航,则从第一个开始。id_x
DESC
ASC
您已经拥有的索引支持比较行值 - 该功能符合 ISO SQL 标准,但并非每个 RDBMS 都支持它。
CREATE INDEX vote_order_asc ON big_table (vote, id);
或降序:
SELECT *
FROM big_table
WHERE (vote, id) < (vote_x, id_x) -- ROW values
ORDER BY vote DESC, id DESC
LIMIT n;
可以使用相同的索引。
我建议您声明您的专栏NOT NULL
或熟悉该NULLS FIRST|LAST
构造:
特别注意两点:
子句中的ROW
值WHERE
不能用单独的成员字段替换。WHERE (vote, id) > (vote_x, id_x)
不能替换为:
WHERE vote >= vote_x
AND id > id_x
这将排除所有带有 的行id <= id_x
,而我们只想为同一个投票而不是下一个投票。正确的翻译应该是:
WHERE (vote = vote_x AND id > id_x) OR vote > vote_x
...它不能很好地与索引一起使用,并且对于更多列变得越来越复杂。
显然,对于单个列来说很简单。这就是我一开始提到的特殊情况。
该技术不适用于以下混合方向ORDER BY
:
ORDER BY vote ASC, id DESC
至少我想不出一种通用的方法来有效地实现这一点。如果两列中至少有一个是数字类型,则可以使用带有反转值的功能索引(vote, (id * -1))
- 并在 中使用相同的表达式ORDER BY
:
ORDER BY vote ASC, (id * -1) ASC
有关的:
请特别注意 Markus Winand 的演示文稿,我链接到: