简体   繁体   English

分区表查询仍然扫描所有分区

[英]Partitioned table query still scanning all partitions

I have a table with over a billion records. 我有一张超过十亿条记录的桌子。 In order to improve performance, I partitioned it to 30 partitions. 为了提高性能,我将其分区为30个分区。 The most frequent queries have (id = ...) in their where clause, so I decided to partition the table on the id column. 最常见的查询在where子句中有(id = ...) ,所以我决定在id列上对表进行分区。

Basically, the partitions were created in this way: 基本上,分区是以这种方式创建的:

CREATE TABLE foo_0 (CHECK (id % 30 = 0)) INHERITS (foo);
CREATE TABLE foo_1 (CHECK (id % 30 = 1)) INHERITS (foo);
CREATE TABLE foo_2 (CHECK (id % 30 = 2)) INHERITS (foo);
CREATE TABLE foo_3 (CHECK (id % 30 = 3)) INHERITS (foo);
.
.
.

I ran ANALYZE for the entire database and in particular, I made it collect extra statistics for this table's id column by running: 我为整个数据库运行了ANALYZE ,特别是,我通过运行以下方法收集了该表的id列的额外统计信息:

ALTER TABLE foo ALTER COLUMN id SET STATISTICS 10000;

However when I run queries that filter on the id column the planner shows that it's still scanning all the partitions. 但是,当我运行在id列上过滤的查询时,计划程序会显示它仍在扫描所有分区。 constraint_exclusion is set to partition , so that's not the problem. constraint_exclusion设置为partition ,因此不是问题。

EXPLAIN ANALYZE SELECT * FROM foo WHERE (id = 2);


                                               QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------
 Result  (cost=0.00..8106617.40 rows=3620981 width=54) (actual time=30.544..215.540 rows=171477 loops=1)
   ->  Append  (cost=0.00..8106617.40 rows=3620981 width=54) (actual time=30.539..106.446 rows=171477 loops=1)
         ->  Seq Scan on foo  (cost=0.00..0.00 rows=1 width=203) (actual time=0.002..0.002 rows=0 loops=1)
               Filter: (id = 2)
         ->  Bitmap Heap Scan on foo_0 foo  (cost=3293.44..281055.75 rows=122479 width=52) (actual time=0.020..0.020 rows=0 loops=1)
               Recheck Cond: (id = 2)
               ->  Bitmap Index Scan on foo_0_idx_1  (cost=0.00..3262.82 rows=122479 width=0) (actual time=0.018..0.018 rows=0 loops=1)
                     Index Cond: (id = 2)
         ->  Bitmap Heap Scan on foo_1 foo  (cost=3312.59..274769.09 rows=122968 width=56) (actual time=0.012..0.012 rows=0 loops=1)
               Recheck Cond: (id = 2)
               ->  Bitmap Index Scan on foo_1_idx_1  (cost=0.00..3281.85 rows=122968 width=0) (actual time=0.010..0.010 rows=0 loops=1)
                     Index Cond: (id = 2)
         ->  Bitmap Heap Scan on foo_2 foo  (cost=3280.30..272541.10 rows=121903 width=56) (actual time=30.504..77.033 rows=171477 loops=1)
               Recheck Cond: (id = 2)
               ->  Bitmap Index Scan on foo_2_idx_1  (cost=0.00..3249.82 rows=121903 width=0) (actual time=29.825..29.825 rows=171477 loops=1)
                     Index Cond: (id = 2)
.
.
.

What could I do to make the planer have a better plan? 我能做些什么让刨床有更好的计划? Do I need to run ALTER TABLE foo ALTER COLUMN id SET STATISTICS 10000; 我是否需要运行ALTER TABLE foo ALTER COLUMN id SET STATISTICS 10000; for all the partitions as well? 对于所有分区呢?

EDIT 编辑

After using Erwin's suggested change to the query, the planner only scans the correct partition, however the execution time is actually worse then a full scan (at least of the index). 在使用Erwin建议的查询更改后,计划程序仅扫描正确的分区,但执行时间实际上比完整扫描(至少是索引)更差。

EXPLAIN ANALYZE select * from foo where (id % 30 = 2) and (id = 2);
                                                                         QUERY PLAN
                                                                             QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Result  (cost=0.00..8106617.40 rows=3620981 width=54) (actual time=32.611..224.934 rows=171477 loops=1)
   ->  Append  (cost=0.00..8106617.40 rows=3620981 width=54) (actual time=32.606..116.565 rows=171477 loops=1)
         ->  Seq Scan on foo  (cost=0.00..0.00 rows=1 width=203) (actual time=0.002..0.002 rows=0 loops=1)
               Filter: (id = 2)
         ->  Bitmap Heap Scan on foo_0 foo  (cost=3293.44..281055.75 rows=122479 width=52) (actual time=0.046..0.046 rows=0 loops=1)
               Recheck Cond: (id = 2)
               ->  Bitmap Index Scan on foo_0_idx_1  (cost=0.00..3262.82 rows=122479 width=0) (actual time=0.044..0.044 rows=0 loops=1)
                     Index Cond: (id = 2)
         ->  Bitmap Heap Scan on foo_1 foo  (cost=3312.59..274769.09 rows=122968 width=56) (actual time=0.021..0.021 rows=0 loops=1)
               Recheck Cond: (id = 2)
               ->  Bitmap Index Scan on foo_1_idx_1  (cost=0.00..3281.85 rows=122968 width=0) (actual time=0.020..0.020 rows=0 loops=1)
                     Index Cond: (id = 2)
         ->  Bitmap Heap Scan on foo_2 foo  (cost=3280.30..272541.10 rows=121903 width=56) (actual time=32.536..86.730 rows=171477 loops=1)
               Recheck Cond: (id = 2)
               ->  Bitmap Index Scan on foo_2_idx_1  (cost=0.00..3249.82 rows=121903 width=0) (actual time=31.842..31.842 rows=171477 loops=1)
                     Index Cond: (id = 2)
         ->  Bitmap Heap Scan on foo_3 foo  (cost=3475.87..285574.05 rows=129032 width=52) (actual time=0.035..0.035 rows=0 loops=1)
               Recheck Cond: (id = 2)
               ->  Bitmap Index Scan on foo_3_idx_1  (cost=0.00..3443.61 rows=129032 width=0) (actual time=0.031..0.031 rows=0 loops=1)
.
.
.
         ->  Bitmap Heap Scan on foo_29 foo  (cost=3401.84..276569.90 rows=126245 width=56) (actual time=0.019..0.019 rows=0 loops=1)
               Recheck Cond: (id = 2)
               ->  Bitmap Index Scan on foo_29_idx_1  (cost=0.00..3370.28 rows=126245 width=0) (actual time=0.018..0.018 rows=0 loops=1)
                     Index Cond: (id = 2)
 Total runtime: 238.790 ms

Versus: 与:

EXPLAIN ANALYZE select * from foo where (id % 30 = 2) and (id = 2);
                                                                            QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Result  (cost=0.00..273120.30 rows=611 width=56) (actual time=31.519..257.051 rows=171477 loops=1)
   ->  Append  (cost=0.00..273120.30 rows=611 width=56) (actual time=31.516..153.356 rows=171477 loops=1)
         ->  Seq Scan on foo  (cost=0.00..0.00 rows=1 width=203) (actual time=0.002..0.002 rows=0 loops=1)
               Filter: ((id = 2) AND ((id % 30) = 2))
         ->  Bitmap Heap Scan on foo_2 foo  (cost=3249.97..273120.30 rows=610 width=56) (actual time=31.512..124.177 rows=171477 loops=1)
               Recheck Cond: (id = 2)
               Filter: ((id % 30) = 2)
               ->  Bitmap Index Scan on foo_2_idx_1  (cost=0.00..3249.82 rows=121903 width=0) (actual time=30.816..30.816 rows=171477 loops=1)
                     Index Cond: (id = 2)
 Total runtime: 270.384 ms

For non-trivial expressions you have to repeat the more or less verbatim condition in queries to make the Postgres query planner understand it can rely on the CHECK constraint. 对于非平凡的表达式,您必须在查询中重复或多或少的逐字条件,以使Postgres查询规划器理解它可以依赖于CHECK约束。 Even if it seems redundant! 即使看起来多余!

Per documentation : 每个文件

With constraint exclusion enabled, the planner will examine the constraints of each partition and try to prove that the partition need not be scanned because it could not contain any rows meeting the query's WHERE clause. 启用约束排除后,计划程序将检查每个分区的约束,并尝试证明不需要扫描分区,因为它不能包含满足查询的WHERE子句的任何行。 When the planner can prove this , it excludes the partition from the query plan. 当规划人员可以证明这一点时 ,它会从查询计划中排除分区。

Bold emphasis mine. 大胆强调我的。 The planner does not understand complex expressions. 规划者不理解复杂的表达方式。 Of course, this has to be met, too: 当然,这也必须得到满足:

Ensure that the constraint_exclusion configuration parameter is not disabled in postgresql.conf . 确保在postgresql.conf未禁用constraint_exclusion配置参数。 If it is, queries will not be optimized as desired. 如果是,则不会根据需要优化查询。

Instead of 代替


  
 
  
  
    SELECT * FROM foo WHERE (id = 2); 
  

Try: 尝试:

SELECT * FROM foo WHERE id % 30 = 2 AND id = 2;

And: 和:

The default (and recommended) setting of constraint_exclusion is actually neither on nor off , but an intermediate setting called partition , which causes the technique to be applied only to queries that are likely to be working on partitioned tables. constraint_exclusion的默认(和推荐)设置实际上既不是on也不是off ,而是一个名为partition的中间设置,这使得该技术仅应用于可能在分区表上工作的查询。 The on setting causes the planner to examine CHECK constraints in all queries, even simple ones that are unlikely to benefit. on设置使计划程序检查所有查询中的CHECK约束,即使是不太可能受益的简单查询。

You can experiment with the constraint_exclusion = on to see if the planner catches on without redundant verbatim condition. 您可以尝试使用constraint_exclusion = on来查看计划程序是否捕获而没有多余的逐字条件。 But you have to weigh cost and benefit of this setting. 但是你必须权衡这个设置的成本和收益。

The alternative would be simpler conditions for your partitions as already outlined by @harmic . 替代方案是@harmic已经概述的分区更简单的条件。

An no, increasing the number for STATISTICS will not help in this case. 否,增加STATISTICS的数量在这种情况下无济于事。 Only the CHECK constraints and your WHERE conditions in the query matter. 查询中只有CHECK约束和WHERE条件。

Unfortunately, partioning in postgresql is fairly primitive. 不幸的是,在postgresql中分配是相当原始的。 It only works for range and list based constraints. 它仅适用于基于范围和列表的约束。 Your partition constraints are too complex for the query planner to use to decide to exclude some partitions. 您的分区约束太复杂,查询计划程序无法决定排除某些分区。

In the manual it says: 手册中它说:

Keep the partitioning constraints simple, else the planner may not be able to prove that partitions don't need to be visited. 保持分区约束简单,否则规划人员可能无法证明不需要访问分区。 Use simple equality conditions for list partitioning, or simple range tests for range partitioning, as illustrated in the preceding examples. 使用简单的相等条件进行列表分区,或使用简单范围测试进行范围分区,如前面的示例所示。 A good rule of thumb is that partitioning constraints should contain only comparisons of the partitioning column(s) to constants using B-tree-indexable operators. 一个好的经验法则是,分区约束应该只包含使用B-tree-indexable运算符将分区列与常量进行比较。

You might get away with changing your WHERE clause so that the modulus expression is explicitly mentioned, as Erwin suggested. 正如Erwin建议的那样,你可能会改变你的WHERE子句,以便明确提到模数表达式。 I haven't had much luck with that in the past, although I have not tried recently and as he says, there have been improvements in the planner. 我过去没有太多运气,虽然我最近没有尝试过,正如他所说,计划者已经有了改进。 That is probably the first thing to try. 这可能是第一件要尝试的事情。

Otherwise, you will have to rearrange your partitions to use ranges of id values instead of the modulus method you are using now. 否则,您必须重新排列分区以使用id值范围而不是现在使用的模数方法。 Not a great solution, I know. 我知道,这不是一个好的解决方案。

One other solution is to store the modulus of the id in a separate column, which you can then use a simple value equality check for the partition constraint. 另一种解决方案是将id的模数存储在单独的列中,然后可以对分区约束使用简单的值相等性检查。 Bit of a waste of disk space, though, and you would also need to add a term to the where clauses to boot. 但是,有点浪费磁盘空间,而且还需要在where子句中添加一个术语来启动。

In addition to Erwin's answer about the details of the how the planner works with partitions, there is a larger issue here. 除了Erwin关于规划器如何使用分区的细节的答案之外,这里还有一个更大的问题。

Partitioning is not a magic bullet. 分区不是一个神奇的子弹。 There are a handful of very specific things for which partitioning is very useful. 有一些非常具体的事情,分区非常有用。 If none of those very specific things apply to you, then you cannot expect a performance improvement from partitioning, and most likely will get a decrease instead. 如果这些非常具体的事情都不适用于您,那么您不能指望通过分区提高性能,而且很可能会降低性能。

To do partitioning correctly, you need a thorough understanding of your usage patterns, or your data loading and unloading patterns. 要正确进行分区,您需要全面了解您的使用模式或数据加载和卸载模式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM