Postgres 9.4:如何修复查询计划程序的哈希选择加入任何运行速度慢10倍的ARRAY查找

时间:2015-09-23 15:00:07

标签: postgresql postgresql-9.4 database-tuning query-tuning

我当然意识到解决这些问题可能很复杂并需要大量信息,但我希望这个特定情况有一个已知的问题或解决方法。我已经缩小了导致次优查询计划的查询中的更改(这是运行Postgres 9.4)。

以下查询在大约50毫秒内运行。 tag_device表是一个包含约200万个条目的联结表,devices表有大约150万个条目,而标签表有大约500,000个条目(注意:实际的IP值只是组成)。

WITH inner_query AS (
  SELECT * FROM tag_device
  INNER JOIN tags  ON tag_device.tag_id = tags.id
  INNER JOIN devices ON tag_device.device_id = devices.id
  WHERE devices.device_ip <<= ANY(ARRAY[
    '10.0.0.1', '10.0.0.2', '10.0.0.5', '11.1.1.1', '12.2.2.35','13.0.0.1', '15.0.0.8', '1.160.0.1', '17.1.1.24', '18.2.2.1',
    '10.0.0.6', '10.0.0.21', '10.0.0.52', '11.1.1.2', '12.2.2.34','13.0.0.2', '15.0.0.7', '1.160.0.2', '17.1.1.23', '18.2.2.2',
    '10.0.0.7', '10.0.0.22', '10.0.0.53', '11.1.1.3', '12.2.2.33','13.0.0.3', '15.0.0.6', '1.160.0.3', '17.1.1.22', '18.2.2.3'
    ]::iprange[])
 ))
 SELECT * FROM inner_query LIMIT 100 OFFSET 0;

有几点需要注意。 device_ip正在使用ip4r模块(https://github.com/RhodiumToad/ip4r)来提供ip范围查找,此列上有一个gist索引。上述查询使用以下查询计划在大约50ms内运行:

Limit  (cost=140367.19..140369.19 rows=100 width=239)
  CTE inner_query
    ->  Nested Loop  (cost=40147.63..140367.19 rows=56193 width=431)
          ->  Merge Join  (cost=40147.20..113345.15 rows=56193 width=261)
                Merge Cond: (tag_device.device_id = devices.id)
                ->  Index Scan using tag_device_device_id_idx on tag_device  (cost=0.43..67481.36 rows=1900408 width=51)
                ->  Materialize  (cost=40136.82..40402.96 rows=53228 width=210)
                      ->  Sort  (cost=40136.82..40269.89 rows=53228 width=210)
                            Sort Key: devices.id
                            ->  Bitmap Heap Scan on devices  (cost=1489.12..30498.45 rows=53228 width=210)
                                  Recheck Cond: (device_ip <<= ANY ('{10.0.0.1,10.0.0.2,10.0.0.5,11.1.1.1,12.2.2.2,13.0.0.1,15.0.0.2,1.160.0.5,17.1.1.1,18.2.2.2,10.0.0.1,10.0.0.2,10.0.0.5,11.1.1.1,12.2.2.2,13.0.0.1,15.0.0.2,1.160.0.5,17.1.1.1,18.2.2.2 (...)
                                  ->  Bitmap Index Scan on devices_iprange_idx  (cost=0.00..1475.81 rows=53228 width=0)
                                        Index Cond: (device_ip <<= ANY ('{10.0.0.1,10.0.0.2,10.0.0.5,11.1.1.1,12.2.2.2,13.0.0.1,15.0.0.2,1.160.0.5,17.1.1.1,18.2.2.2,10.0.0.1,10.0.0.2,10.0.0.5,11.1.1.1,12.2.2.2,13.0.0.1,15.0.0.2,1.160.0.5,17.1.1.1,18.2 (...)
          ->  Index Scan using tags_id_pkey on tags  (cost=0.42..0.47 rows=1 width=170)
                Index Cond: (id = tag_device.tag_id)
  ->  CTE Scan on inner_query  (cost=0.00..1123.86 rows=56193 width=239)

如果我增加查找的ARRAY中的IP地址数,则查询计划会发生变化并变得非常慢。因此,在快速版本的查询中,数组中有30个项目。如果我将此增加到数组中的80个项目,则查询计划会更改并变得非常慢(超过10x)查询在所有其他方面保持不变。新的查询计划使用散列连接而不是合并连接和嵌套循环。这是一个新的(慢得多)查询计划,当阵列中有80个项目而不是30个时。

Limit  (cost=204482.39..204484.39 rows=100 width=239)
  CTE inner_query
    ->  Hash Join  (cost=85839.13..204482.39 rows=146180 width=431)
          Hash Cond: (tag_device.tag_id = tags.id)
          ->  Hash Join  (cost=51368.40..145023.34 rows=146180 width=261)
                Hash Cond: (tag_device.device_id = devices.id)
                ->  Seq Scan on tag_device  (cost=0.00..36765.08 rows=1900408 width=51)
                ->  Hash  (cost=45580.57..45580.57 rows=138466 width=210)
                      ->  Bitmap Heap Scan on devices  (cost=3868.31..45580.57 rows=138466 width=210)
                            Recheck Cond: (device_ip <<= ANY ('{10.0.0.1,10.0.0.2,10.0.0.5,11.1.1.1,12.2.2.35,13.0.0.1,15.0.0.8,1.160.0.1,17.1.1.24,18.2.2.1,10.0.0.6,10.0.0.21,10.0.0.52,11.1.1.2,12.2.2.34,13.0.0.2,15.0.0.7,1.160.0.2,17.1.1.23,18.2.2.2 (...)
                            ->  Bitmap Index Scan on devices_iprange_idx  (cost=0.00..3833.70 rows=138466 width=0)
                                  Index Cond: (device_ip <<= ANY ('{10.0.0.1,10.0.0.2,10.0.0.5,11.1.1.1,12.2.2.35,13.0.0.1,15.0.0.8,1.160.0.1,17.1.1.24,18.2.2.1,10.0.0.6,10.0.0.21,10.0.0.52,11.1.1.2,12.2.2.34,13.0.0.2,15.0.0.7,1.160.0.2,17.1.1.23,18.2 (...)
          ->  Hash  (cost=16928.88..16928.88 rows=475188 width=170)
                ->  Seq Scan on tags  (cost=0.00..16928.88 rows=475188 width=170)
  ->  CTE Scan on inner_query  (cost=0.00..2923.60 rows=146180 width=239)

上面的查询使用它的默认查询计划运行大约500毫秒(慢10倍)。如果我使用SET enable_hashjoin= OFF;关闭散列连接,则查询计划将返回使用合并连接,并在阵列中以80个项目再次运行~50ms。

此处唯一的变化是ARRAY中正在查找的项目数。

有没有人对计划员为什么做出导致大幅减速的糟糕选择有任何想法?

数据库完全适合内存并位于SSD上。

我还想指出我正在使用CTE,因为我遇到了一个问题,当我在查询的限制中添加时,计划程序不会在tag_device表上使用索引。基本上这里描述的问题是:http://thebuild.com/blog/2014/11/18/when-limit-attacks/

谢谢!

1 个答案:

答案 0 :(得分:0)

我看到有一种排序作为合并连接的一部分。一旦超过某个阈值,进行合并连接所需的排序操作就会被认为过于昂贵,并且估计散列连接会更便宜。它可能更昂贵(时间明智)但在CPU消耗方面更便宜以这种方式运行查询。