PostgreSQL COUNT(DISTINCT…)非常慢


166

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

SELECT COUNT(DISTINCT x) FROM table;

我的表大约有150万行。该查询运行缓慢。大约需要7.5秒,而

 SELECT COUNT(x) FROM table;

大约需要435ms。有什么方法可以更改查询以提高性能?我试过分组并进行常规计数,以及在x上放置索引;两者具有相同的7.5s执行时间。


我不这么认为。获得150万行的不同值只会很慢。
Ry-

5
我刚刚在C#中进行了尝试,从内存中获取150万个整数的不同值在我的计算机上花费了超过一秒钟的时间。因此,我认为您可能不走运。
Ry-

查询计划将在很大程度上取决于表结构(索引)和调整常量(工作)的设置(工作存储器,有效缓存大小,随机页成本)。通过合理的调整,查询可能会在不到一秒钟的时间内执行。
wildplasser 2012年

你可以再详细一点吗?要在一秒钟内获得它,需要什么索引和调整常数?为简单起见,假设这是一个两列表,第一列y上有主键,而我正在对int类型的第二列x(具有150万行)进行“区别”查询。
ferson2020

1
请在表定义中包含所有索引(\d输出psql为好)并精确描述您遇到问题的列。最好看到EXPLAIN ANALYZE两个查询。
vyegorov 2012年

Answers:


316

您可以使用此:

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

这比以下要快得多:

COUNT(DISTINCT column_name)

38
圣查询蝙蝠侠!这使我的postgres计数从190s加速到了4.5哇!
rogerdpack

20
我在www.postgresql.org上发现了该线程,该线程讨论了同一件事:link。其中一项答复(由Jeff Janes发表)说COUNT(DISTINCT())对表进行排序以完成其工作,而不是使用哈希。
Ankur 2014年

5
我可以问你一个问题吗?由于COUNT(DISTINCT())执行了排序,因此对column_name具有相对较小的索引(尤其是work_mem哈希将产生大量相关批处理)进行索引无疑将很有帮助。从那以后,使用COUNT(DISTINCT()_并不总是不好,不是吗?
St.Antario

2
@musmahn Count(column)仅计算非空值。count(*)计算行数。因此,第一个/更长的行也将计算空行(一次)。进行更改以count(column_name)使它们的行为相同。
GolezTrol

1
@ankur对我来说没有太大用处。没有得到任何明显的改善。
Shiwangini

11
-- 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相同的计划也可能通过其他方法(窗口函数)来制定


2
您是否考虑过缓存的作用?如果随后进行三个“解释分析”,则第一个可能是从磁盘中缓慢地获取内容,而后两个则可能是从内存中快速获取内容。
tobixen

确实:有效的缓存大小是要调整的第一个设置。我的是2GB,IIRC。
wildplasser 2012年

我将我的effective_cache_size设置为2GB,而性能没有变化。您还有其他建议调整的设置吗?如果是这样,该怎么办?
ferson2020

1)您如何设置?(您是否感到高兴吗?)2)您实际上有那么多可用内存吗?3)向我们展示您的计划。4)也许我的机器速度更快,或者您的机器有更多的并发负载要处理。@ ferson2020:好的
wildplasser,2012年

我用以下语句设置它:SET effective_cache_size ='2GB'; 我确实有那么多内存。我尝试包括查询计划,但是它不适合在注释框中。
ferson2020

2

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


0

我也在搜索相同的答案,因为在某个时间点,我需要具有不同值的total_count以及limit / offset

因为这样做很麻烦-要获得具有不同值以及限制/偏移量的总数。通常很难获得带有限制/偏移量的总数。终于我有了办法-

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

查询性能也很高。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.