sql - 在大表上使用 OFFSET 优化查询

标签 sql postgresql pagination sql-order-by postgresql-9.5

我有 table

create table big_table (
id serial primary key,
-- other columns here
vote int
); 

这个表很大,大概有7000万行,我要查询:

SELECT * FROM big_table
ORDER BY vote [ASC|DESC], id [ASC|DESC]
OFFSET x LIMIT n  -- I need this for pagination

您可能知道,当 x 是一个很大的数字时,这样的查询会非常慢。

为了性能优化我添加了索引:

create index vote_order_asc on big_table (vote asc, id asc);

create index vote_order_desc on big_table (vote desc, id desc);

EXPLAIN 表明上面的 SELECT 查询使用了这些索引,但是它的速度非常慢,偏移量很大。

如何优化大表中使用 OFFSET 的查询?也许 PostgreSQL 9.5 甚至更高版本有一些特性?我已经搜索过,但没有找到任何东西。

最佳答案

OFFSET总是会很慢。 Postgres 必须对所有行进行排序并将可见 行计数到您的偏移量。要直接跳过所有前面的行,您可以添加索引 row_number到表(或创建一个 MATERIALIZED VIEW 包括所述 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_xid_x来自上一页最后行(对于DESCASC)。如果向后导航,则从第一个开始。

您已有的索引支持比较行值 - 该功能符合 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构造:

请特别注意两件事:

  1. ROW WHERE 中的值子句不能替换为分隔的成员字段。 WHERE (vote, id) > (vote_x, id_x) 不能替换为:

    <strike>WHERE  vote >= vote_x
    AND    id   > id_x</strike>

    这将排除 所有id <= id_x ,而我们只想对同一次投票而不是下一次投票这样做。正确的翻译应该是:

    WHERE (vote = vote_x AND id > id_x) OR vote > vote_x
    

    ...它不能很好地与索引一起使用,并且对于更多的列会变得越来越复杂。

    显然,单个 列会很简单。这就是我一开始提到的特殊情况。

  2. 该技术不适用于 ORDER BY 中的混合方向喜欢:

    ORDER  BY vote ASC, id DESC
    

    至少我想不出一种通用的方法来有效地实现它。如果两列中至少有一个是数字类型,则可以在 (vote, (id * -1)) 上使用具有倒排值的函数索引。 - 并在 ORDER BY 中使用相同的表达式:

    ORDER  BY vote ASC, (id * -1) ASC
    

相关:

请特别注意 Markus Winand 的演讲,我链接到:

关于sql - 在大表上使用 OFFSET 优化查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34110504/

相关文章:

java - GraphQL:如何使用 graphQL-java 实现分页?

javascript - 显示列表中的元素,计数器为三

php - MySQL说: #1194 - Table 'tablename' is marked as crashed and should be repaired

sql - Azure SQL 的 Webhook

sql - 是否可以使用结果中的值将另一个结果合并到前一个结果集中?

java - Postgres/hibernate 运算符不存在 : text = bytea

angular - 在执行 ng build --prod 时,jw-angular-pagination 抛出错误

SQL Server : coalesce, 阻止额外空间?

sql - PostgreSql - 如何使用另一列上的过滤器创建条件列?

postgresql - 将 ElementCollection 与 jpa 一起使用时需要什么列类型?