分区约束不用于涉及按时间戳分区的表的连接

rap*_*ael 11 postgresql performance partitioning postgresql-9.6 query-performance

我有一个分区表结构,如:

CREATE TABLE measurements (
    sensor_id bigint,
    tx timestamp,
    measurement int
);

CREATE TABLE measurements_201201(
    CHECK (tx >= '2012-01-01 00:00:00'::timestamp without time zone 
       AND tx < ('2012-01-01 00:00:00'::timestamp without time zone + '1 mon'::interval))    
)INHERITS (measurements);
CREATE INDEX ON measurements_201201(sensor_id);
CREATE INDEX ON measurements_201201(tx);
CREATE INDEX ON measurements_201201(sensor_id, tx);
....
Run Code Online (Sandbox Code Playgroud)

等等。每个表有大约 20M 行。

如果我在WHERE子句中查询传感器样本和时间戳样本,查询计划会显示选择的正确表和使用的索引,例如:

SELECT *
FROM measurements
INNER JOIN sensors TABLESAMPLE BERNOULLI (0.01) USING (sensor_id)
WHERE tx BETWEEN '2015-01-04 05:00' AND '2015-01-04 06:00' 
    OR tx BETWEEN '2015-02-04 05:00' AND '2015-02-04 06:00' 
    OR tx BETWEEN '2014-03-05 05:00' AND '2014-04-07 06:00' ;
Run Code Online (Sandbox Code Playgroud)

但是,如果我使用 CTE,或者将时间戳值放入表中(未显示,即使临时表上有索引)。

WITH sensor_sample AS(
    SELECT sensor_id, start_ts, end_ts
    FROM sensors TABLESAMPLE BERNOULLI (0.01)
    CROSS JOIN (VALUES (TIMESTAMP '2015-01-04 05:00', TIMESTAMP '2015-01-04 06:00'),
        (TIMESTAMP '2015-02-04 05:00', TIMESTAMP '2015-02-04 06:00'),
        (TIMESTAMP  '2014-03-05 05:00', '2014-04-07 06:00') ) tstamps(start_ts, end_ts)
)
Run Code Online (Sandbox Code Playgroud)

像下面这样的东西

SET constraint_exclusion = on;
SELECT * FROM measurements
INNER JOIN sensor_sample USING (sensor_id)
WHERE tx BETWEEN start_ts AND end_ts
Run Code Online (Sandbox Code Playgroud)

对每个表执行索引扫描。这仍然相对较快,但随着查询复杂性的增加,这可能会变成 seq 扫描,这最终会非常缓慢地从有限的分区表子集(50 个中的 4-5 个)中检索约 40K 行。

我关注的是,像这样的问题。

对于非平凡的表达式,您必须在查询中或多或少地重复逐字条件,以使 Postgres 查询规划器理解它可以依赖于 CHECK 约束。即使它看起来多余!

如何改进分区和查询结构以减少对所有数据运行 seq 扫描的可能性?

fil*_*rem 1

基于约束的排除[CBE]是在查询规划的早期阶段执行的,就在查询被解析、映射到实际关系并重写之后。(内部结构,规划器/优化器阶段)

规划器不能假设“sensor_sample”表的任何内容。

因此,除非您在查询中硬编码了值,否则规划器不会排除“分区”。

我猜想 CTE 变体会发生什么......规划器受到限制,因为您使用 TABLESAMPLE 并且整个子查询可能被视为易失性,即使子查询中的文字是静态的。(这只是我的猜测,我不是规划器代码方面的专家

好的一面是,带有负面结果的索引扫描速度非常快。(最多单页扫描!)所以除非你有超过 10000 个分区,否则我不会打扰。

所以,直接回答你的问题:

  • 您无法进一步改进此数据结构。

  • 关于索引扫描 - 它们很便宜;

  • 关于顺序扫描 - 尽可能避免它们,正如您在自己的示例中看到的那样。