为什么其中一个查询运行得如此之慢?

时间:2014-02-03 14:53:26

标签: sql postgresql

当我在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);

编辑:上面更新解释说明分析,添加了关于表格中的索引,表格结构和数据模式的信息

3 个答案:

答案 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邮件列表。我目前正在研究查询规划器代码,但不是优化器逻辑,只有真正了解优化器的人才可能真正能够很好地回答这个问题。