索引分区表上的查询计划。避免顺序扫描
Posted
技术标签:
【中文标题】索引分区表上的查询计划。避免顺序扫描【英文标题】:Query plan on indexed partitioned table. Avoid sequential scan 【发布时间】:2019-07-17 17:27:27 【问题描述】:在 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)
小米查看代码:
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
版本(这是对另一个很小的表的真正查询),但它的计划非常不同而且更糟。
QUERY PLAN 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(行集,慢):
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
如何在类似2
的查询中使用计划1
?
【问题讨论】:
你不能。修剪分区发生在编译时。子查询的结果在编译时是未知的,因此查询需要查看所有分区。 这在 Postgres 11 中得到了改进,在 Postgres 12 中甚至会更好 【参考方案1】:您可以在 PostgreSQL v10 中解决您的问题,将 WHERE 条件的输入包装为一个 IMMUTABLE plpgsql 函数,该函数返回一个整数数组。根据定义,IMMUTABLE plpgsql 函数 "(...) 允许优化器在查询使用常量参数 (...)" (https://www.postgresql.org/docs/10/xfunc-volatility.html) 调用该函数时预先评估该函数。
这个解决方案应该可以工作。
例子:
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;
【讨论】:
它运行得很好!谢谢。【参考方案2】:问题在于您使用的是 PostgreSQL v10,其中 分区修剪 仅在计划时发生。
在您的第一个查询中,条件是一个常数,因此可以进行分区修剪。在第二种情况下,它是一个子查询结果(没有展平),所以它不起作用。
对两个查询运行 EXPLAIN
以查看差异。
您应该使用 PostgreSQL v11,其中分区修剪也可以在查询执行时进行。
【讨论】:
以上是关于索引分区表上的查询计划。避免顺序扫描的主要内容,如果未能解决你的问题,请参考以下文章