小数据集的错误查询计划使其速度非常慢

问题描述

问题

我有少量数据(约5万行)时,我的查询(带有少量联接的简单查询)运行极慢,而当我有大量数据(约18万行)时,查询运行很快。时差很大,因为从几秒钟到将近半个小时。

尝试

我重新检查了联接,它们都是正确的。另外,在运行查询之前,我已经对表运行了VACUUM ANALYZE,但是它并不能解决任何问题。我还检查了是否有锁以任何方式阻止查询,或者是否在任何情况下连接速度都很慢,但不是错误的情况。

因此,我去检查了EXPLAIN输出。阅读结果后,我发现在慢速情况下,它会进行不必要的额外排序,并且会卡在嵌套的for循环中,这种情况在我拥有更多数据的情况下是不存在的。我不确定如何告诉postgres做与更大数据集场景相同的计划。

基于评论,我也尝试不使用CTE,但这也无济于事:仍然进行嵌套循环和排序。

详细信息:

  1. Postgres版本Postgresql 12.3
  2. 完整查询文本
WITH t0 AS (SELECT * FROM original_table WHERE id=0),t1 AS (SELECT * FROM original_table WHERE id=1),t2 AS (SELECT * FROM original_table WHERE id=2),t3 AS (SELECT * FROM original_table WHERE id=3),t4 AS (SELECT * FROM original_table WHERE id=4)
 
SELECT
    t0.dtime,t1.dtime,t3.dtime,t3.dtime::date,t4.dtime,t1.first_id,t1.field,t1.second_id,t1.third_id,t2.fourth_id,t4.fourth_id
FROM t1
LEFT JOIN t0 ON t1.first_id=t0.first_id
     JOIN t2 ON t1.first_id=t2.first_id AND t1.second_id = t2.second_id AND t1.third_id = t2.third_id
     JOIN t3 ON t1.first_id=t3.first_id AND t1.second_id = t3.second_id AND t1.third_id = t3.third_id
     JOIN t4 ON t1.first_id=t4.first_id AND t1.second_id = t4.second_id AND t1.fourth_id= t4.third_id
ORDER BY t3.dtime
;
  1. 表定义
Column    |            Type
----------+----------------------------
id        | smallint
dtime     | timestamp without time zone
first_id  | character varying(10)
second_id | character varying(10)
third_id  | character varying(10)
fourth_id | character varying(10)
field     | character varying(10)
  1. 基数:慢速情况约50k,快情况约180k
  2. 查询计划:两种情况下EXPLAIN (BUFFERS,ANALYZE)输出-慢速情况https://explain.depesz.com/s/5JDw快速情况:https://explain.depesz.com/s/JMIL
  3. 其他信息:相关的内存配置为:
      name     | current_setting |        source
---------------+-----------------+---------------------
max_stack_dept | 2MB             | environment variable
max_wal_size   | 1GB             | configuration file
min_wal_size   | 80MB            | configuration file
shared_buffers | 128MB           | configuration file

解决方法

这在SQL Server上经常发生。
通常会导致速度缓慢的原因是,它每加入一行就执行一次CTE。

您可以通过选择临时表而不使用CTE来防止这种情况的发生。
我认为对于PostgreSQL同样如此,但是我没有对其进行测试:

DROP TABLE IF EXISTS tempT0;
DROP TABLE IF EXISTS tempT1;
CREATE TEMP TABLE tempT0 AS SELECT * FROM original_table WHERE id=0; 
CREATE TEMP TABLE tempT1 AS SELECT * FROM original_table WHERE id=1; 
[... etc]

FROM tempT1 AS t1
LEFT JOIN tempT0 AS t0 ON  t1.first_id=t0.first_id