10

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

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

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

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

基本上我的问题是:

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

您能给我的任何建议将不胜感激。

4

3 回答 3

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 和更早版本)中 ing 表中的所有行都非常慢,并且需要顺序扫描,这实际上会比您当前的查询慢。如果在 9.2 上有一个 b-tree 索引,则可以使用仅索引扫描来计算行数id,在这种情况下,这实际上可能没问题:

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]原始查询的 4s vs 2s,这是最糟糕的。在索引具有高度选择性的情况下(即:没有多少匹配项,例如ARRAY[199]),它的运行时间约为 1.2 秒,而原始索引的运行时间为 3 秒。这个索引根本不值得这个查询。

这里的教训?有时,正确的答案就是进行顺序扫描。

由于这不会影响您的命中率,因此请按照@debenhur 的建议使用触发器维护物化视图,或者尝试将数组反转为条目没有的参数列表,以便您可以使用 GiST 索引作为@maniek 建议。

于 2012-10-26T00:30:20.570 回答
4

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

在这种情况下,您最好的选择可能是规范化您的模式。将数组拆分成一个表。在属性表上添加 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 范围内运行。同样的问题是,搜索存在的东西比搜索存在的东西要慢得多。如果我们正在寻找包含属性的行,我们可以避免 seqscandemo并直接扫描properties匹配的 ID。

同样,对包含数组的表进行 seq 扫描也可以完成这项工作。

于 2012-10-26T02:17:10.667 回答
2

我认为您当前的数据模型不走运。尝试考虑数据库必须为您的查询执行的算法。如果没有顺序扫描数据,它就无法工作。

您可以安排列以便它存储数据的倒数(以便查询是select count(id) from table where ARRAY[‘parameter value’] <@ table.array_column)?此查询将使用 gin/gist 索引。

于 2012-10-25T19:41:57.997 回答