涉及视图和函数的查询不能使用索引

Query involving views and functions can't use index

我有以下 postgresql 查询(为便于阅读而简化):

select *
from a_view
where a in (select * from a_function(a_input))
      and b in (select * from b_function(b_input));

此查询的执行速度异常缓慢。

如果我运行 两个子查询都是独立的,它们会非常快。如果我 运行 查询写出子查询的输出,即:

select *
from a_view
where a in (394990, 393762, 393748, 1)
      and b in (331142, 330946, 331228, 331325);

这也相当快。我运行 explain analyze 和上面原来的形式实现,查询不能利用索引,使用顺序扫描。只是为了提供更多细节,视图 (a_view) 涉及一个大的 table (10+ 百万行)并且它在 (a,b) 和 (b) 上都建立了索引。

有没有办法帮助查询利用索引?

可能有两个问题:

  1. 默认情况下,任何 SRF 函数在 1000 上都有 ROWS 子句 - 计划者也期望如此。在你的例子中这是错误的。尝试将此属性设置为更合适的值(例如,10 - 太小也可能不好):

    postgres=# explain select * from xx();
    ┌───────────────────────────────────────────────────────────┐
    │                        QUERY PLAN                         │
    ╞═══════════════════════════════════════════════════════════╡
    │ Function Scan on xx  (cost=0.25..10.25 rows=1000 width=4) │
    └───────────────────────────────────────────────────────────┘
    (1 row)
    
  2. PLpgSQL 函数是规划器的 blackbox。与使用函数相比,如果您只使用常量列表,规划器将获得更多关于谓词的信息。在这种情况下,规划器必须使用一些默认规则,这些规则可能对您的情况来说太过分了。

    postgres=# explain select * from xx where a in (10,20);
    ┌────────────────────────────────────────────────────┐
    │                     QUERY PLAN                     │
    ╞════════════════════════════════════════════════════╡
    │ Seq Scan on xx  (cost=0.00..170.00 rows=2 width=4) │
    │   Filter: (a = ANY ('{10,20}'::integer[]))         │
    └────────────────────────────────────────────────────┘
    (2 rows)
    
    postgres=# explain select * from xx where a in (select * from xx());
    ┌──────────────────────────────────────────────────────────────────────────────────┐
    │                                    QUERY PLAN                                    │
    ╞══════════════════════════════════════════════════════════════════════════════════╡
    │ Hash Join  (cost=17.25..201.85 rows=5000 width=4)                                │
    │   Hash Cond: (xx.a = xx_1.xx)                                                    │
    │   ->  Seq Scan on xx  (cost=0.00..145.00 rows=10000 width=4)                     │
    │   ->  Hash  (cost=14.75..14.75 rows=200 width=4)                                 │
    │         ->  HashAggregate  (cost=12.75..14.75 rows=200 width=4)                  │
    │               Group Key: xx_1.xx                                                 │
    │               ->  Function Scan on xx xx_1  (cost=0.25..10.25 rows=1000 width=4) │
    └──────────────────────────────────────────────────────────────────────────────────┘
    (7 rows)
    

    我有两个可能相同的查询,但计划完全不同,而且性能可能也完全不同。

可以解决的问题:

  1. 不要这样做 - 在 SQL 查询的关键位置(主要是 WHERE 子句)使用 plpgsql 会产生相当大的负面影响。

  2. 您可以将函数重写为 return int[] 而不是 SETOF int。在这种情况下,规划器将使用不同的规则,性能会更好。

    postgres=# explain select * from xx where a = any( xx2());
    ┌──────────────────────────────────────────────────────┐
    │                      QUERY PLAN                      │
    ╞══════════════════════════════════════════════════════╡
    │ Seq Scan on xx  (cost=0.00..2770.00 rows=11 width=4) │
    │   Filter: (a = ANY (xx2()))                          │
    └──────────────────────────────────────────────────────┘
    (2 rows)
    
  3. 如果a_functionb_function的结果不依赖于内容ab,那么可以在通过在这些函数上设置标志 IMMUTABLE 来查询。然后在计划时间内评估函数并将结果用作常量 - 计划者将获得更多信息。 注意:如果前提条件为假,则结果可能是错误的。当心。

    -- xx2 is IMMUTABLE now
    postgres=# explain select * from xx where a = any( xx2());
    ┌───────────────────────────────────────────────────────┐
    │                      QUERY PLAN                       │
    ╞═══════════════════════════════════════════════════════╡
    │ Seq Scan on xx  (cost=0.00..182.50 rows=3 width=4)    │
    │   Filter: (a = ANY ('{30314,3783,70448}'::integer[])) │
    └───────────────────────────────────────────────────────┘
    (2 rows)