当我在Postgres 9.1中运行以下4个查询时,我得到了非常令人费解的结果。查询1,2和4的行为符合预期,快速返回结果,但查询3运行时间要长得多。
plant_data是一个包含约6700万条记录的表,并以所有预期的方式使用btree索引编制索引。
-- Query 1
SELECT COUNT(*)
FROM plant_data
WHERE sensor_id IN (
SELECT name FROM nomenclature
WHERE resolution = '1DAY' LIMIT 1 OFFSET 0)
使用索引,在<中运行14ms(返回0) http://explain.depesz.com/s/dx9
-- Query 2
SELECT COUNT(*)
FROM plant_data
WHERE sensor_id IN (
SELECT name FROM nomenclature
WHERE resolution = '1DAY' LIMIT 1 OFFSET 1)
还使用索引,在<中运行14毫秒(返回29) http://explain.depesz.com/s/Zfl
-- Query 3
SELECT COUNT(*)
FROM plant_data
WHERE sensor_id IN (
SELECT name FROM nomenclature
WHERE resolution = '1DAY' LIMIT 2)
seq。扫描,在261秒内运行(返回29) http://explain.depesz.com/s/Xui
-- Query 4
select count(*)
FROM plant_data
WHERE sensor_id IN (
'BUR_PCLo_SAMPww_C_COD_1DAY', -- First 2 results in
'BUR_ANDIi1_FLOraw_I_VOL_1DAY') -- nomenclature table
使用索引,在<中运行14ms,(返回29) http://explain.depesz.com/s/iQc
问题:为什么查询3不使用索引并快速返回结果?
表格中的数据往往遵循相当规律的模式。从广义上讲,有4组数据......首先是一个块,它往往是2800个条目的块,具有相同的sensor_id。第二个是96个值的块,具有相同的sensor_id。第三个是12个块,具有相同的sensor_id,第四个是块1个。我们倾向于得到~50个类型1然后是~50个类型2,接着是~50个类型3,接着是少数类型4,之前循环回到开头。上面查询中的数据都是类型4(选择因为结果集可以管理以手动运行查询)。
总结,我们选择的数据往往分散在我们的6700万条记录中。
完整的表和索引结构:
CREATE TABLE plant_data
(
id serial NOT NULL,
date timestamp without time zone,
sensor_id text,
value double precision,
day_id integer,
week_id integer,
month_id integer,
year_id integer,
resolution integer
)
WITH (
OIDS=FALSE
);
ALTER TABLE plant_data
OWNER TO inners_data;
CREATE INDEX plant_data_date_index
ON plant_data
USING btree
(date);
CREATE INDEX plant_data_day_id
ON plant_data
USING btree
(day_id);
CREATE INDEX plant_data_month_id
ON plant_data
USING btree
(month_id);
CREATE INDEX plant_data_resolution_idx
ON plant_data
USING btree
(resolution);
CREATE INDEX plant_data_sensor_id_date_index
ON plant_data
USING btree
(date, sensor_id COLLATE pg_catalog."default");
CREATE INDEX plant_data_sensor_id_index
ON plant_data
USING btree
(sensor_id COLLATE pg_catalog."default");
CREATE INDEX plant_data_week_id
ON plant_data
USING btree
(week_id);
CREATE INDEX plant_data_year_id
ON plant_data
USING btree
(year_id);
编辑:上面更新解释说明分析,添加了关于表格中的索引,表格结构和数据模式的信息
答案 0 :(得分:2)
评论太长了。
以下是快速还是慢速运行?
-- Query 1
SELECT COUNT(*)
FROM plant_data
WHERE exists (SELECT 1 FROM nomenclature
WHERE resolution = '1DAY' and sensor_id = name LIMIT 2
);
首先,Postgres会对带有常量的in
列表很聪明。第四个快速回归并不奇怪。对于前两个示例,我怀疑(或者可能是这个示例证明)Postgres优化了一个子查询,该子查询返回一行与返回多行的子查询不同。也就是说,第一个查询被识别为返回一行并进行优化,就好像它是:
SELECT COUNT(*)
FROM plant_data
WHERE sensor_id = (
SELECT name FROM nomenclature
WHERE resolution = '1DAY' LIMIT 1 OFFSET 0)
但是,对于多行,Postgres采用了不同的方法,可能涉及每次重新执行内部查询。这个查询是快还是慢?
-- Query 2
with list as (
SELECT name FROM nomenclature
WHERE resolution = '1DAY' LIMIT 2
)
SELECT COUNT(*)
FROM plant_data
WHERE sensor_id IN (select name from list);
编辑:
您的解释确实解释了性能的差异。使用limit 1
,Postgres正在进行嵌套循环连接,当只有一行要循环时,它恰好是真正的。它也可以真正快速循环两行。作为嵌套循环的一部分,它使用resolution
上的索引。
在limit 2
案例中,Postgres选择进行“哈希半连接”。这意味着需要处理较大表中的数据。更糟糕的是,数据不是来自索引,而是扫描整个表。 Arrrg!
我不知道为什么Postgres优化器在这种情况下是如此不理想,避免使用完全合理的索引。
希望Postgres大师能够更加深入了解。
答案 1 :(得分:0)
切线,您的子选择是在没有订单的情况下进行限制。这是一种不好的做法,因为这种查询的结果是不确定的。 (也可能会使计划者感到困惑)
你的查询认为他们会找到700,000行,但实际上只找到0或29.当postgres认为选择性与实际情况如此不同时,postgres低估了索引的值也就不足为奇了。
表格中sensor_id的分布是什么?这种分布可能存在巨大的偏差。如果你将偏斜推得足够高,即使在LIMIT为1的情况下,你甚至可能会选择seq扫描。
基本上你的subselect只返回罕见的sensor_id(总是这样吗?)而不是常见的,但是postgresql并不提前知道。
增加default_statistics_target和分析可能会有所帮助,但我不会对此感到震惊。升级到9.2或9.3也可能有所帮助,因为“仅索引扫描”在这里可能有效。
唉,最安全的事情可能就是单独运行子选择,然后根据结果汇总查询4。
答案 2 :(得分:0)
问题的部分原因是PostgreSQL在构造方面不是很好:
WHERE sensor_id IN (
SELECT name FROM nomenclature
WHERE resolution = '1DAY' LIMIT 1 OFFSET 0)
OFFSET
充当优化围栏,阻止PostgreSQL将查询重组为更有效的形式。由于缺少ORDER BY
以使查询明确无误,情况会更糟。
一般来说,用EXISTS
编写此类查询要清楚得多:
WHERE EXISTS (
SELECT 1
FROM nomenclature n
WHERE n.resolution = '1DAY'
AND sensor_id = n.name)
有关行为原因的详细评论,我认为您需要发布到pgsql-performance邮件列表。我目前正在研究查询规划器代码,但不是优化器逻辑,只有真正了解优化器的人才可能真正能够很好地回答这个问题。