优化PostgreSQL的计数查询

jer*_*uki 7 postgresql count database-performance postgresql-performance

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

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

select count(id) 
from table 
where not (ARRAY['parameter value'] <@ table.array_column)
Run Code Online (Sandbox Code Playgroud)

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

基本上我的问题是:

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

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

Cra*_*ger 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);
Run Code Online (Sandbox Code Playgroud)

不幸的是,这表明我们不能使用索引编写.如果你不否定它可以使用的状态,这样你就可以搜索和计算的行包含搜索元素(删除NOT).

你可以使用索引来算的条目包含目标值,然后从所有条目的数量减去结果.由于countPostgreSQL(9.1及更早版本)中表中的所有行都很慢并且需要顺序扫描,因此这实际上比当前查询慢.如果您打开了b树索引,则在9.2上可能会使用仅索引扫描对行进行计数id,在这种情况下,这可能实际上是正常的:

SELECT (
  SELECT count(id) FROM arrtable
) - (
  SELECT count(id) FROM arrtable 
  WHERE (ARRAY[1] <@ arrtable.array_column)
);
Run Code Online (Sandbox Code Playgroud)

它保证比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);
Run Code Online (Sandbox Code Playgroud)

请注意,像这样的GIN索引会减慢LOT的更新速度,并且首先创建起来非常慢.它不适合那些根本没有更新的表 - 比如你的表.

更糟糕的是,使用此索引的查询占用原始查询的两倍,最多只占同一数据集的一半.对于索引不是非常有选择性的情况(例如ARRAY[1]原始查询的4s vs 2s),这是最糟糕的.在索引具有高度选择性的情况下(即:不是很多匹配ARRAY[199]),它在原始的3秒内运行大约1.2秒.这个索引根本不值得拥有这个查询.

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

由于不会为你的命中率做,无论是作为@debenhur建议保持与触发器物化视图,或尝试反转阵列是一个参数列表,该条目并不会有那么你可以使用其主旨在于指数@maniek建议.


Cra*_*ger 4

\n

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

\n
\n\n

在这种情况下,最好的选择可能是标准化您的架构。将数组拆分成一个表。在属性表上添加 B 树索引,或对主键进行排序,以便可以通过property_id.

\n\n
CREATE TABLE demo( id integer primary key );\nINSERT INTO demo (id) SELECT id FROM arrtable;\nCREATE TABLE properties (\n  demo_id integer not null references demo(id),\n  property integer not null,\n  primary key (demo_id, property)\n);\nCREATE INDEX properties_property_idx ON properties(property);\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后您可以查询属性:

\n\n
SELECT count(id) \nFROM demo \nWHERE NOT EXISTS (\n  SELECT 1 FROM properties WHERE demo.id = properties.demo_id AND property = 1\n)\n
Run Code Online (Sandbox Code Playgroud)\n\n

我预计这会比原始查询快很多,但实际上对于相同的示例数据来说,结果是一样的;它的运行时间与您的原始查询相同 2 到 3 秒。这是同样的问题,搜索不存在的东西比搜索存在的东西慢得多;如果我们正在查找包含属性的行,我们可以避免 seqscandemoproperties直接扫描匹配的 ID。

\n\n

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

\n