我在postgresql中有一个表,其中包含一个不断更新的数组。
在我的应用程序中,我需要获取该数组列中不存在特定参数的行数。我的查询如下:
select count(id)
from table
where not (ARRAY['parameter value'] <@ table.array_column)
但是当增加行数和该查询的执行量(每秒几次,可能是数百或数千)时,性能会下降很多,在我看来,postgresql中的计数可能具有线性顺序执行(我不完全确定这一点)。
基本上我的问题是:
是否存在我不知道的现有模式适用于这种情况?什么是最好的方法呢?
任何你能给我的建议都会非常感激。
答案 0 :(得分:5)
PostgreSQL实际上支持数组列上的GIN索引。不幸的是,它似乎不适用于NOT ARRAY[...] <@ indexed_col
,而且GIN
索引不适用于频繁更新的表格。
演示:
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);
-- Use the following *only* for testing whether Pg can use an index
-- Do not use it in production.
SET enable_seqscan = off;
explain (buffers, analyze) select count(id)
from arrtable
where not (ARRAY[1] <@ arrtable.array_column);
不幸的是,这表明我们不能使用索引编写。如果你没有否定它可以使用的条件,那么你可以搜索和计算做包含搜索元素的行(通过删除NOT
)。
您可以使用索引计算执行包含目标值的条目,然后从所有条目的计数中减去该结果。由于count
表中的所有行在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索引会降低LOT的更新速度,并且首先创建速度非常慢。它不适合那些根本没有更新的表格 - 比如你的桌子。
更糟糕的是,使用此索引的查询占用原始查询的两倍,而在同一数据集上最多只有一半。对于索引不是很有选择性的情况(例如ARRAY[1]
- 原始查询的4s vs 2s),情况最糟糕。在索引具有高度选择性的情况下(即:ARRAY[199]
不是很多匹配),它比原始的3秒运行大约1.2秒。这个索引根本不值得进行此查询。
这里有什么教训?有时,正确的答案就是进行顺序扫描。
由于这不符合你的命中率,要么像@debenhur所建议的那样使用触发器维护物化视图,要么尝试将数组反转为条目不这样你可以像@maniek建议的那样使用GiST索引。
答案 1 :(得分:3)
是否存在我不知道的现有模式适用于此 情况?什么是最好的方法呢?
在这种情况下,最好的选择可能是规范化您的架构。将数组拆分为表格。在属性表上添加b树索引,或者对主键进行排序,以便property_id
可以有效地搜索它。
CREATE TABLE demo( id integer primary key );
INSERT INTO demo (id) SELECT id FROM arrtable;
CREATE TABLE properties (
demo_id integer not null references demo(id),
property integer not null,
primary key (demo_id, property)
);
CREATE INDEX properties_property_idx ON properties(property);
然后,您可以查询属性:
SELECT count(id)
FROM demo
WHERE NOT EXISTS (
SELECT 1 FROM properties WHERE demo.id = properties.demo_id AND property = 1
)
我预计这比原始查询快得多,但实际上相同的样本数据却大致相同;它与原始查询在2s到3s范围内运行。这是同样的问题,搜索不的内容比搜索那里的 慢得多;如果我们要查找包含属性的行,我们可以避免使用demo
的seqscan,只需扫描properties
即可直接匹配ID。
同样,对包含数组的表进行seq扫描也能完成这项工作。
答案 2 :(得分:2)
我认为你当前的数据模型你运气不好。尝试考虑数据库必须为您的查询执行的算法。如果没有顺序扫描数据,它就无法工作。
您可以安排列,以便存储数据的反转(以便查询为select count(id) from table where ARRAY[‘parameter value’] <@ table.array_column
)吗?此查询将使用gin / gist索引。