PostgreSQL 13:分区和tsrange的高效使用

PostgreSQL 13: Efficient use of partitions and tsrange

我正在尝试弄清楚如何最有效地使用 table 分区和 tsrange 函数。我正在使用 PostgreSQL 13。

我将在此处 post 举一个例子,知道它效率不高并理解原因,但我正在寻找有关如何使其高效和调整的建议。 在下面的示例中,每次我 运行 select 查询时都会读取所有分区。 我想在预订日历中查找一次乘船旅行是否与另一次重叠。

CREATE TABLE boat_trips (
   id INTEGER NOT NULL
   , boat_name VARCHAR(32)
   , departure_time TIMESTAMP WITHOUT TIME ZONE  NOT NULL
   , destination_time TIMESTAMP WITHOUT TIME ZONE NOT NULL
) 
PARTITION BY RANGE (departure_time);
                          
CREATE TABLE IF NOT EXISTS boat_trips_20210206 PARTITION OF boat_trips FOR VALUES FROM ('2021-02-06 00:00:00') TO ('2021-02-06 23:59:59');
CREATE TABLE IF NOT EXISTS boat_trips_20210207 PARTITION OF boat_trips FOR VALUES FROM ('2021-02-07 00:00:00') TO ('2021-02-07 23:59:59');
CREATE TABLE IF NOT EXISTS boat_trips_20210208 PARTITION OF boat_trips FOR VALUES FROM ('2021-02-08 00:00:00') TO ('2021-02-08 23:59:59');
                           
INSERT INTO boat_trips VALUES (1, 'The Beautiful', '2021-02-06 11:15:00'::TIMESTAMP, '2021-02-06 12:15:00'::TIMESTAMP);
INSERT INTO boat_trips VALUES (2, 'The Incredible', '2021-02-06 13:15:00'::TIMESTAMP, '2021-02-06 14:15:00'::TIMESTAMP);
INSERT INTO boat_trips VALUES (3, 'The Beautiful', '2021-02-06 12:30:00'::TIMESTAMP, '2021-02-06 13:15:00'::TIMESTAMP);
INSERT INTO boat_trips VALUES (4, 'The Beautiful', '2021-02-07 11:15:00'::TIMESTAMP, '2021-02-07 12:15:00'::TIMESTAMP);
INSERT INTO boat_trips VALUES (5, 'The Incredible', '2021-02-07 13:15:00'::TIMESTAMP, '2021-02-07 14:15:00'::TIMESTAMP);
INSERT INTO boat_trips VALUES (6, 'The Beautiful', '2021-02-07 12:30:00'::TIMESTAMP, '2021-02-07 13:15:00'::TIMESTAMP);
INSERT INTO boat_trips VALUES (7, 'The Beautiful', '2021-02-08 11:15:00'::TIMESTAMP, '2021-02-08 12:15:00'::TIMESTAMP);
INSERT INTO boat_trips VALUES (8, 'The Incredible', '2021-02-08 13:15:00'::TIMESTAMP, '2021-02-08 14:15:00'::TIMESTAMP);
INSERT INTO boat_trips VALUES (9, 'The Beautiful', '2021-02-08 12:30:00'::TIMESTAMP, '2021-02-08 13:15:00'::TIMESTAMP);

然后我运行select查询如下:

SELECT DISTINCT bt.id, bt.boat_name, bt.departure_time, bt.destination_time
FROM  boat_trips bt
WHERE  tsrange(departure_time, destination_time) &&
       tsrange '[2021-02-07 00:00:00,2021-02-08 00:00:00)';

如果我得到解释计划,我发现所有的分区都被解析了,这是正常的,因为分区键id departure_time。

如何在 select 查询中利用分区和 tsrange 函数?

我尝试在 tsrange(departure_time, destination_time) 上进行分区,但是我找不到创建分区的语法。

这里有个例子=> db fiddle

对于像整数这样的标量值,有一个 order relationship,所以如果你有一个限制值 L,你可以决定任何值 x=L 进入分区 foo进入分区栏。

范围没有可用于制作分区的顺序关系...

假设您有范围 [x1,x2] 和 [y1,y2],为了将它们分类到分区中,您必须对它们进行排序,这意味着定义一个具有所需属性的运算符“<=”:

  • a ≤ a(自反性)
  • 如果a≤b且b≤a则a=b(反对称)
  • 如果a≤b且b≤c则a≤c(传递性)。

Postgres 有这样一个范围运算符,如果您在范围上创建 btree 索引,它就会使用它。没有订单操作就无法创建 btree。但是这个运算符不会产生对范围有意义的顺序,因为对于范围 [x1,x2] <= [y1,y2] 只是比较元组 (x1,x2) <= (y1,y2),在其他情况下也就是说,returns是x1和y1比较的结果,如果相等,是x2和y2比较的结果。

如果您有一组不重叠的范围,那么您可以定义适当的顺序关系。但是你的乘船出发和到达时间不是一个不重叠的集合

这意味着,无论分区之间的限制值L如何,前一个分区中可能存在一个范围,其长度足以使其与下一个分区中的范围重叠。

但是,如果您知道每次旅行都不会超过一个月...那么任何间隔都不会超过一个月。因此,如果按月进行分区,您知道分区 N-1 中的某些范围可能与分区 N 中的范围重叠,但分区 N-1 中的范围不会与分区 N+1 中的任何范围重叠。

因此,如果您对每个分区施加约束以确保每个范围的上限不能超过某个值,例如:

CHECK( destination_time < ... )

然后您可以将此添加到您的查询中:

哪里 tsrange(departure_time, destination_time) && tsrange '[2021-02-07 00:00:00,2021-02-08 00:00:00)' AND destination_time < '2021-02-08 00:00:00';

约束排除应消除不需要扫描的分区。

请注意,使用 GIST 索引可以大大加快范围重叠测试的速度。如果您使用分区的唯一原因是性能,这可能是更好的解决方案。如果你想使用分区来管理大量数据并使删除速度更快,那么只使用几个大分区和一个 gist 索引可能是最优的。范围查询将命中所有分区上的要点索引,但如果其中没有匹配的范围,则每个分区不应超过几十微秒。