我当然意识到解决这些问题可能很复杂并需要大量信息,但我希望这个特定情况有一个已知的问题或解决方法。我已经缩小了导致次优查询计划的查询中的更改(这是运行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/。
谢谢!
答案 0 :(得分:0)
我看到有一种排序作为合并连接的一部分。一旦超过某个阈值,进行合并连接所需的排序操作就会被认为过于昂贵,并且估计散列连接会更便宜。它可能更昂贵(时间明智)但在CPU消耗方面更便宜以这种方式运行查询。