3

我在 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')

运行 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')

运行 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)

重新检查条件: (((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)

重新检查条件: (((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

4

1 回答 1

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')

写:

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);

演示设置:

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');

演示输出:

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)

对于较大的值列表,可能值得创建一个临时表并INSERTing 或COPYing 到其中,而不是使用内联VALUES列表。对于真正庞大的数据集,您可能会受益于在device_id,country.

于 2012-11-18T02:41:46.057 回答