我有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或更新版本有一些功能?我找过了但什么也没找到。
2条答案
按热度按时间oxosxuxt1#
一个大的
OFFSET
总是很慢。Postgres必须对所有行进行排序,并将 * 可见 * 的行计数到偏移量。要直接跳过前面的所有行直接,可以在表中添加索引row_number
(或创建MATERIALIZED VIEW
,包括row_number
),然后使用WHERE row_number > x
而不是OFFSET x
。然而,这种方法仅适用于只读(或大部分)数据。对于可以 * 并发 * 更改的表数据,实现相同的方法更具挑战性。你需要从定义你想要的行为开始 * 精确 。
我建议一种不同的方法,有时称为“键集分页”**:
其中
vote_x
和id_x
来自***上一页***的 * 最后 * 行(DESC
和ASC
)。或从 * 第一个 *,如果导航 * 向后 *。比较行值由您已有的索引支持-这是一个符合ISO SQL标准的功能,但并非所有RDBMS都支持它。
或降序:
可以使用相同的索引。
我建议你声明你的列
NOT NULL
或者熟悉NULLS FIRST|LAST
的构造:注意两件事特别:
WHERE
子句中的ROW
值不能用分开的成员字段替换。WHERE (vote, id) > (vote_x, id_x)
***不能***替换为:这将排除 * 所有 * 行
id <= id_x
,而我们只想为同一个投票而不是下一个投票做这件事。正确的翻译应该是:...这并不能很好地与索引一起工作,并且对于更多的列来说变得越来越复杂。
显然,对于一个 * 单 * 列来说很简单。这就是我在一开始提到的特殊情况。
1.该技术不适用于
ORDER BY
中的混合方向,如:至少我想不出一种通用的方法来有效地实现这个。如果这两列中至少有一列是数值类型,则可以在
(vote, (id * -1))
上使用一个带有倒排值的函数索引-并在ORDER BY
中使用相同的表达式:相关内容:
请特别注意Markus Winand的演讲,我链接到:
wyyhbhjk2#
你试过把table分开吗?
易于管理、改进的可伸缩性和可用性以及减少阻塞是划分表的常见原因。提高查询性能并不是使用分区的理由,尽管在某些情况下这可能是一个有益的副作用。在性能方面,确保实现计划包括查询性能的审查非常重要。确认在表分区后,索引继续适当地支持查询,并验证使用聚集索引和非聚集索引的查询是否受益于分区消除(如果适用)。
http://sqlperformance.com/2013/09/sql-indexes/partitioning-benefits