Postgresql 何时对 JOIN 列进行分区修剪?
When does Postgresql do partition pruning with JOIN columns?
我在 Postgres 11 数据库中有两个 table:
client table
--------
client_id integer
client_name character_varying
file table
--------
file_id integer
client_id integer
file_name character_varying
客户端table未分区,文件table被client_id分区(按列表分区)。将新客户端插入客户端 table 时,触发器会为文件 table 创建一个新分区。
文件 table 在 client_id.
上有引用客户端 table 的外键约束
当我执行此 SQL(其中 c.client_id = 1)时,一切似乎都很好:
explain
select *
from client c
join file f using (client_id)
where c.client_id = 1;
使用分区修剪,只扫描分区file_p1:
Nested Loop (cost=0.00..3685.05 rows=100001 width=82)
-> Seq Scan on client c (cost=0.00..1.02 rows=1 width=29)
Filter: (client_id = 1)
-> Append (cost=0.00..2684.02 rows=100001 width=57)
-> Seq Scan on file_p1 f (cost=0.00..2184.01 rows=100001 width=57)
Filter: (client_id = 1)
但是当我使用像 "where c.client_name = 'test'" 这样的 where 子句时,数据库会扫描所有分区并且无法识别 client_name "test" 等于 client_id 1 :
explain
select *
from client c
join file f using (client_id)
where c.client_name = 'test';
执行计划:
Hash Join (cost=1.04..6507.57 rows=100001 width=82)
Hash Cond: (f.client_id = c.client_id)
-> Append (cost=0.00..4869.02 rows=200002 width=57)
-> Seq Scan on file_p1 f (cost=0.00..1934.01 rows=100001 width=57)
-> Seq Scan on file_p4 f_1 (cost=0.00..1934.00 rows=100000 width=57)
-> Seq Scan on file_pdefault f_2 (cost=0.00..1.00 rows=1 width=556)
-> Hash (cost=1.02..1.02 rows=1 width=29)
-> Seq Scan on client c (cost=0.00..1.02 rows=1 width=29)
Filter: ((name)::text = 'test'::text)
所以对于这个SQL,扫描文件-table中的所有分区。
那么每个 select 都应该使用 table 分区依据的列吗?数据库是否不能偏离分区修剪标准?
编辑:
要添加一些信息:
过去,我大部分时间都在使用 Oracle 数据库。
那里的执行计划应该是
- 使用客户端名称对客户端 table 进行全面 table 扫描,找出 client_id。
- 对文件 table 进行 "PARTITION LIST" 访问,其中 SQL 开发人员声明 PARTITION_START = KEY 和 PARTITION_STOP = KEY 以指示确切的分区在计算执行计划时是未知的,但是将只访问分区列表,这些分区是根据客户端 table.
中找到的 client_id 计算的
这也是我在 Postgresql 中所期望的。
The documentation 指出动态分区修剪是可能的
(...) During actual execution of the query plan. Partition pruning may also be performed here to remove partitions using values which are only known during actual query execution. This includes values from subqueries and values from execution-time parameters such as those from parameterized nested loop joins.
如果我理解正确,它适用于准备好的语句或带有提供分区键值作为参数的子查询的查询。使用explain analyse
查看动态剪枝(我的示例数据在三个分区中包含一百万行):
explain analyze
select *
from file
where client_id = (
select client_id
from client
where client_name = 'test');
Append (cost=25.88..22931.88 rows=1000000 width=14) (actual time=0.091..96.139 rows=333333 loops=1)
InitPlan 1 (returns [=10=])
-> Seq Scan on client (cost=0.00..25.88 rows=6 width=4) (actual time=0.040..0.042 rows=1 loops=1)
Filter: (client_name = 'test'::text)
Rows Removed by Filter: 2
-> Seq Scan on file_p1 (cost=0.00..5968.66 rows=333333 width=14) (actual time=0.039..70.026 rows=333333 loops=1)
Filter: (client_id = [=10=])
-> Seq Scan on file_p2 (cost=0.00..5968.68 rows=333334 width=14) (never executed)
Filter: (client_id = [=10=])
-> Seq Scan on file_p3 (cost=0.00..5968.66 rows=333333 width=14) (never executed)
Filter: (client_id = [=10=])
Planning Time: 0.423 ms
Execution Time: 109.189 ms
请注意,对分区 p2 和 p3 的扫描是 never executed
。
回答你的确切问题,问题中描述的连接查询中的分区修剪没有在 Postgres 中实现(还没有?)
我在 Postgres 11 数据库中有两个 table:
client table
--------
client_id integer
client_name character_varying
file table
--------
file_id integer
client_id integer
file_name character_varying
客户端table未分区,文件table被client_id分区(按列表分区)。将新客户端插入客户端 table 时,触发器会为文件 table 创建一个新分区。 文件 table 在 client_id.
上有引用客户端 table 的外键约束当我执行此 SQL(其中 c.client_id = 1)时,一切似乎都很好:
explain
select *
from client c
join file f using (client_id)
where c.client_id = 1;
使用分区修剪,只扫描分区file_p1:
Nested Loop (cost=0.00..3685.05 rows=100001 width=82)
-> Seq Scan on client c (cost=0.00..1.02 rows=1 width=29)
Filter: (client_id = 1)
-> Append (cost=0.00..2684.02 rows=100001 width=57)
-> Seq Scan on file_p1 f (cost=0.00..2184.01 rows=100001 width=57)
Filter: (client_id = 1)
但是当我使用像 "where c.client_name = 'test'" 这样的 where 子句时,数据库会扫描所有分区并且无法识别 client_name "test" 等于 client_id 1 :
explain
select *
from client c
join file f using (client_id)
where c.client_name = 'test';
执行计划:
Hash Join (cost=1.04..6507.57 rows=100001 width=82)
Hash Cond: (f.client_id = c.client_id)
-> Append (cost=0.00..4869.02 rows=200002 width=57)
-> Seq Scan on file_p1 f (cost=0.00..1934.01 rows=100001 width=57)
-> Seq Scan on file_p4 f_1 (cost=0.00..1934.00 rows=100000 width=57)
-> Seq Scan on file_pdefault f_2 (cost=0.00..1.00 rows=1 width=556)
-> Hash (cost=1.02..1.02 rows=1 width=29)
-> Seq Scan on client c (cost=0.00..1.02 rows=1 width=29)
Filter: ((name)::text = 'test'::text)
所以对于这个SQL,扫描文件-table中的所有分区。
那么每个 select 都应该使用 table 分区依据的列吗?数据库是否不能偏离分区修剪标准?
编辑: 要添加一些信息:
过去,我大部分时间都在使用 Oracle 数据库。
那里的执行计划应该是
- 使用客户端名称对客户端 table 进行全面 table 扫描,找出 client_id。
- 对文件 table 进行 "PARTITION LIST" 访问,其中 SQL 开发人员声明 PARTITION_START = KEY 和 PARTITION_STOP = KEY 以指示确切的分区在计算执行计划时是未知的,但是将只访问分区列表,这些分区是根据客户端 table. 中找到的 client_id 计算的
这也是我在 Postgresql 中所期望的。
The documentation 指出动态分区修剪是可能的
(...) During actual execution of the query plan. Partition pruning may also be performed here to remove partitions using values which are only known during actual query execution. This includes values from subqueries and values from execution-time parameters such as those from parameterized nested loop joins.
如果我理解正确,它适用于准备好的语句或带有提供分区键值作为参数的子查询的查询。使用explain analyse
查看动态剪枝(我的示例数据在三个分区中包含一百万行):
explain analyze
select *
from file
where client_id = (
select client_id
from client
where client_name = 'test');
Append (cost=25.88..22931.88 rows=1000000 width=14) (actual time=0.091..96.139 rows=333333 loops=1)
InitPlan 1 (returns [=10=])
-> Seq Scan on client (cost=0.00..25.88 rows=6 width=4) (actual time=0.040..0.042 rows=1 loops=1)
Filter: (client_name = 'test'::text)
Rows Removed by Filter: 2
-> Seq Scan on file_p1 (cost=0.00..5968.66 rows=333333 width=14) (actual time=0.039..70.026 rows=333333 loops=1)
Filter: (client_id = [=10=])
-> Seq Scan on file_p2 (cost=0.00..5968.68 rows=333334 width=14) (never executed)
Filter: (client_id = [=10=])
-> Seq Scan on file_p3 (cost=0.00..5968.66 rows=333333 width=14) (never executed)
Filter: (client_id = [=10=])
Planning Time: 0.423 ms
Execution Time: 109.189 ms
请注意,对分区 p2 和 p3 的扫描是 never executed
。
回答你的确切问题,问题中描述的连接查询中的分区修剪没有在 Postgres 中实现(还没有?)