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.

的字符串

我尝试过的事情:

  1. 增加统计(和运行宁vacuum/analyze)
  2. 用语言 SQL
  3. 在另一个函数中调用有问题的 SQL
  4. 添加另一个索引(现在用于执行 INDEX ONLY 扫描的索引)
  5. 为 image_family table 创建一个 CTE(这确实有助于提高性能,但仍然会在 image_family 上进行序列扫描,而不是使用索引,所以太慢了)
  6. 从执行原始 SQL 更改为在函数中使用 EXECUTE ... INTO .. USING

两个 table 的构成:

image_family:

provider_data_id: numeric(16)
family_id:        int4

(为简洁起见省略其余部分)

这里大约有 2000 万行。家庭有很多 provider_data_ids,但并非所有 provider_data_ids 都是家庭的一部分,因此并非全部都在这个 table.

provider_file:

provider_data_id numeric(16)
image_group_id   numeric(16)

(为简洁起见省略其余部分)

此 table 中大约有 3200 万行。大多数行 (> 95%) 都有一个非空 image_group_id.

Postgres 版本 10

如何获得查询性能以匹配我是从函数调用它还是在查询工具中作为原始 SQL 调用它?

问题出现在这一行:

Filter: ((family_id)::numeric = '8419853'::numeric)

无法使用 family_id 上的索引,因为 family_idnumeric 值进行比较。这需要转换为 numeric,并且 family_id::numeric.

上没有索引

虽然integernumeric都是表示数字的类型,但它们的内部表示却大不相同,因此索引不兼容。换句话说,转换为 numeric 就像 PostgreSQL 的一个函数,并且由于它在该函数表达式上没有索引,所以它必须求助于整个 table(或索引)的扫描。

不过,解决方案很简单:使用 integer 而不是 numeric 参数进行查询。如有疑问,请使用像

这样的演员表
fam.family_id = ::integer