postgresql COUNT(DISTINCT ...) 非常慢

Posted

技术标签:

【中文标题】postgresql COUNT(DISTINCT ...) 非常慢【英文标题】:postgresql COUNT(DISTINCT ...) very slow 【发布时间】:2012-06-30 07:18:18 【问题描述】:

我有一个非常简单的 SQL 查询:

SELECT COUNT(DISTINCT x) FROM table;

我的表有大约 150 万行。这个查询运行得很慢;大约需要 7.5 秒,相比之下

 SELECT COUNT(x) FROM table;

大约需要 435 毫秒。有什么方法可以更改我的查询以提高性能?我尝试分组并定期计数,以及在 x 上放置索引;两者都有相同的 7.5 秒执行时间。

【问题讨论】:

我不这么认为。获取 150 万行的不同值会很慢。 我刚刚在 C# 中尝试过,从内存中获取 150 万个 整数 的不同值在我的计算机上花费了超过一秒钟的时间。所以我认为你可能不走运。 查询计划在很大程度上取决于表结构(索引)和调整常量(work)mem、effective_cache_size、random_page_cost)的设置。通过合理的调整,查询可能会在不到一秒的时间内执行。 您能说得更具体些吗?在一秒钟内获得它需要哪些索引和调整常量?为简单起见,假设这是一个双列表,第一列 y 上有一个主键,我正在对 int 类型的第二列 x 执行此“不同”查询,有 150 万行。 请包含所有索引的表定义(psql\d 输出是好的),并准确列出您有问题的列。很高兴看到两个查询的EXPLAIN ANALYZE 【参考方案1】:

你可以用这个:

SELECT COUNT(*) FROM (SELECT DISTINCT column_name FROM table_name) AS temp;

这比:

COUNT(DISTINCT column_name)

【讨论】:

圣问蝙蝠侠!这加快了我的 postgres 计数从 190 到 4.5 哇! 我在www.postgresql.org 上找到了这个帖子,讨论了同样的事情:link。其中一个回复(由 Jeff Janes 提供)说 COUNT(DISTINCT()) 对表进行排序以完成其工作,而不是使用哈希。 @Ankur 我可以问你问题吗?由于COUNT(DISTINCT()) 执行排序,因此在column_name 上建立索引肯定会有所帮助,尤其是在work_mem 数量相对较少的情况下(其中散列会产生相对大量的批次)。从那以后,使用 COUNT (DISTINCT()_,不是吗? @musmahn Count(column) 只计算非空值。 count(*) 计算行数。所以第一个/更长的,也将计算空行(一次)。更改为 count(column_name) 以使它们的行为相同。 @ankur 这对我没有多大用处..没有得到任何显着的改进。【参考方案2】:
-- My default settings (this is basically a single-session machine, so work_mem is pretty high)
SET effective_cache_size='2048MB';
SET work_mem='16MB';

\echo original
EXPLAIN ANALYZE
SELECT
        COUNT (distinct val) as aantal
FROM one
        ;

\echo group by+count(*)
EXPLAIN ANALYZE
SELECT
        distinct val
       -- , COUNT(*)
FROM one
GROUP BY val;

\echo with CTE
EXPLAIN ANALYZE
WITH agg AS (
    SELECT distinct val
    FROM one
    GROUP BY val
    )
SELECT COUNT (*) as aantal
FROM agg
        ;

结果:

original                                                      QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=36448.06..36448.07 rows=1 width=4) (actual time=1766.472..1766.472 rows=1 loops=1)
   ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=31.371..185.914 rows=1499845 loops=1)
 Total runtime: 1766.642 ms
(3 rows)

group by+count(*)
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=36464.31..36477.31 rows=1300 width=4) (actual time=412.470..412.598 rows=1300 loops=1)
   ->  HashAggregate  (cost=36448.06..36461.06 rows=1300 width=4) (actual time=412.066..412.203 rows=1300 loops=1)
         ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=26.134..166.846 rows=1499845 loops=1)
 Total runtime: 412.686 ms
(4 rows)

with CTE
                                                             QUERY PLAN                                                             
------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=36506.56..36506.57 rows=1 width=0) (actual time=408.239..408.239 rows=1 loops=1)
   CTE agg
     ->  HashAggregate  (cost=36464.31..36477.31 rows=1300 width=4) (actual time=407.704..407.847 rows=1300 loops=1)
           ->  HashAggregate  (cost=36448.06..36461.06 rows=1300 width=4) (actual time=407.320..407.467 rows=1300 loops=1)
                 ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=24.321..165.256 rows=1499845 loops=1)
       ->  CTE Scan on agg  (cost=0.00..26.00 rows=1300 width=0) (actual time=407.707..408.154 rows=1300 loops=1)
     Total runtime: 408.300 ms
    (7 rows)

可能也可以通过其他方法(窗口函数)生成与 CTE 相同的计划

【讨论】:

你考虑过缓存的效果吗?如果随后进行三个“解释分析”,第一个可能会从磁盘中缓慢获取内容,而后两个可能会从内存中快速获取。 确实:effective_cache_size 是第一个需要调整的设置。我的是 2GB,IIRC。 我将 Effective_cache_size 设置为 2GB,性能没有变化。您建议调整任何其他设置吗?如果是,那是什么? 1) 如何你是怎么设置的? (你 HUP 了吗?) 2)你真的有那么多可用的内存吗? 3) 向我们展示您的计划。 4)也许我的机器更快,或者你的机器有更多的并发负载要处理。 @ferson2020:好的 我用语句设置它:SET effective_cache_size='2GB';我确实有那么多可用的内存。我尝试包含我的查询计划,但它不适合评论框。【参考方案3】:

如果您的count(distinct(x))count(x) 慢得多,那么您可以通过使用触发器在不同的表(例如table_name_x_counts (x integer not null, x_count int not null))中维护x 值计数来加速此查询。但是您的写入性能会受到影响,如果您在单个事务中更新多个 x 值,那么您需要以某种明确的顺序执行此操作以避免可能的死锁。

【讨论】:

【参考方案4】:

我也在寻找相同的答案,因为有时我需要 total_count 具有不同的值以及限制/偏移量

因为它有点棘手 - 获得具有不同值的总计数以及限制/偏移量。通常很难通过限制/偏移来获得总计数。终于有办法了——

SELECT DISTINCT COUNT(*) OVER() as total_count, * FROM table_name limit 2 offset 0;

查询性能也很高。

【讨论】:

【参考方案5】:

select coluna, count(coluna) as qtd from tabela group by coluna

【讨论】:

以上是关于postgresql COUNT(DISTINCT ...) 非常慢的主要内容,如果未能解决你的问题,请参考以下文章

"HybridDB · 性能优化 · Count Distinct的几种实现方式” 读后感

SQL优化 快速计算Distinct Count

在插入 table2 之前,如何在 table1 的多个列上应用 count 和 distinct

为啥 distinct 不能与 Laravel + Postgresql 一起使用?

Codeigniter 中由 distinct() 或 group_by() 过滤的计数结果

count和distinct