索引分区表上的查询计划。避免顺序扫描

时间:2019-07-17 17:27:27

标签: sql postgresql

在postgres 10.1服务器中,我有一个很大的表,该表按列表值分区,而视图仅按分区列过滤该表。

使用视图时,计划者没有给我最好的计划,我的意思是仅扫描选定的子表。 相反,它总是扫描父表的所有分区。

我已经通过分区列和约束工具创建了索引。 DDL:


                                  Table "parted_mob_matrix"
    Column    |         Type          | Collation | Nullable | Default | Storage  | Stats target | Description 
--------------+-----------------------+-----------+----------+---------+----------+--------------+-------------
 id           | integer               |           | not null |         | plain    |              | 
 delivery_id  | integer               |           |          |         | Partition key: LIST (delivery_id)
Partitions: parted_mob_matrix_delivery_0 FOR VALUES IN (0),
            parted_mob_matrix_delivery_1 FOR VALUES IN (1),
            parted_mob_matrix_delivery_10 FOR VALUES IN (10),
            ....
            parted_mob_matrix_delivery_10 FOR VALUES IN (620),


                            Table "parted_mob_matrix_delivery_620"
    Column    |         Type          | Collation | Nullable | Default | Storage  | Stats target | Description 
--------------+-----------------------+-----------+----------+---------+----------+--------------+-------------
 id           | integer               |           | not null |         | plain    |              | 
 delivery_id  | integer               |           |          |         | plain    |              | 
Partition of: parted_mob_matrix FOR VALUES IN (620)
Partition constraint: ((delivery_id IS NOT NULL) AND (delivery_id = ANY (ARRAY[620])))
Indexes:
    "parted_mob_matrix_delivery_620_delivery_id_idx" btree (delivery_id)
Check constraints:
    "parted_mob_matrix_delivery_620_check_delivery" CHECK (delivery_id = 620)

Mi查看代码:

EXPLAIN SELECT
  parted_mob_matrix.*
FROM
  parted_mob_matrix
1) where parted_mob_matrix.delivery_id in (620)
2) where parted_mob_matrix.delivery_id in (select 620)

我需要在这里使用简化的2版本(这是对另一个很小的表的真实查询),但是它的计划却大不相同,甚至更糟。

查询计划1(效果不错):

Append  (cost=0.00..78308.11 rows=758031 width=738)

  ->  Seq Scan on parted_mob_matrix_delivery_620  (cost=0.00..78308.11 rows=758031 width=738)

        Filter: (delivery_id = 620)

查询计划2(rowset,慢):


Hash Semi Join  (cost=0.01..25077311.20 rows=7539693 width=860)

  Hash Cond: (parted_mob_matrix_delivery_0.delivery_id = (620))

  ->  Append  (cost=0.00..24942162.20 rows=211111399 width=859)

        ->  Seq Scan on parted_mob_matrix_delivery_0  (cost=0.00..10.75 rows=250 width=294)

        ->  Seq Scan on parted_mob_matrix_delivery_1  (cost=0.00..10.75 rows=250 width=294)

 -- All the child tables

        ->  Seq Scan on parted_mob_matrix_delivery_620  (cost=0.00..77929.09 rows=758031 width=738)

 -- All the child tables are scanned

如何在类似1的查询中使用计划2

2 个答案:

答案 0 :(得分:3)

您可以在PostgreSQL v10中解决问题,将WHERE条件的输入包装为IMMUTABLE plpgsql函数,该函数返回整数数组。根据定义,当查询使用常量参数(...)“ (https://www.postgresql.org/docs/10/xfunc-volatility.html进行调用时,IMMUTABLE plpgsql函数”(...)允许优化器预先评估该函数。

此解决方案应该有效。

示例:

SELECT
  parted_mob_matrix.*
FROM
  parted_mob_matrix
WHERE parted_mob_matrix.delivery_id = ANY(get_deliveries('cod_011'))

您可以使用的功能:

CREATE OR REPLACE FUNCTION get_deliveries(
    high_level_id TEXT
)
RETURNS INTEGER[]
AS $BODY$
DECLARE
    _delivery_ids INTEGER[];
BEGIN
  EXECUTE format(
    $$
    SELECT ARRAY_AGG(delivery_id)
    FROM
        your_table_with_all_delivery_ids
    WHERE
        high_level_id = '%1$s'
    ;
    $$, high_level_id
  ) INTO _delivery_ids;
  RETURN _delivery_ids;
END;
$BODY$
LANGUAGE plpgsql IMMUTABLE;

答案 1 :(得分:2)

问题是您使用的是PostgreSQL v10,其中分区修剪仅在计划时间进行。

在您的第一个查询中,条件是一个常量,因此可以进行分区修剪。在第二种情况下,它是一个子查询结果(未展平),因此它将不起作用。

在两个查询上运行EXPLAIN来查看差异。

您应该使用PostgreSQL v11,在该分区上也可以在查询执行时进行分区修剪。