我有桌子
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_x
与id_x
来自最后的排前一页(对于DESC
和ASC
).或者从第一个向后导航.
您已经拥有的索引支持比较行值 - 这是一个符合ANSI 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
构造:
PostgreSQL按日期时间asc排序,先是null吗?
特别注意两件事:
子句中的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
有关:
'WHERE(col1,col2)<(val1,val2)'的SQL语法术语
使用许多表中的列提高订单的性能
请特别注意Markus Winand的演讲,我将其链接到:
"Pagination做了PostgreSQL的方式"