单个项目的索引使用 ANY(ARRAY[...])

Index usage with single item ANY(ARRAY[...])

测试table和索引(PostgreSQL 9.5.3):

CREATE TABLE public.t (id serial, a integer, b integer);

INSERT INTO t(a, b) 
SELECT round(random()*1000), round(random()*1000)
FROM generate_series(1, 1000000);

CREATE INDEX "i_1" ON public.t USING btree (a, b);
CREATE INDEX "i_2" ON public.t USING btree (b);

如果在第一个查询中"a = 50",一切正常,使用了适当的索引"i_1":

SELECT * FROM t WHERE a = 50 ORDER BY b LIMIT 1

"Limit  (cost=0.42..4.03 rows=1 width=12) (actual time=0.085..0.085 rows=1 loops=1)"
"  Buffers: shared hit=1 read=3"
"  ->  Index Scan using i_1 on t  (cost=0.42..4683.12 rows=1300 width=12) (actual time=0.084..0.084 rows=1 loops=1)"
"        Index Cond: (a = 50)"
"        Buffers: shared hit=1 read=3"
"Planning time: 0.637 ms"
"Execution time: 0.114 ms"

"a IN (50)"结果相同:

SELECT * FROM t WHERE a IN (50) ORDER BY b LIMIT 1

"Limit  (cost=0.42..4.03 rows=1 width=12) (actual time=0.058..0.058 rows=1 loops=1)"
"  Buffers: shared hit=4"
"  ->  Index Scan using i_1 on t  (cost=0.42..4683.12 rows=1300 width=12) (actual time=0.056..0.056 rows=1 loops=1)"
"        Index Cond: (a = 50)"
"        Buffers: shared hit=4"
"Planning time: 0.287 ms"
"Execution time: 0.105 ms"

问题出在我尝试使用 "a = ANY(ARRAY[50])" 时。使用了错误的索引 "i_2" 而不是 "i_1" 并且执行时间变长了 x25:

SELECT * FROM t WHERE a = ANY(ARRAY[50]) ORDER BY b LIMIT 1

"Limit  (cost=0.42..38.00 rows=1 width=12) (actual time=2.591..2.591 rows=1 loops=1)"
"  Buffers: shared hit=491 read=4"
"  ->  Index Scan using i_2 on t  (cost=0.42..48853.65 rows=1300 width=12) (actual time=2.588..2.588 rows=1 loops=1)"
"        Filter: (a = ANY ('{50}'::integer[]))"
"        Rows Removed by Filter: 520"
"        Buffers: shared hit=491 read=4"
"Planning time: 0.251 ms"
"Execution time: 2.627 ms"

你可以说:"PostgreSQL can't use index if you use ANY(ARRAY[])",但实际上可以。如果我删除 "ORDER BY" 它再次工作:

SELECT * FROM t WHERE a = ANY(ARRAY[50]) LIMIT 1

"Limit  (cost=0.42..4.03 rows=1 width=12) (actual time=0.034..0.034 rows=1 loops=1)"
"  Buffers: shared hit=4"
"  ->  Index Scan using i_1 on t  (cost=0.42..4683.12 rows=1300 width=12) (actual time=0.033..0.033 rows=1 loops=1)"
"        Index Cond: (a = ANY ('{50}'::integer[]))"
"        Buffers: shared hit=4"
"Planning time: 0.182 ms"
"Execution time: 0.090 ms"

我的问题:

  1. 如果 PostgreSQL 足够聪明,可以很好地与 "IN" 一起工作,那么 ANY(ARRAY[]) 有什么问题?

  2. 如果我删除 "ORDER BY" 子句,为什么它与 ANY(ARRAY[]) 一起工作?

PostgreSQL 不够聪明,无法弄清楚 a =ANY(ARRAY[50])a = 50 相同。
它不检查数组是否只包含一个元素。

IN 列表的处理方式如下(参见 src/backend/parser/parse_expr.c 中的 transformAExprIn):

  1. 检查 IN 列表的所有元素是否为常量(参见 here)。

  2. 如果有多个常量,并且它们都可以被强制转换为同一类型,则构建一个 =ANY 表达式(参见 here)。

  3. 来自 2 的表达式(如果有)和 IN 列表的其余元素被构建到 OR 表达式中(参见 here)。

因此,例如表达式 x IN (1, 2, mycol) 将以 x = ANY ('{2,3}'::integer[])) OR (x = mycol)) 结尾,而 x IN (42) 将变为 x = 42(没有 OR,因为列表只有一个元素)。

另一方面,=ANY 表达式在 src/backend/parser/parse_oper.c 中的 make_scalar_array_op 中处理。没有尝试专门处理单元素数组。

PostgreSQL 可以=ANY条件使用索引扫描,但除非数组只有一个元素,否则结果不会被排序by b,所以它必须对结果进行排序。此外,由于索引扫描在第一个结果后无法停止,因此将不得不扫描更多行。

要了解为什么不会对这样的结果进行排序,请考虑以下示例:

这是索引的一部分:

 a  |  b
----+----
... | ...
 49 | 812
 50 |   1
 50 |   2
 50 | 595
 50 | 973
 51 |   5
 52 |  80
 52 | 991
 55 |  27
... | ...

现在,如果我们扫描 a =ANY(ARRAY[50,51]) 的索引,找到的 b 的值将依次为 1、2、595、973、5、80 和 991。如果我们必须 return 与 ORDER BY b 的结果,我们需要额外的排序。唯一的例外是数组只包含一个元素,但 PostgreSQL 不会专门检查它。

所以PostgreSQL选择扫描其他索引,因为它估计按排序顺序搜索table,遇到第一个符合=ANY条件的行时停止会更便宜。

如果删除 ORDER BY 子句,PostgreSQL 在这两种情况下都可以在第一次命中后停止扫描,并且使用第一个索引更便宜。