优化PostgreSQL的计数查询

时间:2012-10-25 18:48:02

标签: postgresql count database-performance postgresql-performance

我在postgresql中有一个表,其中包含一个不断更新的数组。

在我的应用程序中,我需要获取该数组列中不存在特定参数的行数。我的查询如下:

select count(id) 
from table 
where not (ARRAY['parameter value'] <@ table.array_column)

但是当增加行数和该查询的执行量(每秒几次,可能是数百或数千)时,性能会下降很多,在我看来,postgresql中的计数可能具有线性顺序执行(我不完全确定这一点)。

基本上我的问题是:

是否存在我不知道的现有模式适用于这种情况?什么是最好的方法呢?

任何你能给我的建议都会非常感激。

3 个答案:

答案 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索引。