单个项目的索引使用 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"
我的问题:
如果 PostgreSQL 足够聪明,可以很好地与 "IN" 一起工作,那么 ANY(ARRAY[]) 有什么问题?
如果我删除 "ORDER BY" 子句,为什么它与 ANY(ARRAY[]) 一起工作?
PostgreSQL 不够聪明,无法弄清楚 a =ANY(ARRAY[50])
与 a = 50
相同。
它不检查数组是否只包含一个元素。
IN
列表的处理方式如下(参见 src/backend/parser/parse_expr.c
中的 transformAExprIn
):
检查 IN
列表的所有元素是否为常量(参见 here)。
如果有多个常量,并且它们都可以被强制转换为同一类型,则构建一个 =ANY
表达式(参见 here)。
来自 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 在这两种情况下都可以在第一次命中后停止扫描,并且使用第一个索引更便宜。
测试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"
我的问题:
如果 PostgreSQL 足够聪明,可以很好地与 "IN" 一起工作,那么 ANY(ARRAY[]) 有什么问题?
如果我删除 "ORDER BY" 子句,为什么它与 ANY(ARRAY[]) 一起工作?
PostgreSQL 不够聪明,无法弄清楚 a =ANY(ARRAY[50])
与 a = 50
相同。
它不检查数组是否只包含一个元素。
IN
列表的处理方式如下(参见 src/backend/parser/parse_expr.c
中的 transformAExprIn
):
检查
IN
列表的所有元素是否为常量(参见 here)。如果有多个常量,并且它们都可以被强制转换为同一类型,则构建一个
=ANY
表达式(参见 here)。来自 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 在这两种情况下都可以在第一次命中后停止扫描,并且使用第一个索引更便宜。