表t
有两个索引:
create table t (a int, b int);
create type int_pair as (a int, b int);
create index t_row_idx on t (((a,b)::int_pair));
create index t_a_b_idx on t (a,b);
insert into t (a,b)
select i, i
from generate_series(1, 100000) g(i)
;
运算符不使用索引ANY
:
explain analyze
select *
from t
where (a,b) = any(array[(1,1),(1,2)])
;
QUERY PLAN
---------------------------------------------------------------------------------------------------
Seq Scan on t (cost=0.00..1693.00 rows=1000 width=8) (actual time=0.042..126.789 rows=1 loops=1)
Filter: (ROW(a, b) = ANY (ARRAY[ROW(1, 1), ROW(1, 2)]))
Rows Removed by Filter: 99999
Planning time: 0.122 ms
Execution time: 126.836 ms
但其中之一与IN
运算符一起使用:
explain analyze
select *
from t
where (a,b) in ((1,1),(1,2))
;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------
Index Only Scan using t_a_b_idx on t (cost=0.29..8.32 rows=1 width=8) (actual time=0.028..0.029 rows=1 loops=1)
Index Cond: (a = 1)
Filter: ((b = 1) OR (b = 2))
Heap Fetches: 1
Planning time: 0.161 ms
Execution time: 0.066 ms
如果记录被转换为正确的类型,它将使用记录索引:
explain analyze
select *
from t
where (a,b)::int_pair = any(array[row(1,1),row(1,2)])
;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Index Scan using t_row_idx on t (cost=0.42..12.87 rows=2 width=8) (actual time=0.106..0.126 rows=1 loops=1)
Index Cond: (ROW(a, b)::int_pair = ANY (ARRAY[ROW(1, 1), ROW(1, 2)]))
Planning time: 0.208 ms
Execution time: 0.203 ms
为什么规划器不为操作员使用非记录索引,ANY
因为它为IN
操作员使用它?
在内部,有两种不同形式的
IN
,也有两种不同形式的ANY
构造。每个中的一个,取一个set,等效于另一个,并且还导致与可以使用普通索引
expr IN (<set>)
的查询计划相同的查询计划。expr = ANY(<set>)
细节:因此,以下两个查询是等效的,并且都可以使用普通索引(如果您试图让您的查询使用索引
t_a_b_idx
,这也可以是解决方案):或者:
两者相同:
但是,这不能轻易地传递给函数,因为 Postgres 中没有“表变量”。这导致了开始这个话题的问题:
该问题有多种解决方法。一个是我在那里添加的替代答案。其他一些:
每个的第二种形式是不同的:
ANY
接受一个实际的数组,而IN
接受一个逗号分隔的值列表。这对输入输入有不同的影响。正如我们在
EXPLAIN
问题的输出中看到的那样,这种形式:被视为以下的简写:
并比较实际的 ROW 值。Postgres 目前还不够聪明,无法看到复合类型上的索引
t_row_idx
是否适用。它也没有意识到简单索引t_a_b_idx
也应该适用。显式转换有助于克服这种缺乏智能的问题:
转换正确的操作数 (
::int_pair[]
) 是可选的(尽管为了性能和避免歧义更可取)。一旦左操作数具有众所周知的类型,右操作数就会从“匿名记录”强制转换为匹配类型。只有这样,才能明确定义运算符。Postgres 根据运算符和左操作数选择适用的索引。对于定义 aCOMMUTATOR
的许多运算符,查询计划器可以翻转操作数以将索引表达式带到左侧。但这对于构造是不可能的ANY
。有关的:
有没有办法有效地索引包含正则表达式模式的文本列?
.. 值被视为元素,Postgres 能够比较单个整数值,正如我们在
EXPLAIN
输出中再次看到的那样:因此 Postgres 发现
t_a_b_idx
可以使用简单索引。因此,对于示例中的特定情况,将有另一种解决方案:由于示例中的自定义复合类型
int_pair
恰好等同于表t
本身的行类型,我们可以简化:更短的等效语法:
但第一种变体更安全。如果应该存在同名的列,则第二个变体将解析为该列。
然后此查询将使用索引而无需任何更显式的强制转换:
但是典型的用例将无法利用隐式存在的表行类型。