6

我给这个查询PostgreSQL的9.2优化:PostgreSQL的查询优化无内/外连接允许

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) - > 2ndary B +树,bids(item_id) - > 2ndary B +树,并categories(id) - >初级这里指数,

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

当我在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正在和我一起玩游戏,不使用索引!为什么它很奇怪?

+0

要按名称分组,它虽然要按名称排序,如果你在categories.name上添加索引,它可能会更好。我还会在items.id上放置一个索引来加速独立(i.id)和加入到b.item_id。 –

+0

另外,请记住,Postgres“解释计划”可能在您进行“真空分析”之后才会给您带来好的结果。 –

+0

感谢回复@PaulTomblin。 Yeap我确信我在解释计划之前使用了真空分析以获得准确的结果。 项目。id是items表的主键,因此它将包含聚集索引。 – ImNoob

回答

1

Thankyou发布EXPLAIN输出没有被问及,EXPLAIN (BUFFERS, ANALYZE)

查询的性能问题的显著部分很可能是外部排序规划节点,这是做磁盘上的归并排序与临时文件:

Sort Method: external merge Disk: 20160kB 

你可以做这样的记忆通过设置:

SET work_mem = '50MB'; 

运行您的查询之前。此设置也可以根据用户,每个数据库或全局设置为postgresql.conf

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

我怀疑还有其他的方式来表达这个查询,它会使用完全不同的和更高效的执行策略,但是我的大脑褪色了大约他们可能是什么并且不想花费时间组成虚拟桌子玩耍。更多work_mem应该显着改进查询。

+0

感谢您的帮助。当然,设置work_mem戏剧性地提高了性能。不幸的是我不能在我的答案中使用它。我一直在为所涉及的所有属性添加索引,而唯一看起来有用的索引是通过更快地创建分组的类别(名称)索引。除此之外,没有其他的索引更好。将发布任何更新,如果我设法更好地更新它。 – ImNoob

+0

索引与外键约束之间存在差异。 OP需要FK约束,而不是索引。另外:正如@Craig Ringer所说:查询需要* all *行(来自FK约束的所有3个表,或者使用索引和正确的统计数据时可能更少)。最后的排序/ group by是一个杀手,因为它是正交于其他关键维度 – wildplasser

+0

@ImNoob如果你发布一个模式和一些虚拟数据到sqlfiddle.com,可以更容易地尝试完全不同的查询方法 –

0

从查询计划,我们可以看到:
1.结果和类别有20条记录
2.项目与类别是
项目的所有金额的5%“的行通过加入过滤器去除:950000”
“rows = 50000”in sequential scan
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是真的,你可以删除存在从查询部分。

任何建议或想法?

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; 

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

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