查询规划器未使用时间戳上的部分索引,尽管 WHERE 子句中的周期匹配

问题描述 投票:0回答:1

我正在使用一个相对较大的 Postgres 数据库,其中包含几 TB 的数据。它由 5 年前的数据组成。结果,所有索引都在增长,从而降低了查询性能并消耗大量存储。当前索引定义(

original_index
)如下:

CREATE INDEX original_index ON table1
   USING btree (key10 DESC)

我试图将索引限制为从现在起仅 1 年前,索引定义 (

constrained_index
) 如下:(我在创建
original_index
之前删除了
constrained_index

CREATE INDEX constrained_index ON table1 
   USING btree (key10 DESC) 
   WHERE (key10 >= '2023-06-01 00:00:00+00'::timestamp with time zone)

我对

original_index
constrained_index
使用特定查询来查看执行时间之前和之后。

当我只有

original_index
时,查询将其用于查询规划器。但是当我只有
constrained_index
时,查询规划器不会使用
constrained_index
。相反,它使用了对桌子的顺序扫描。

创建

constrained_index
后,我尝试运行
vacuum verbose analyze table1;
来更新数据库的统计信息。

如果有人能解释为什么不选择index2,我们将不胜感激。

Postgres 版本:

12.5

表格定义:

CREATE TABLE table1 (
    key1 integer,
    key2 character varying(128),
    key3 character varying(64),
    key4 character varying(128),
    key5 character varying(10),
    key6 timestamp with time zone,
    key7 timestamp with time zone,
    key8 integer NOT NULL GENERATED BY DEFAULT AS IDENTITY
    key9 boolean NOT NULL,
    key10 timestamp with time zone
);

CREATE TABLE table2 (
    key2 integer NOT NULL GENERATED BY DEFAULT AS IDENTITY
    key3 timestamp with time zone NOT NULL,
    key1 integer
);

CREATE TABLE table3 (
    key1 character varying(128) NOT NULL,
    key2 text
);

CREATE TABLE table4 (
    key1 character varying(64) NOT NULL,
    key2 text
);

查询:

SELECT table1.key1 AS a,
       table1.key2 AS b,
       table1.key3 AS c,
       table1.key4 AS d,
       table2.key1 AS e,
       COUNT(DISTINCT table1.key8) FILTER (WHERE table1.key5 = 'A') AS f,
       COUNT(DISTINCT table1.key8) FILTER (WHERE table1.key5 = 'B') AS g,
       COUNT(DISTINCT table1.key8) FILTER (WHERE table1.key5 = 'C') AS h,
       COUNT(DISTINCT table1.key8) FILTER (WHERE table1.key5 = 'D') AS i,
       COUNT(DISTINCT table1.key8) FILTER (WHERE table1.key5 IS NULL) AS j,
       SUM((table1.key6 - table1.key7)) AS k,
       COUNT(DISTINCT table1.key8) FILTER (WHERE table1.key9) AS l,
       COUNT(DISTINCT table1.key8) AS m
FROM table1
LEFT OUTER JOIN table3 ON (table1.key4 = table3.key1)
LEFT OUTER JOIN table2 ON (table1.key1 = table2.key2)
LEFT OUTER JOIN table4 ON (table1.key3 = table4.key1)
WHERE table1.key10 BETWEEN '2023-10-31 23:00:00+00:00' AND '2023-11-02 23:00:00+00:00'
GROUP BY 1,
         2,
         3,
         4,
         5,
         table2.key3,
         table4.key2,
         table3.key2
ORDER BY table2.key3 DESC,
         table1.key2 ASC,
         table4.key2 DESC,
         table3.key2 DESC,
         table2.key1 ASC
LIMIT 20

EXPLAIN ANALYZE
的输出:

Limit  (cost=18341692.59..18341693.89 rows=20 width=159) (actual time=32149.681..32192.245 rows=20 loops=1)
  ->  GroupAggregate  (cost=18341692.59..18353658.57 rows=184092 width=159) (actual time=31749.816..31792.379 rows=20 loops=1)
        Group Key: table2.key3, table1.key2, table4.key2, table3.key2, table2.key1, table1.key1, table1.key3, table1.key4
        ->  Sort  (cost=18341692.59..18342152.82 rows=184092 width=115) (actual time=31749.763..31792.253 rows=80 loops=1)
              Sort Key: table2.key3 DESC, table1.key2, table4.key2 DESC, table3.key2 DESC, table2.key1, table1.key1, table1.key3, table1.key4
              Sort Method: external merge  Disk: 23424kB
              ->  Gather  (cost=83595.20..18314267.69 rows=184092 width=115) (actual time=31417.800..31681.660 rows=186540 loops=1)
                    Workers Planned: 2
                    Workers Launched: 2
                    ->  Hash Left Join  (cost=82595.20..18294858.49 rows=76705 width=115) (actual time=31392.569..31545.627 rows=62180 loops=3)
                          Hash Cond: ((table1.key4)::text = (table3.key1)::text)
                          ->  Hash Left Join  (cost=81113.79..18293175.70 rows=76705 width=90) (actual time=31379.926..31524.528 rows=62180 loops=3)
                                Hash Cond: ((table1.key3)::text = (table4.key1)::text)
                                ->  Parallel Hash Left Join  (cost=78481.53..18288053.08 rows=76705 width=72) (actual time=31361.889..31481.346 rows=62180 loops=3)
                                      Hash Cond: (table1.key1 = table2.key2)
                                      ->  Parallel Seq Scan on table1  (cost=0.00..18200395.20 rows=76705 width=60) (actual time=2.888..30824.027 rows=62180 loops=3)
                                            Filter: ((key10 >= '2023-10-31 23:00:00+00'::timestamp with time zone) AND (key10 "<= '2023-11-02 23:00:00+00'::timestamp with time zone))
                                            Rows Removed by Filter: 43882417
                                      ->  Parallel Hash  (cost=52397.57..52397.57 rows=1500557 width=16) (actual time=497.607..497.608 rows=1200445 loops=3)
                                            Buckets: 131072  Batches: 64  Memory Usage: 3712kB
                                            ->  Parallel Seq Scan on table2  (cost=0.00..52397.57 rows=1500557 width=16) (actual time=225.666..349.851 rows=1200445 loops=3)
                                ->  Hash  (cost=1244.45..1244.45 rows=71745 width=25) (actual time=17.720..17.721 rows=72031 loops=3)
                                      Buckets: 65536  Batches: 2  Memory Usage: 2568kB
                                      ->  Seq Scan on table4  (cost=0.00..1244.45 rows=71745 width=25) (actual time=0.019..6.313 rows=72031 loops=3)
                          ->  Hash  (cost=893.96..893.96 rows=46996 width=40) (actual time=12.367..12.368 rows=47030 loops=3)
                                Buckets: 65536  Batches: 1  Memory Usage: 3865kB
                                ->  Seq Scan on table3  (cost=0.00..893.96 rows=46996 width=40) (actual time=0.017..4.731 rows=47030 loops=3)
Planning Time: 2.249 ms
JIT:
  Functions: 90
  Options: Inlining true, Optimization true, Expressions true, Deforming true
  Timing: Generation 8.216 ms, Inlining 84.266 ms, Optimization 596.840 ms, Emission 394.586 ms, Total 1083.909 ms
Execution Time: 32199.853 ms
postgresql indexing database-performance postgresql-12
1个回答
1
投票

如果有人能解释为什么不选择,我们将不胜感激 索引2.

示例 11.2 中关于部分索引的 Postgres 文档说:

但是,请记住谓词必须与条件匹配 用于应该从索引中受益的查询。成为 精确的,只有当系统可以时才可以在查询中使用部分索引 认识到查询的 WHERE 条件在数学上意味着 索引的谓词。 PostgreSQL 没有复杂的 可以识别数学等价的定理证明者 以不同形式书写的表达式。(不仅是这样 一般定理证明器极难创建,它会 可能太慢而没有任何实际用途。)系统可以识别 简单的不平等含义,例如“x < 1” implies “x < 2”; otherwise the predicate condition must exactly match part of the query's WHERE condition or the index will not be recognized as usable. Matching takes place at query planning time, not at run time. As a result, parameterized query clauses do not work with a partial index. For example a prepared query with a parameter might specify “x < ?” which will never imply “x < 2” for all possible values of the parameter.

您的索引有一个简单的

>=
条件。您的查询有
BETWEEN
。是的,它们在逻辑上是重叠的,但正如您刚刚观察到的,Postgres planner 不够聪明,无法推断出它们是等价的。

首先尝试使用简单的

>=
过滤器运行查询。即使没有第二个
<=
部分,并且具有与索引定义中完全相同的恒定日期,并查看规划器是否会使用部分索引。然后将查询中的固定日期更改为不同的日期。然后添加第二个
<=
部分。

此外,请仔细检查有关参数化查询不适用于部分索引的最后一个短语是否适用于您的查询。

最后一点,请参阅示例 11.4 及其警告:不要使用部分索引作为分区的替代品


此外,一般来说,在实践中,当部分索引大幅减少索引中包含的行数时,它们很有用。 5 年中的 1 年 (20%) 并不重要。即使您让优化器为部分索引生成与完整索引相同的计划,我也不期望查询性能有明显的差异。毕竟,查询中的日期范围是相同的,无论如何它都必须从表中读取相同数量的行。在 b 树中搜索范围的起始点速度很快 (

log(N)
)。当
N
变成
0.2*N
时,您不会注意到差异。

© www.soinside.com 2019 - 2024. All rights reserved.