使用GROUP BY和ORDER BY对大表进行慢查询


14

我有一张桌子,上面有720万个元组,看起来像这样:

                               table public.methods
 column |          type         |                      attributes
--------+-----------------------+----------------------------------------------------
 id     | integer               | not null DEFAULT nextval('methodkey'::regclass)
 hash   | character varying(32) | not null
 string | character varying     | not null
 method | character varying     | not null
 file   | character varying     | not null
 type   | character varying     | not null
Indexes:
    "methods_pkey" PRIMARY KEY, btree (id)
    "methodhash" btree (hash)

现在,我想选择一些值,但是查询速度非常慢:

db=# explain 
    select hash, string, count(method) 
    from methods 
    where hash not in 
          (select hash from nostring) 
    group by hash, string 
    order by count(method) desc;
                                            QUERY PLAN
----------------------------------------------------------------------------------------
 Sort  (cost=160245190041.10..160245190962.07 rows=368391 width=182)
   Sort Key: (count(methods.method))
   ->  GroupAggregate  (cost=160245017241.77..160245057764.73 rows=368391 width=182)
       ->  Sort  (cost=160245017241.77..160245026451.53 rows=3683905 width=182)
             Sort Key: methods.hash, methods.string
             ->  Seq Scan on methods  (cost=0.00..160243305942.27 rows=3683905 width=182)
                   Filter: (NOT (SubPlan 1))
                   SubPlan 1
                   ->  Materialize  (cost=0.00..41071.54 rows=970636 width=33)
                     ->  Seq Scan on nostring  (cost=0.00..28634.36 rows=970636 width=33)

hash列是的md5哈希,string并具有索引。因此,我认为我的问题是整个表是按ID而不是按哈希排序的,因此需要花一些时间先对其进行排序然后再对其进行分组?

该表nostring仅包含我不想拥有的哈希列表。但是我需要两个表都具有所有值。因此,不能删除它们。

其他信息:所有列均不能为null(已在表定义中修复),并且我正在使用PostgreSQL 9.2。


1
始终提供您使用的PostgreSQL 版本NULL列中值的百分比是method多少?上有重复品string吗?
Erwin Brandstetter,2012年

Answers:


18

LEFT JOIN@·德热的答案应该是不错的。但是,索引本身将几乎无用,因为查询无论如何都必须读取整个表-例外是Postgres 9.2+中的仅索引扫描和有利条件,请参见下文。

SELECT m.hash, m.string, count(m.method) AS method_ct
FROM   methods m
LEFT   JOIN nostring n USING (hash)
WHERE  n.hash IS NULL
GROUP  BY m.hash, m.string 
ORDER  BY count(m.method) DESC;

EXPLAIN ANALYZE在查询上运行。几次排除现金影响和噪音。比较最佳结果。

创建一个与您的查询匹配的多列索引:

CREATE INDEX methods_cluster_idx ON methods (hash, string, method);

等待?我说完索引对您没有帮助吗?好吧,我们需要它到CLUSTER桌子上:

CLUSTER methods USING methods_cluster_idx;
ANALYZE methods;

重新运行EXPLAIN ANALYZE。快一点吗?它应该是。

CLUSTER是一项一次性操作,用于按使用的索引顺序重写整个表。它实际上也是一个VACUUM FULL。如果您想确定的话,可以VACUUM FULL单独进行预测试,以了解可以归因于此的内容。

如果表中有很多写操作,则效果会随着时间而降低。安排CLUSTER在下班时间恢复效果。微调取决于您的确切用例。有关的手册CLUSTER

CLUSTER是一个相当粗糙的工具,需要在表上具有排他锁。如果您负担不起,请考虑pg_repack在没有互斥锁的情况下可以做到的相同操作。在此稍后的答案中有更多信息:


如果NULL列中的值百分比method很高(超过〜20%,具体取决于实际的行大小),则部分索引应该有帮助:

CREATE INDEX methods_foo_idx ON methods (hash, string)
WHERE method IS NOT NULL;

(您以后的更新显示您的列为NOT NULL,因此不适用。)

如果您运行的是PostgreSQL 9.2或更高版本(如@deszo所述),CLUSTER则在计划者不能利用仅索引扫描的情况下,所提供的索引可能很有用。仅在有利条件下适用:由于VACUUM索引必须覆盖查询的最后一列和所有列,因此不会影响可见性图的写操作。基本上,只读表可以随时使用,而写大量表的表受到限制。在Postgres Wiki中有更多详细信息。

在这种情况下,上述部分索引可能会更加有用。

另一方面,如果 column 中没有 NULLmethod,则应该
1.)定义它,NOT NULL
2.)使用count(*)代替count(method),这会稍微快一些,并且在没有NULL值的情况下也可以这样做。

如果您必须经常调用此查询并且该表是只读的,请创建一个MATERIALIZED VIEW


奇妙之处:您的表名为nostring,但似乎包含哈希。通过排除哈希而不是字符串,可以排除比预期更多的字符串。不可能,但可能。


集群的速度更快。仍然需要5分钟左右的查询时间,但这比整夜运行要好得多:D
reox 2012年

@reox:由于您运行v9.2:在集群之前,您是否仅使用索引进行测试?如果您发现有所不同,那将很有趣。(不能在聚类后重现差异。)另外(这很便宜),EXPLAIN现在显示索引扫描还是全表扫描?
Erwin Brandstetter

5

欢迎使用DBA.SE!

您可以尝试如下重新查询:

SELECT m.hash, string, count(method) 
FROM 
    methods m
    LEFT JOIN nostring n ON m.hash = n.hash
WHERE n.hash IS NULL
GROUP BY hash, string 
ORDER BY count(method) DESC;

或其他可能性:

SELECT m.hash, string, count(method) 
FROM 
    methods m
WHERE NOT EXISTS (SELECT hash FROM nostring WHERE hash = m.hash)
GROUP BY hash, string 
ORDER BY count(method) DESC;

NOT IN 是性能的典型接收器,因为很难与索引一起使用。

这可以通过索引进一步增强。上的索引nostring.hash看起来很有用。但是首先:你现在得到什么?(最好查看输出结果,EXPLAIN ANALYZE因为成本本身并不能告诉操作时间。)


已经在nostring.hash上创建了一个索引,但我认为postgres不使用它,因为有太多的元组...当我明确禁用序列扫描时,它使用了索引。如果我使用左
联接

3
这样做的代价只是使计划者能够选择足够好的计划。实际时间通常与此相关,但不一定。因此,如果您想确定要使用EXPLAIN ANALYZE
dezso 2012年

1

由于hash是md5,因此您可能会尝试将其转换为数字:您可以将其存储为数字,或者只是创建一个在不可变函数中计算该数字的函数索引。

其他人已经创建了一个pl / pgsql函数,该函数将md5值(的一部分)从文本转换为字符串。有关示例,请参见/programming/9809381/hashing-a-string-to-a-numeric-value-in-postgressql

我相信您在扫描索引时确实在字符串比较上花费了大量时间。如果您设法将该值存储为数字,那么它实际上应该会更快。


1
我怀疑这种转换会加快速度。这里的所有查询都使用相等性进行比较。计算数字表示形式然后检查是否相等对我来说不会有太大的收获。
dezso 2012年

2
我认为我将md5存储为字节数而不是空间效率数字:sqlfiddle.com
#!

另外,欢迎访问dba.se!
杰克说试试topanswers.xyz 2012年

@JackDouglas:有趣的评论!对于大型表,每个md5 16字节而不是32字节相当多。
Erwin Brandstetter,2012年

0

我经常遇到这个问题,并发现了一个简单的两部分技巧。

  1. 在散列值上创建子字符串索引:(通常长度为7)

    create index methods_idx_hash_substring ON methods(substring(hash,1,7))

  2. 让您的搜索/联接包含子字符串匹配项,因此建议查询计划者使用索引:

    旧: WHERE hash = :kwarg

    新: WHERE (hash = :kwarg) AND (substring(hash,1,7) = substring(:kwarg,1,7))

您还应该在原始数据上有一个索引hash

结果(通常)是计划者将首先查询子字符串索引并清除大部分行。然后将完整的32个字符的哈希值与相应的索引(或表)进行匹配。这种方法对我来说将800ms查询减少到4个。

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.