PostgreSQL 计数查询优化

Posted

技术标签:

【中文标题】PostgreSQL 计数查询优化【英文标题】:Optimization of count query for PostgreSQL 【发布时间】:2012-10-16 00:39:39 【问题描述】:

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

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

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

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

基本上我的问题是:

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

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

【问题讨论】:

不确定,但我认为 table.array_column 上的 GIN 索引将有助于加快速度。您需要运行 EXPLAIN 才能找到答案。见这里:dba.stackexchange.com/a/27505/1822 随着表变大,很难在 postgres 中提高效率。杜松子酒索引仅在测试谓词中的“包含在”而不是“不包含在”时才有帮助。如果计数是否 100% 准确并不重要,您可以尝试使用一些 TTL 在应用层缓存它。如果你对表的写入率不是太高,你可以合理地使用触发器来更新另一个包含当前计数的表。 最好显示你的版本和explain analyze;见***.com/tags/postgresql-performance/info 是否有固定的属性列表?您无法真正索引那里 not 的内容,因此您可以将其重新构建为条目 not 具有的参数列表。 我相信属性列表可能是固定的。如果这有助于以某种方式解决问题,当然可以假定它已修复。 【参考方案1】:

PostgreSQL 实际上支持数组列上的 GIN 索引。不幸的是,它似乎不适用于NOT ARRAY[...] &lt;@ 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)。

您可以使用索引来计算确实包含目标值的条目,然后从所有条目的计数中减去该结果。由于counting 表中的所有行在 PostgreSQL(9.1 和更早版本)中非常慢,并且需要顺序扫描,这实际上会比您当前的查询慢。如果您在 id 上有一个 b-tree 索引,则在 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] - 原始查询的 4 秒 vs 2 秒,这是最糟糕的情况。在索引具有高度选择性的情况下(即:没有多少匹配项,例如ARRAY[199]),它的运行时间约为 1.2 秒,而原始索引的运行时间为 3 秒。这个索引根本不值得这个查询。

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

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

【讨论】:

【参考方案2】:

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

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

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

【讨论】:

感谢您的详细解释,是的,显然在我目前的情况下最好进行顺序计数或考虑另一种存储信息的方式以加快搜索速度,再次非常感谢这是真的好用【参考方案3】:

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

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

【讨论】:

以上是关于PostgreSQL 计数查询优化的主要内容,如果未能解决你的问题,请参考以下文章

从 EF 为 PostGresql 生成的查询中的非最佳计数

禁用 PostgreSQL 查询优化?

优化 PostgreSQL 中的 SQL 查询

PostgreSQL 查询性能和可能的优化

PostgreSQL代码分析,查询优化部分,process_duplicate_ors

理解/优化 Postgresql 中的 SQL 查询