我在Postgres 9.1数据库上运行了两个不同的SQL查询:
SELECT device_id, country FROM devices WHERE
(device_id = '97c179bd' AND country = 'US') OR
(device_id = 'bf5f50c6' AND country = 'US') OR
....
(device_id = '0e66c04d' AND country = 'US')
Run Code Online (Sandbox Code Playgroud)
运行12秒(由OR分隔的3620个子句)
和
SELECT device_id, country FROM devices WHERE
(device_id = '97c179bd' AND country = 'US') OR
(device_id = 'bf5f50c6' AND country = 'US') OR
....
(device_id = '0e66c04d' AND country = 'US') OR
(device_id = '0e66c04d' AND country = 'different')
Run Code Online (Sandbox Code Playgroud)
运行0.6秒(由OR分隔的3620个子句)
在第一个中,国家条件在每个条款中都是相同的.在第二个中,我在最后一个条款中将国家改为"不同".
第一个select语句需要12秒才能运行,第二个select语句需要0.6秒才能运行.
在第一个查询中,CPU几乎在所有12秒内没有任何磁盘读取的情况下固定在100%,表明它可能是花费这么长时间的解析器.第二个查询不会发生这种情况.
我运行了EXPLAIN ANALYZE并获得了两个查询分解的完全相同的结果.
这里发生了什么?为什么在每个WHERE子句语句中第二个条件相同会导致查询时间长得多?
编辑:
第一次查询的前几行EXPLAIN ANALYZE:
设备上的位图堆扫描(成本= 18807.49..52584.74行= 3564宽度= 39)(实际时间= 73.994..78.994行= 3620循环= 1)
重新检查Cond:(((device_id = '97c179bd':: text)AND(country ='US':: text))OR((device_id ='bf5f50c6':: text)AND(country ='US':: text) )OR((device_id ='3b05d35a':: text)AND(country ='US':: text))OR((device_id ='c6684bc0':: text)AND(country ='US':: text))或((device_id ='0e66c04d':: text)AND(country ='US':: text))
第二个查询的前几行EXPLAIN ANALYZE:
设备上的位图堆扫描(成本= 18806.59..85317.68行= 3563宽度= 39)(实际时间= 74.737..79.769行= 3619个循环= 1)
重新检查Cond:(((device_id = '97c179bd':: text)AND(country ='US':: text))OR((device_id ='bf5f50c6':: text)AND(country ='US':: text) )OR((device_id ='3b05d35a':: text)AND(country ='US':: text))OR((device_id ='c6684bc0':: text)AND(country ='US':: text))或((device_id ='0e66c04d':: text)AND(country ='US':: text))
编辑2:
以下是两个EXPLAIN ANALYZE结果:
虽然不是对性能差异的解释,但解决此问题的最佳方法是重构您的查询,不要使用超过3000个OR子句.那太可怕了.
代替:
SELECT device_id, country FROM devices WHERE
(device_id = '97c179bd' AND country = 'US') OR
(device_id = 'bf5f50c6' AND country = 'US') OR
....
(device_id = '0e66c04d' AND country = 'US')
Run Code Online (Sandbox Code Playgroud)
写:
SELECT d.device_id, d.country
FROM devices d
INNER JOIN (VALUES
('97c179bd','US'),
('bf5f50c6','US'),
('0e66c04d','US')
) v(device_id,country) USING (device_id,country);
Run Code Online (Sandbox Code Playgroud)
演示设置:
create table devices (device_id text, country text, primary key (device_id,country));
insert into devices values
('97c179bd','US'),
('bf5f50c6','US'),
('0e66c04d','US'),('0e66c04d','different');
Run Code Online (Sandbox Code Playgroud)
演示输出:
regress=> SELECT d.device_id, d.country
FROM devices d
INNER JOIN (VALUES
('97c179bd','US'),
('bf5f50c6','US'),
('0e66c04d','US')
) v(device_id,country) USING (device_id,country);
device_id | country
-----------+---------
97c179bd | US
bf5f50c6 | US
0e66c04d | US
(3 rows)
Run Code Online (Sandbox Code Playgroud)
对于较大的值列表,它可能是值得创建临时表和INSERT荷兰国际集团或COPY荷兰国际集团到它,而不是使用一种在线VALUES列表.对于真正庞大的数据集,您可以从创建唯一索引中受益device_id,country.