Postgresql查询优化不允许内部/外部联接

时间:2012-10-20 03:24:25

标签: database postgresql join query-optimization postgresql-performance

我得到了这个查询以优化POSTGRESQL 9.2:

SELECT C.name, COUNT(DISTINCT I.id) AS NumItems, COUNT(B.id)
FROM Categories C INNER JOIN Items I ON(C.id = I.category) 
                  INNER JOIN Bids B ON (I.id = B.item_id)
GROUP BY C.name

作为我学校作业的一部分。

我已在相应的表格上创建了​​这些索引:items(category) - >第二个b +树,bids(item_id) - >第二个b +树,以及categories(id) - >这里的主要指数,

奇怪的是,PostgreSQL正在对我的Items,Categories和Bids表进行顺序扫描,当我设置enable_seqscan=off时,索引搜索结果比下面的结果更可怕。 / p>

当我在PostgreSQL中运行解释时,这就是结果:请不要删除错误,因为它们是重要的!

GroupAggregate  (cost=119575.55..125576.11 rows=20 width=23) (actual time=6912.523..9459.431 rows=20 loops=1)
  Buffers: shared hit=30 read=12306, temp read=6600 written=6598
  ->  Sort  (cost=119575.55..121075.64 rows=600036 width=23) (actual time=6817.015..8031.285 rows=600036 loops=1)
        Sort Key: c.name
        Sort Method: external merge  Disk: 20160kB
        Buffers: shared hit=30 read=12306, temp read=6274 written=6272
        ->  Hash Join  (cost=9416.95..37376.03 rows=600036 width=23) (actual time=407.974..3322.253 rows=600036 loops=1)
              Hash Cond: (b.item_id = i.id)
              Buffers: shared hit=30 read=12306, temp read=994 written=992
              ->  Seq Scan on bids b  (cost=0.00..11001.36 rows=600036 width=8) (actual time=0.009..870.898 rows=600036 loops=1)
                    Buffers: shared hit=2 read=4999
              ->  Hash  (cost=8522.95..8522.95 rows=50000 width=19) (actual time=407.784..407.784 rows=50000 loops=1)
                    Buckets: 4096  Batches: 2  Memory Usage: 989kB
                    Buffers: shared hit=28 read=7307, temp written=111
                    ->  Hash Join  (cost=1.45..8522.95 rows=50000 width=19) (actual time=0.082..313.211 rows=50000 loops=1)
                          Hash Cond: (i.category = c.id)
                          Buffers: shared hit=28 read=7307
                          ->  Seq Scan on items i  (cost=0.00..7834.00 rows=50000 width=8) (actual time=0.004..144.554 rows=50000 loops=1)
                                Buffers: shared hit=27 read=7307
                          ->  Hash  (cost=1.20..1.20 rows=20 width=19) (actual time=0.062..0.062 rows=20 loops=1)
                                Buckets: 1024  Batches: 1  Memory Usage: 1kB
                                Buffers: shared hit=1
                                ->  Seq Scan on categories c  (cost=0.00..1.20 rows=20 width=19) (actual time=0.004..0.028 rows=20 loops=1)
                                      Buffers: shared hit=1
Total runtime: 9473.257 ms

请参阅this plan on explain.depesz.com

我只是想知道为什么会发生这种情况,也就是说为什么索引会使查询与顺序扫描相比变得非常慢。

编辑: 我想我已经设法通过postgresql文档发现了一些东西。 Postgresql决定对某些表格进行seq扫描,例如出价和项目,因为它预测它必须检索表格中的每一行(比较实际时间之前括号中的行数和实际时间部分中的行数) )。顺序扫描更好地检索所有行。那部分什么都做不了。

我为categories(name)创建了额外的索引,下面的结果就是我所拥有的。它以某种方式改进但现在散列连接被嵌套循环替换。有什么线索为什么?

GroupAggregate  (cost=0.00..119552.02 rows=20 width=23) (actual time=617.330..7725.314 rows=20 loops=1)
  Buffers: shared hit=178582 read=37473 written=14, temp read=2435 written=436
  ->  Nested Loop  (cost=0.00..115051.55 rows=600036 width=23) (actual time=0.120..6186.496 rows=600036 loops=1)
        Buffers: shared hit=178582 read=37473 written=14, temp read=2109 written=110
        ->  Nested Loop  (cost=0.00..26891.55 rows=50000 width=19) (actual time=0.066..2827.955 rows=50000 loops=1)
              Join Filter: (c.id = i.category)
              Rows Removed by Join Filter: 950000
              Buffers: shared hit=2 read=7334 written=1, temp read=2109 written=110
              ->  Index Scan using categories_name_idx on categories c  (cost=0.00..12.55 rows=20 width=19) (actual time=0.039..0.146 rows=20 loops=1)
                    Buffers: shared hit=1 read=1
              ->  Materialize  (cost=0.00..8280.00 rows=50000 width=8) (actual time=0.014..76.908 rows=50000 loops=20)
                    Buffers: shared hit=1 read=7333 written=1, temp read=2109 written=110
                    ->  Seq Scan on items i  (cost=0.00..7834.00 rows=50000 width=8) (actual time=0.007..170.464 rows=50000 loops=1)
                          Buffers: shared hit=1 read=7333 written=1
        ->  Index Scan using bid_itemid_idx on bids b  (cost=0.00..1.60 rows=16 width=8) (actual time=0.016..0.036 rows=12 loops=50000)
              Index Cond: (item_id = i.id)
              Buffers: shared hit=178580 read=30139 written=13
Total runtime: 7726.392 ms

如果计划更好,请查看计划here

我通过在类别(id)和items(category)上创建索引,设法将其减少到114062.92。 Postgresql使用这两个索引来获得114062.92的成本。 但是,现在postgresql通过不使用索引与我玩游戏!为什么这么马车?

3 个答案:

答案 0 :(得分:1)

感谢您在未被询问的情况下发布EXPLAIN输出,以及EXPLAIN (BUFFERS, ANALYZE)

查询性能问题的一个重要部分可能是外部排序计划节点,它正在使用临时文件进行磁盘上合并排序:

Sort Method: external merge Disk: 20160kB

您可以通过设置:

在内存中进行此类排序
SET work_mem = '50MB';

在运行查询之前。此设置也可以按用户,按数据库设置,也可以在postgresql.conf中全局设置。

我不相信添加索引会有很大的好处,因为查询目前是结构化的。它需要读取和连接所有三个表中的所有行,并且散列连接可能是最快的方法。

我怀疑还有其他方法来表达那个将使用完全不同且更有效的执行策略的查询,但我对于他们可能是什么并且不想要的是什么有着大脑的褪色花时间弥补虚拟桌子来玩。更多work_mem应该可以显着改善查询。

答案 1 :(得分:0)

从查询计划中我们可以看到:
1.结果和类别有20条记录 2.具有类别的项目是所有项目数量的5%  “通过连接过滤器删除的行:950000”
 顺序扫描中的“行= 50000”
3.匹配的出价是行= 600036(你能给我们出价总数吗?)
4.每个类别都有出价?

因此我们希望在项目(类别)和出价(item_id)上使用索引。 我们也希望在内存中进行排序。

 select  
   (select name from Categories where id = foo.category) as name, 
   count(foo.id),  
   sum(foo.bids_count)  
 from 
   (select 
      id,  
      category,  
      (select count(item_id) from Bids where item_id = i.id) as bids_count  
    from Items i  
    where category in (select id from Categories)  
      and exists (select 1 from Bids where item_id = i.id)  
   ) as foo  
  group by foo.category  
  order by name

当然,你必须记住它严格依赖于第1点和第2点的数据。

如果4为真,则可以从查询中删除存在的部分。

任何建议或想法?

答案 2 :(得分:0)

请注意,如果bids的大小系统且明显大于items,那么遍历items两次实际上可能更便宜(尤其如果items适合RAM)而不是从连接结果中挑选那些不同的项ID(即使你在内存中排序)。此外,根据Postgres管道如何从重复表中提取数据,即使在不利的负载或内存条件下也可能会受到限制(这可能是您在pgsql-general上可以提出的一个很好的问题。)

SELECT name, IC.cnt, BC.cnt FROM
Categories C,
( SELECT category, count(1) cnt from Items I GROUP BY category ) IC,
( SELECT category, count(1) cnt from Bids B INNER JOIN Items I ON (I.id = B.item_id) GROUP BY category ) BC
WHERE IC.category=C.id AND BC.category=id;

便宜多少钱?给予足够的缓存至少4倍,即610ms与2500ms(内存中排序),20个类别,50k项目和600k出价,并且在我的机器上文件系统缓存刷新后仍然快于2x。

请注意,上述内容并非直接替代原始查询;例如,它假设类别ID和名称之间存在1:1映射(这可能是一个非常合理的假设;如果不是,只需SUM(BC.cnt)SUM(IC.cnt)作为{{1} }),但更重要的是每个类别的项目计数包括没有出价的项目,与原始GROUP BY name不同。如果只需要出价项目计数,则可以在IC子查询中添加INNER JOIN;这也将第二次遍历WHERE EXISTS (SELECT 1 FROM Bids B where item_id=I.id)(在我的情况下,对现有的~600ms计划增加了约200ms的惩罚,仍然远低于2400ms。)