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

reo*_*eox 14 postgresql performance index sorting group-by

我有一个包含 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)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

hash列是 的 md5 哈希值string并有一个索引。所以我觉得我的问题是整个表是按id而不是hash排序的,所以先排序再分组需要一段时间?

该表nostring仅包含我不想拥有的哈希列表。但是我需要两个表都具有所有值。所以删除这些不是一个选项。

附加信息:没有任何列可以为空(在表定义中修复了该问题)并且我使用的是 postgresql 9.2。

Erw*_*ter 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;
Run Code Online (Sandbox Code Playgroud)

运行EXPLAIN ANALYZE查询。多次排除兑现效应和噪音。比较最佳结果。

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

CREATE INDEX methods_cluster_idx ON methods (hash, string, method);
Run Code Online (Sandbox Code Playgroud)

等待?在我说索引没有帮助之后?好吧,我们需要把它CLUSTER放在桌子上:

CLUSTER methods USING methods_cluster_idx;
ANALYZE methods;
Run Code Online (Sandbox Code Playgroud)

重新运行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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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


异国情调的要点:您的表名为nostring,但似乎包含散列。通过排除散列而不是字符串,您有可能排除比预期更多的字符串。不可能,但有可能。


dez*_*zso 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;
Run Code Online (Sandbox Code Playgroud)

或另一种可能性:

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;
Run Code Online (Sandbox Code Playgroud)

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

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

  • 成本只是为了规划者能够选择一个足够好的计划。实际时间通常与其相关,但不一定。因此,如果您想确定,请使用`EXPLAIN ANALYZE`。 (3认同)