实际上,PostgreSQL支持在数组列上创建GIN索引。不幸的是,似乎无法将其用于NOT ARRAY[...] <@ indexed_col
,而且对于经常更新的表,GIN
索引也不适合。
Demo:
CREATE TABLE arrtable (id integer primary key, array_column integer[]);
INSERT INTO arrtable(1, ARRAY[1,2,3,4]);
CREATE INDEX arrtable_arraycolumn_gin_arr_idx
ON arrtable USING GIN(array_column);
SET enable_seqscan = off;
explain (buffers, analyze) select count(id)
from arrtable
where not (ARRAY[1] <@ arrtable.array_column);
很遗憾,这表明按照现有的写法我们不能使用索引。如果不否定条件,则可以使用它,因此您可以搜索并计算包含搜索元素的行数(通过删除“NOT”)。您可以使用索引计算包含目标值的条目数量,然后从所有条目的计数中减去该结果。由于在 PostgreSQL(9.1及更早版本)中计算表中所有行的数量相当缓慢且需要顺序扫描,因此这实际上比您当前的查询更慢。如果您在id上有一个b树索引,则在9.2上可能会使用仅索引扫描来计算行数,那么这可能是可以接受的:
SELECT (
SELECT count(id) FROM arrtable
) - (
SELECT count(id) FROM arrtable
WHERE (ARRAY[1] <@ arrtable.array_column)
);
保证在Pg 9.1及以下版本中,其性能肯定比您的原始版本差,因为除了需要seqscan之外,原始版本还需要GIN索引扫描。我已经在9.2上进行了测试,它确实会使用索引进行计数,因此值得在9.2上探索。对于一些不太琐碎的虚拟数据:
drop index arrtable_arraycolumn_gin_arr_idx ;
truncate table arrtable;
insert into arrtable (id, array_column)
select s, ARRAY[1,2,s,s*2,s*3,s/2,s/4] FROM generate_series(1,1000000) s;
CREATE INDEX arrtable_arraycolumn_gin_arr_idx
ON arrtable USING GIN(array_column);
请注意,像这样的GIN索引会显著减慢更新速度,并且在创建时相当缓慢。它不适合那些更新频繁的表格 - 就像你的表格一样。
更糟糕的是,使用该索引的查询时间最长可达到原始查询的两倍,并且在相同数据集上最多需要一半的时间。例如使用
ARRAY[1]
时性能最差,原始查询需要2秒而该索引则需要4秒。当索引高度具有选择性(即匹配较少,例如
ARRAY[199]
)时,它运行时间约为1.2秒,而原始查询则需要3秒。对于此查询,这个索引根本不值得拥有。
教训是什么?有时,正确答案就是进行顺序扫描。
既然这对于您的命中率不够用,请尝试像@maniek建议的那样将数组反转为entry没有的参数列表,这样可以使用GiST索引;或者像@debenhur建议的那样维护一个包含触发器的物化视图。
explain analyze
; 请参见http://stackoverflow.com/tags/postgresql-performance/info - Craig Ringer