PL/pgSQL 查询计划内部函数比外部函数更差
PL/pgSQL Query Plan Worse Inside Function Than Outside
我有一个功能运行太慢了。我已经隔离了哪个功能很慢..一个小的SELECT
声明:
SELECT image_group_id
FROM programs.image_family fam
JOIN programs.provider_file pf
ON (fam.provider_data_id = pf.provider_data_id
AND fam.family_id = AND pf.image_group_id IS NOT NULL)
LIMIT 1
当我运行这段SQL函数生成如下查询计划:
Query Text: SELECT image_group_id FROM programs.image_family fam JOIN programs.provider_file pf ON (fam.provider_data_id = pf.provider_data_id AND fam.family_id = AND pf.image_group_id IS NOT NULL) LIMIT 1
Limit (cost=0.56..6.75 rows=1 width=6) (actual time=3471.004..3471.004 rows=0 loops=1)
-> Nested Loop (cost=0.56..594054.42 rows=96017 width=6) (actual time=3471.002..3471.002 rows=0 loops=1)
-> Seq Scan on image_family fam (cost=0.00..391880.08 rows=96023 width=6) (actual time=3471.001..3471.001 rows=0 loops=1)
Filter: ((family_id)::numeric = '8419853'::numeric)
Rows Removed by Filter: 19204671
-> Index Scan using "IX_DBO_PROVIDER_FILE_1" on provider_file pf (cost=0.56..2.11 rows=1 width=12) (never executed)
Index Cond: (provider_data_id = fam.provider_data_id)
Filter: (image_group_id IS NOT NULL)
当我 运行 在查询工具(函数外)中选择查询时,查询计划如下所示:
Limit (cost=1.12..3.81 rows=1 width=6) (actual time=0.043..0.043 rows=1 loops=1)
Output: pf.image_group_id
Buffers: shared hit=11
-> Nested Loop (cost=1.12..14.55 rows=5 width=6) (actual time=0.041..0.041 rows=1 loops=1)
Output: pf.image_group_id
Inner Unique: true
Buffers: shared hit=11
-> Index Only Scan using image_family_family_id_provider_data_id_idx on programs.image_family fam (cost=0.56..1.65 rows=5 width=6) (actual time=0.024..0.024 rows=1 loops=1)
Output: fam.family_id, fam.provider_data_id
Index Cond: (fam.family_id = 8419853)
Heap Fetches: 2
Buffers: shared hit=6
-> Index Scan using "IX_DBO_PROVIDER_FILE_1" on programs.provider_file pf (cost=0.56..2.58 rows=1 width=12) (actual time=0.013..0.013 rows=1 loops=1)
Output: pf.provider_data_id, pf.provider_file_path, pf.posted_dt, pf.file_repository_id, pf.restricted_size, pf.image_group_id, pf.is_master, pf.is_biggest
Index Cond: (pf.provider_data_id = fam.provider_data_id)
Filter: (pf.image_group_id IS NOT NULL)
Buffers: shared hit=5
Planning time: 0.809 ms
Execution time: 0.100 ms
如果我在函数中禁用序列扫描,我可以获得类似的查询计划:
Query Text: SELECT image_group_id FROM programs.image_family fam JOIN programs.provider_file pf ON (fam.provider_data_id = pf.provider_data_id AND fam.family_id = AND pf.image_group_id IS NOT NULL) LIMIT 1
Limit (cost=1.12..8.00 rows=1 width=6) (actual time=3855.722..3855.722 rows=0 loops=1)
-> Nested Loop (cost=1.12..660217.34 rows=96017 width=6) (actual time=3855.721..3855.721 rows=0 loops=1)
-> Index Only Scan using image_family_family_id_provider_data_id_idx on image_family fam (cost=0.56..458043.00 rows=96023 width=6) (actual time=3855.720..3855.720 rows=0 loops=1)
Filter: ((family_id)::numeric = '8419853'::numeric)
Rows Removed by Filter: 19204671
Heap Fetches: 368
-> Index Scan using "IX_DBO_PROVIDER_FILE_1" on provider_file pf (cost=0.56..2.11 rows=1 width=12) (never executed)
Index Cond: (provider_data_id = fam.provider_data_id)
Filter: (image_group_id IS NOT NULL)
查询计划不同,过滤器函数用于仅索引扫描。该函数有更多 Heap Fetches
并且似乎将参数视为转换为 numeric
.
的字符串
我尝试过的事情:
- 增加统计(和运行宁vacuum/analyze)
- 用语言
SQL
在另一个函数中调用有问题的 SQL
- 添加另一个索引(现在用于执行 INDEX ONLY 扫描的索引)
- 为 image_family table 创建一个 CTE(这确实有助于提高性能,但仍然会在 image_family 上进行序列扫描,而不是使用索引,所以太慢了)
- 从执行原始 SQL 更改为在函数中使用
EXECUTE ... INTO .. USING
。
两个 table 的构成:
image_family:
provider_data_id: numeric(16)
family_id: int4
(为简洁起见省略其余部分)
provider_data_id
上的唯一索引
索引 family_id
我最近也在 (family_id, provider_data_id)
上添加了一个唯一索引
这里大约有 2000 万行。家庭有很多 provider_data_ids,但并非所有 provider_data_ids 都是家庭的一部分,因此并非全部都在这个 table.
中
provider_file:
provider_data_id numeric(16)
image_group_id numeric(16)
(为简洁起见省略其余部分)
provider_data_id
上的唯一索引
此 table 中大约有 3200 万行。大多数行 (> 95%) 都有一个非空 image_group_id
.
Postgres 版本 10
如何获得查询性能以匹配我是从函数调用它还是在查询工具中作为原始 SQL 调用它?
问题出现在这一行:
Filter: ((family_id)::numeric = '8419853'::numeric)
无法使用 family_id
上的索引,因为 family_id
与 numeric
值进行比较。这需要转换为 numeric
,并且 family_id::numeric
.
上没有索引
虽然integer
和numeric
都是表示数字的类型,但它们的内部表示却大不相同,因此索引不兼容。换句话说,转换为 numeric
就像 PostgreSQL 的一个函数,并且由于它在该函数表达式上没有索引,所以它必须求助于整个 table(或索引)的扫描。
不过,解决方案很简单:使用 integer
而不是 numeric
参数进行查询。如有疑问,请使用像
这样的演员表
fam.family_id = ::integer
我有一个功能运行太慢了。我已经隔离了哪个功能很慢..一个小的SELECT
声明:
SELECT image_group_id
FROM programs.image_family fam
JOIN programs.provider_file pf
ON (fam.provider_data_id = pf.provider_data_id
AND fam.family_id = AND pf.image_group_id IS NOT NULL)
LIMIT 1
当我运行这段SQL函数生成如下查询计划:
Query Text: SELECT image_group_id FROM programs.image_family fam JOIN programs.provider_file pf ON (fam.provider_data_id = pf.provider_data_id AND fam.family_id = AND pf.image_group_id IS NOT NULL) LIMIT 1
Limit (cost=0.56..6.75 rows=1 width=6) (actual time=3471.004..3471.004 rows=0 loops=1)
-> Nested Loop (cost=0.56..594054.42 rows=96017 width=6) (actual time=3471.002..3471.002 rows=0 loops=1)
-> Seq Scan on image_family fam (cost=0.00..391880.08 rows=96023 width=6) (actual time=3471.001..3471.001 rows=0 loops=1)
Filter: ((family_id)::numeric = '8419853'::numeric)
Rows Removed by Filter: 19204671
-> Index Scan using "IX_DBO_PROVIDER_FILE_1" on provider_file pf (cost=0.56..2.11 rows=1 width=12) (never executed)
Index Cond: (provider_data_id = fam.provider_data_id)
Filter: (image_group_id IS NOT NULL)
当我 运行 在查询工具(函数外)中选择查询时,查询计划如下所示:
Limit (cost=1.12..3.81 rows=1 width=6) (actual time=0.043..0.043 rows=1 loops=1)
Output: pf.image_group_id
Buffers: shared hit=11
-> Nested Loop (cost=1.12..14.55 rows=5 width=6) (actual time=0.041..0.041 rows=1 loops=1)
Output: pf.image_group_id
Inner Unique: true
Buffers: shared hit=11
-> Index Only Scan using image_family_family_id_provider_data_id_idx on programs.image_family fam (cost=0.56..1.65 rows=5 width=6) (actual time=0.024..0.024 rows=1 loops=1)
Output: fam.family_id, fam.provider_data_id
Index Cond: (fam.family_id = 8419853)
Heap Fetches: 2
Buffers: shared hit=6
-> Index Scan using "IX_DBO_PROVIDER_FILE_1" on programs.provider_file pf (cost=0.56..2.58 rows=1 width=12) (actual time=0.013..0.013 rows=1 loops=1)
Output: pf.provider_data_id, pf.provider_file_path, pf.posted_dt, pf.file_repository_id, pf.restricted_size, pf.image_group_id, pf.is_master, pf.is_biggest
Index Cond: (pf.provider_data_id = fam.provider_data_id)
Filter: (pf.image_group_id IS NOT NULL)
Buffers: shared hit=5
Planning time: 0.809 ms
Execution time: 0.100 ms
如果我在函数中禁用序列扫描,我可以获得类似的查询计划:
Query Text: SELECT image_group_id FROM programs.image_family fam JOIN programs.provider_file pf ON (fam.provider_data_id = pf.provider_data_id AND fam.family_id = AND pf.image_group_id IS NOT NULL) LIMIT 1
Limit (cost=1.12..8.00 rows=1 width=6) (actual time=3855.722..3855.722 rows=0 loops=1)
-> Nested Loop (cost=1.12..660217.34 rows=96017 width=6) (actual time=3855.721..3855.721 rows=0 loops=1)
-> Index Only Scan using image_family_family_id_provider_data_id_idx on image_family fam (cost=0.56..458043.00 rows=96023 width=6) (actual time=3855.720..3855.720 rows=0 loops=1)
Filter: ((family_id)::numeric = '8419853'::numeric)
Rows Removed by Filter: 19204671
Heap Fetches: 368
-> Index Scan using "IX_DBO_PROVIDER_FILE_1" on provider_file pf (cost=0.56..2.11 rows=1 width=12) (never executed)
Index Cond: (provider_data_id = fam.provider_data_id)
Filter: (image_group_id IS NOT NULL)
查询计划不同,过滤器函数用于仅索引扫描。该函数有更多 Heap Fetches
并且似乎将参数视为转换为 numeric
.
我尝试过的事情:
- 增加统计(和运行宁vacuum/analyze)
- 用语言
SQL
在另一个函数中调用有问题的 SQL
- 添加另一个索引(现在用于执行 INDEX ONLY 扫描的索引)
- 为 image_family table 创建一个 CTE(这确实有助于提高性能,但仍然会在 image_family 上进行序列扫描,而不是使用索引,所以太慢了)
- 从执行原始 SQL 更改为在函数中使用
EXECUTE ... INTO .. USING
。
两个 table 的构成:
image_family:
provider_data_id: numeric(16)
family_id: int4
(为简洁起见省略其余部分)
provider_data_id
上的唯一索引
索引
family_id
我最近也在
(family_id, provider_data_id)
上添加了一个唯一索引
这里大约有 2000 万行。家庭有很多 provider_data_ids,但并非所有 provider_data_ids 都是家庭的一部分,因此并非全部都在这个 table.
中provider_file:
provider_data_id numeric(16)
image_group_id numeric(16)
(为简洁起见省略其余部分)
provider_data_id
上的唯一索引
此 table 中大约有 3200 万行。大多数行 (> 95%) 都有一个非空 image_group_id
.
Postgres 版本 10
如何获得查询性能以匹配我是从函数调用它还是在查询工具中作为原始 SQL 调用它?
问题出现在这一行:
Filter: ((family_id)::numeric = '8419853'::numeric)
无法使用 family_id
上的索引,因为 family_id
与 numeric
值进行比较。这需要转换为 numeric
,并且 family_id::numeric
.
虽然integer
和numeric
都是表示数字的类型,但它们的内部表示却大不相同,因此索引不兼容。换句话说,转换为 numeric
就像 PostgreSQL 的一个函数,并且由于它在该函数表达式上没有索引,所以它必须求助于整个 table(或索引)的扫描。
不过,解决方案很简单:使用 integer
而不是 numeric
参数进行查询。如有疑问,请使用像
fam.family_id = ::integer