为什么多个WHERE子句SQL SELECT语句在每个子句中有两个条件,其中一个条件具有相同的值需要很长时间?

ssk*_*tes 3 postgresql select

我在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结果:

https://dl.dropbox.com/u/4747107/explain/query1slow.htm

https://dl.dropbox.com/u/4747107/explain/query2fast.htm

Cra*_*ger 6

虽然不是对性能差异的解释,但解决此问题的最佳方法是重构您的查询,不要使用超过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.