Postgresql:按分区键排序的查询
Postgresql: queries with ordering by partitioning key
我在 PostgreSQL 中创建了一个按 received
列分区(参见 here)的 table。让我们使用一个玩具示例:
CREATE TABLE measurement (
received timestamp without timezone PRIMARY KEY,
city_id int not null,
peaktemp int,
unitsales int
);
多年来我每个月都创建一个分区 (measurement_y2012m01
... measurement_y2016m03
)。
我注意到 postgresql 不知道分区的顺序,所以对于如下查询:
select * from measurement where ... order by received desc limit 1000;
postgresql 对所有分区执行索引扫描,即使前 1000 个结果很可能位于最新分区(或前两个或三个)中。
您知道如何利用分区进行此类查询吗?我想强调 where
子句可能会有所不同,我不想对其进行硬编码。
第一个想法是以适当的顺序迭代分区,直到获取 1000 条记录或访问所有分区。但是如何灵活的实现呢?我想避免在应用程序中实现上述迭代,但我不介意应用程序是否需要调用存储过程。
在此先感谢您的帮助!
格热戈日
您可以猜测 received
的范围来满足您的查询并扩展它,直到您获得所需的行数,而不是迭代分区。将范围添加到 WHERE
将排除不必要的分区(假设您设置了排除约束)。
编辑
正确,这就是我的意思(可以用更好的措辞)。
简单似乎是一个相当合理的优势。无论哪种方式,我都不认为性能有什么不同。如果您在大多数情况下都合理地接近所需范围,这实际上可能会更有效一些,但可能不会产生显着差异。
它也更灵活一些,因为您不依赖查询代码中的特定分区方案。
如果您真的不知道要扫描多少个分区才能在输出中获得所需的 1000 行,您可以在存储过程中构建结果集并在分区上迭代获取结果,直到满足限制条件为止。
从最近的分区开始是明智的做法。
select * from measurement_y2016m03 where ... order by received desc limit 1000;
您可以将即时结果集存储在一条记录中并对其进行计数并动态更改下一个扫描分区的限制,这样如果您在第一个分区中获取例如 870
行,您可以使用 limit 130
构建第二个查询,然后再次执行计数,如果仍然不满足您的 1000
行条件,则增加计数器。
为什么 Postgres 在规划期间不知道什么时候停止?
Planner 不知道需要多少分区才能满足您的 LIMIT
条款。因此,它必须通过附加每个分区的结果来对整个集合进行排序,然后执行限制(除非它已经在 运行 时间内满足此条件)。在 SQL 语句中执行此操作的唯一方法是将查找仅限于几个分区 - 但对您来说可能并非如此。此外,如果您在查找期间访问磁盘,增加 work_mem
设置可能会加快速度。
重点说明
此外,要记住的是,当您设置分区时,您应该按最常访问的分区的降序排列。这会加快您的插入速度,因为 Postgres 会逐个检查条件并在第一个满足条件时停止。
我在 PostgreSQL 中创建了一个按 received
列分区(参见 here)的 table。让我们使用一个玩具示例:
CREATE TABLE measurement (
received timestamp without timezone PRIMARY KEY,
city_id int not null,
peaktemp int,
unitsales int
);
多年来我每个月都创建一个分区 (measurement_y2012m01
... measurement_y2016m03
)。
我注意到 postgresql 不知道分区的顺序,所以对于如下查询:
select * from measurement where ... order by received desc limit 1000;
postgresql 对所有分区执行索引扫描,即使前 1000 个结果很可能位于最新分区(或前两个或三个)中。
您知道如何利用分区进行此类查询吗?我想强调 where
子句可能会有所不同,我不想对其进行硬编码。
第一个想法是以适当的顺序迭代分区,直到获取 1000 条记录或访问所有分区。但是如何灵活的实现呢?我想避免在应用程序中实现上述迭代,但我不介意应用程序是否需要调用存储过程。
在此先感谢您的帮助!
格热戈日
您可以猜测 received
的范围来满足您的查询并扩展它,直到您获得所需的行数,而不是迭代分区。将范围添加到 WHERE
将排除不必要的分区(假设您设置了排除约束)。
编辑
正确,这就是我的意思(可以用更好的措辞)。
简单似乎是一个相当合理的优势。无论哪种方式,我都不认为性能有什么不同。如果您在大多数情况下都合理地接近所需范围,这实际上可能会更有效一些,但可能不会产生显着差异。
它也更灵活一些,因为您不依赖查询代码中的特定分区方案。
如果您真的不知道要扫描多少个分区才能在输出中获得所需的 1000 行,您可以在存储过程中构建结果集并在分区上迭代获取结果,直到满足限制条件为止。
从最近的分区开始是明智的做法。
select * from measurement_y2016m03 where ... order by received desc limit 1000;
您可以将即时结果集存储在一条记录中并对其进行计数并动态更改下一个扫描分区的限制,这样如果您在第一个分区中获取例如 870
行,您可以使用 limit 130
构建第二个查询,然后再次执行计数,如果仍然不满足您的 1000
行条件,则增加计数器。
为什么 Postgres 在规划期间不知道什么时候停止?
Planner 不知道需要多少分区才能满足您的 LIMIT
条款。因此,它必须通过附加每个分区的结果来对整个集合进行排序,然后执行限制(除非它已经在 运行 时间内满足此条件)。在 SQL 语句中执行此操作的唯一方法是将查找仅限于几个分区 - 但对您来说可能并非如此。此外,如果您在查找期间访问磁盘,增加 work_mem
设置可能会加快速度。
重点说明
此外,要记住的是,当您设置分区时,您应该按最常访问的分区的降序排列。这会加快您的插入速度,因为 Postgres 会逐个检查条件并在第一个满足条件时停止。