2012-03-29 124 views
1

此查询花费约4秒来完成:为什么DISTINCT/INNER JOIN/ORDER BY postgresql查询这么慢?

SELECT DISTINCT "resources_resource"."id", 
        "resources_resource"."heading", 
        "resources_resource"."name", 
        "resources_resource"."old_name", 
        "resources_resource"."clean_name", 
        "resources_resource"."sort_name", 
        "resources_resource"."see_also_id", 
        "resources_resource"."referenced_passages", 
        "resources_resource"."resource_type", 
        "resources_resource"."ord", 
        "resources_resource"."content", 
        "resources_resource"."thumb", 
        "resources_resource"."resource_origin" 
    FROM "resources_resource" 
    INNER JOIN "resources_passageresource" ON ("resources_resource"."id" = "resources_passageresource"."resource_id") 
    WHERE "resources_passageresource"."start_ref" >= 66001001 
    ORDER BY "resources_resource"."ord" ASC, "resources_resource"."sort_name" ASC LIMIT 5 

通过流行请求解释ANALYZE:

Limit (cost=1125.50..1125.68 rows=5 width=803) (actual time=4434.076..4434.557 rows=5 loops=1) 
    -> Unique (cost=1125.50..1136.91 rows=326 width=803) (actual time=4434.076..4434.557 rows=5 loops=1) 
     -> Sort (cost=1125.50..1126.32 rows=326 width=803) (actual time=4434.075..4434.075 rows=6 loops=1) 
       Sort Key: resources_resource.ord, resources_resource.sort_name, resources_resource.id, resources_resource.heading, resources_resource.name, resources_resource.old_name, resources_resource.clean_name, resources_resource.see_also_id, resources_resource.referenced_passages, resources_resource.resource_type, resources_resource.content, resources_resource.thumb, resources_resource.resource_origin 
       Sort Method: quicksort Memory: 424kB 
       -> Hash Join (cost=697.00..1111.89 rows=326 width=803) (actual time=3.453..41.429 rows=424 loops=1) 
        Hash Cond: (resources_passageresource.resource_id = resources_resource.id) 
        -> Bitmap Heap Scan on resources_passageresource (cost=10.78..190.19 rows=326 width=4) (actual time=0.107..0.401 rows=424 loops=1) 
          Recheck Cond: (start_ref >= 66001001) 
          -> Bitmap Index Scan on resources_passageresource_start_ref (cost=0.00..10.70 rows=326 width=0) (actual time=0.086..0.086 rows=424 loops=1) 
           Index Cond: (start_ref >= 66001001) 
        -> Hash (cost=431.32..431.32 rows=2232 width=803) (actual time=3.228..3.228 rows=2232 loops=1) 
          Buckets: 1024 Batches: 2 Memory Usage: 947kB 
          -> Seq Scan on resources_resource (cost=0.00..431.32 rows=2232 width=803) (actual time=0.002..1.621 rows=2232 loops=1) 
Total runtime: 4435.460 ms 

这是ORM生成SQL。我可以在SQL中工作,但我绝对不熟练,这里的EXPLAIN输出对我来说很神秘。这个查询如何拖拽我?

更新: @Ybakos发现ORDER_BY正在造成麻烦。删除ORDER_BY子句完全有所帮助,但查询仍需要800ms。这里的EXPLAIN分析一下,SANS ORDER_BY

HashAggregate (cost=1122.49..1125.75 rows=326 width=803) (actual time=787.519..787.559 rows=104 loops=1) 
    -> Hash Join (cost=697.00..1111.89 rows=326 width=803) (actual time=3.381..7.312 rows=424 loops=1) 
     Hash Cond: (resources_passageresource.resource_id = resources_resource.id) 
     -> Bitmap Heap Scan on resources_passageresource (cost=10.78..190.19 rows=326 width=4) (actual time=0.095..0.686 rows=424 loops=1) 
       Recheck Cond: (start_ref >= 66001001) 
       -> Bitmap Index Scan on resources_passageresource_start_ref (cost=0.00..10.70 rows=326 width=0) (actual time=0.079..0.079 rows=424 loops=1) 
        Index Cond: (start_ref >= 66001001) 
     -> Hash (cost=431.32..431.32 rows=2232 width=803) (actual time=3.173..3.173 rows=2232 loops=1) 
       Buckets: 1024 Batches: 2 Memory Usage: 947kB 
       -> Seq Scan on resources_resource (cost=0.00..431.32 rows=2232 width=803) (actual time=0.002..1.568 rows=2232 loops=1) 
Total runtime: 787.678 ms 
+0

你对这个字段有'Index'吗? 'resources_resource.id'和'resources_passageresource.resource_id' – 2012-03-29 00:04:32

+0

johntotetwoo有一个很好的问题。您的WHERE子句在resources_passageresource.start_ref上打上索引...那些成本值呢? – ybakos 2012-03-29 00:07:52

+1

ANALYZE的输出是什么? – ybakos 2012-03-29 00:10:37

回答

2

在我看来,DISTINCT必须用于删除连接产生的重复。所以我的问题是,为什么要在第一个地方制作副本?我不完全确定这个查询是ORM生成的必须意味着什么,但是如果重写它是一个选项,你当然可以重写它,以防止重复出现。例如,使用IN

SELECT "resources_resource"."id", 
     "resources_resource"."heading", 
     "resources_resource"."name", 
     "resources_resource"."old_name", 
     "resources_resource"."clean_name", 
     "resources_resource"."sort_name", 
     "resources_resource"."see_also_id", 
     "resources_resource"."referenced_passages", 
     "resources_resource"."resource_type", 
     "resources_resource"."ord", 
     "resources_resource"."content", 
     "resources_resource"."thumb", 
     "resources_resource"."resource_origin" 
    FROM "resources_resource" 
    WHERE "resources_resource"."id" IN (
     SELECT "resources_passageresource"."resource_id" 
      FROM "resources_passageresource" 
      WHERE "resources_passageresource"."start_ref" >= 66001001 
     ) 
    ORDER BY "resources_resource"."ord" ASC, "resources_resource"."sort_name" ASC LIMIT 5 

或使用EXISTS

SELECT "resources_resource"."id", 
     "resources_resource"."heading", 
     "resources_resource"."name", 
     "resources_resource"."old_name", 
     "resources_resource"."clean_name", 
     "resources_resource"."sort_name", 
     "resources_resource"."see_also_id", 
     "resources_resource"."referenced_passages", 
     "resources_resource"."resource_type", 
     "resources_resource"."ord", 
     "resources_resource"."content", 
     "resources_resource"."thumb", 
     "resources_resource"."resource_origin" 
    FROM "resources_resource" 
    WHERE EXISTS (
     SELECT * 
      FROM "resources_passageresource" 
      WHERE "resources_passageresource"."resource_id" = "resources_resource"."id" 
      AND "resources_passageresource"."start_ref" >= 66001001 
     ) 
    ORDER BY "resources_resource"."ord" ASC, "resources_resource"."sort_name" ASC LIMIT 5 

,当然,如果它是可以接受的完全重写查询,我也会删除列前的长桌上名名。考虑下面的例子(IN查询重写):

SELECT "id", 
     "heading", 
     "name", 
     "old_name", 
     "clean_name", 
     "sort_name", 
     "see_also_id", 
     "referenced_passages", 
     "resource_type", 
     "ord", 
     "content", 
     "thumb", 
     "resource_origin" 
    FROM "resources_resource" 
    WHERE "resources_resource"."id" IN (
     SELECT "resource_id" 
      FROM "resources_passageresource" 
      WHERE "start_ref" >= 66001001 
     ) 
    ORDER BY "ord" ASC, "sort_name" ASC LIMIT 5 
+0

+1,但不是计划者应该怎么做? – 2012-03-29 13:36:16

+0

对不起,应该做什么?当存在“DISTINCT”子句时,用“IN”内部替换连接,并且仅从两个表中的一个表(并且可能包括PK列)中抽取行? – 2012-03-29 13:53:42

+0

是的。或类似的东西。 – 2012-03-29 13:57:31

1

这是ORDER的组合BY与LIMIT。

如果你没有索引(ord,sort_name),那么我敢打赌这是性能下降的原因。或者,对于此特定查询,(start_ref,ord,sort_name)上的索引可能是必需的。最后,由于该连接,可能有左/第一个表是您的ORDER BY条件适用的表。

+0

计划者正在估计326行按顺序排序...应该是毫秒而不是秒。 OP需要发布EXPLAIN ANALYSE以获得更好的照片。 – dbenhur 2012-03-29 01:20:19

+0

此外,它需要对Unique传递进行排序以实现DISTINCT。有序的索引扫描并不总是比序列扫描+排序更好,事实上,由于寻找时间,它们通常会更糟糕。 – dbenhur 2012-03-29 01:25:03

+0

我在'ord'上有一个索引,但不在'sort_name'上,我不认为我真的需要在'sort_name'上进行排序。我会尝试删除它。 – 2012-03-29 04:01:28

1

这似乎在JOIN很长一段时间。 postgresql.conf中的默认内存设置对于任何现代计算机都太低。你记得要把它们撞到吗?