使用Postgres在varchar列上使用distinct / group by进行慢查询

时间:2015-05-16 12:15:53

标签: database performance postgresql indexing query-optimization

我有一个created_at表和一个company表,其中有一个多对多关系表,用于链接两个名为industry的表。 company_industry表目前有大约750.000行。

Database schema

现在我需要一个查询,查找给定行业的所有唯一城市名称,其中至少有一家公司。所以基本上我必须找到与特定行业相关的所有公司,并为这些公司选择独特的城市名称。

我可以编写执行此操作的查询,但不是我正在寻找的性能。事先我对性能持怀疑态度,因为company列属于city_name类型。不幸的是,我目前可以自由地将数据库模式更改为更加规范化的模式。

我做的第一件事是在VARCHAR列上添加索引,然后我尝试了以下查询。

city_name

上述查询平均需要大约两秒钟的时间。将SELECT c.city_name AS city FROM industry AS i INNER JOIN company_industry AS ci ON (ci.industry_id = i.id) INNER JOIN company AS c ON (c.id = ci.company_id) WHERE i.id = 288 GROUP BY city; 替换为GROUP BY时也是如此。以下是上述查询的执行计划。

DISTINCT

我尝试将查询更改为使用子查询,如下所示,这使得查询大约快了两倍。

HashAggregate  (cost=56934.21..56961.61 rows=2740 width=9) (actual time=2421.364..2421.921 rows=1962 loops=1)
  ->  Hash Join  (cost=38972.69..56902.50 rows=12687 width=9) (actual time=954.377..2411.194 rows=12401 loops=1)
        Hash Cond: (ci.company_id = c.id)
        ->  Nested Loop  (cost=0.28..13989.91 rows=12687 width=4) (actual time=0.041..203.442 rows=12401 loops=1)
              ->  Index Only Scan using industry_pkey on industry i  (cost=0.28..8.29 rows=1 width=4) (actual time=0.015..0.018 rows=1 loops=1)
                    Index Cond: (id = 288)
                    Heap Fetches: 0
              ->  Seq Scan on company_industry ci  (cost=0.00..13854.75 rows=12687 width=8) (actual time=0.020..199.087 rows=12401 loops=1)
                    Filter: (industry_id = 288)
                    Rows Removed by Filter: 806309
        ->  Hash  (cost=26036.52..26036.52 rows=744152 width=13) (actual time=954.113..954.113 rows=744152 loops=1)
              Buckets: 4096  Batches: 64  Memory Usage: 551kB
              ->  Seq Scan on company c  (cost=0.00..26036.52 rows=744152 width=13) (actual time=0.008..554.662 rows=744152 loops=1)
Total runtime: 2422.185 ms

此查询的执行计划:

SELECT c.city_name
FROM company AS c
WHERE EXISTS(
  SELECT 1
  FROM company_industry
  WHERE industry_id = 288 AND company_id = c.id
)
GROUP BY c.city_name;

那更好,但希望你们能帮助我做得更好。

基本上,查询的昂贵部分似乎是找到唯一的城市名称(如预期的那样),即使列上有索引,性能也不够好。在分析执行计划方面我很生气,但我把它们包括在内,这样你们就可以看到到底发生了什么。

如何更快地检索此数据?

我正在使用 Postgres 9.3.5 ,DDL如下:

HashAggregate  (cost=47108.71..47136.11 rows=2740 width=9) (actual time=1270.171..1270.798 rows=1962 loops=1)
  ->  Hash Semi Join  (cost=14015.50..47076.98 rows=12690 width=9) (actual time=194.548..1251.785 rows=12401 loops=1)
        Hash Cond: (c.id = company_industry.company_id)
        ->  Seq Scan on company c  (cost=0.00..26036.52 rows=744152 width=13) (actual time=0.008..537.856 rows=744152 loops=1)
        ->  Hash  (cost=13856.88..13856.88 rows=12690 width=4) (actual time=194.399..194.399 rows=12401 loops=1)
              Buckets: 2048  Batches: 1  Memory Usage: 436kB
              ->  Seq Scan on company_industry  (cost=0.00..13856.88 rows=12690 width=4) (actual time=0.012..187.449 rows=12401 loops=1)
                    Filter: (industry_id = 288)
                    Rows Removed by Filter: 806309
Total runtime: 1271.030 ms

2 个答案:

答案 0 :(得分:3)

两个查询计划中都有Seq Scan on company_industry,它们应该是(位图)索引扫描。 Seq Scan on company也是如此。

这似乎是缺少索引的问题 - 或者您的数据库中的某些内容不正确。如果您的数据库中出现问题,请在继续之前绘制备份。检查成本设置和统计信息是否有效:

如果设置良好,我会检查相关指数(详见下文)。也许一个简单的

REINDEX TABLE company;
REINDEX TABLE company_industry;

会修复它,也许你需要做更多的事情:

此外,您可以简化查询:

SELECT c.city_name AS city
FROM   company_industry ci
JOIN   company          c ON c.id = ci.company_id
WHERE  ci.industry_id = 288
GROUP  BY 1;

注释

  • 如果您的PK约束在(company_id, industry_id)上,请在(industry_id, company_id)上添加另一个(唯一的)索引逆序!)。为什么呢?

  • Seq Scan on company同样令人烦恼。好像company(id)上没有索引,但你的ER图表示PK,所以不能这样? 最快的选择是在(id, city_name)上设置多列索引 - 如果(且仅当),您可以从中获得仅索引扫描。

  • 由于您已拥有特定行业的ID,因此您根本不需要包含表格industry

  • ON子句中的表达式周围不需要括号。

  • 这很不幸:

      

    不幸的是,我现在没有能力将数据库模式更改为更规范化的内容。

    您的简单架构对于具有很少冗余且几乎没有可用高速缓存存储器的小型表是有意义的。但是城市名称在大表中可能是多余的。 规范化会大幅缩小表格和索引大小,这是性能最重要的因素 具有冗余存储的非规范化形式有时可以为目标查询带来性能提升,有时不会,这取决于。但总是会对其他一切产生负面影响。冗余存储会占用更多可用缓存,因此其他数据必须尽快从缓存中删除。即使如果你在当地获得了某些东西,你也会失去整体 在这种特殊情况下,为city_id int列获取不同的值也会相当便宜,因为integer值比(可能很长)字符串更小,更快。公司(id, city_id)上的多列索引小于(id, city_name)的多列索引,处理速度更快。 折叠许多重复之后再加入相对便宜。

    如果您需要最佳效果,您可以随时为特殊目的添加MATERIALIZED VIEW预先计算的结果(很容易汇总并在industry_id上添加索引),但不要存储在您的主要表格中大量冗余的信息。

答案 1 :(得分:-1)

如果您希望以毫秒为单位进行此查询,那么您应该通过将另一列city_name添加到联结表company_industry并将其编入索引来de-normalize您的数据。

这样你只会查询(未经测试) SELECT DISTINCT(c.city_name) FROM company_industry ci GROUP BY ci.industry_id HAVING COUNT(ci.company_id) >= 1