小数据集的错误查询计划使其非常慢
Bad query plan for small dataset makes it very slow
问题
我的查询(简单的几个连接)运行当我有少量数据(~50k 行)时非常慢,但当我有大量数据时运行s 很快数据(~180k 行)。时差很大,从几秒到将近半小时。
尝试次数
我重新检查了连接,它们都是正确的。此外,在 运行 查询之前,我有 运行 一个 VACUUM ANALYZE
到 table,但它没有解决任何问题。我还检查了是否有锁以任何方式阻止了查询,或者连接在任何情况下都很慢,但它们不是故障的情况。
因此,我去检查EXPLAIN
的输出。阅读结果后,我发现在缓慢的情况下,它会进行不必要的额外排序,并且会卡在一个嵌套的 for 循环中,而在我有更多数据的情况下,这个循环是不存在的。我不确定如何告诉 postgres 执行与更大数据集方案相同的计划。
根据评论,我也尝试不使用 CTE,但它也无济于事:仍然进行嵌套循环和排序。
详情:
- Postgres 版本:
PostgreSQL 12.3
- 完整查询文本:
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
;
- Table 定义:
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)
- 基数:慢的情况~50k,快的情况~180k
- 查询计划:两种情况下
EXPLAIN (BUFFERS, ANALYZE)
的输出 - 慢速情况 https://explain.depesz.com/s/5JDw, fast case: https://explain.depesz.com/s/JMIL
- 附加信息:相关内存配置为:
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。
您可以通过选择 temp-tables 而不是使用 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
问题
我的查询(简单的几个连接)运行当我有少量数据(~50k 行)时非常慢,但当我有大量数据时运行s 很快数据(~180k 行)。时差很大,从几秒到将近半小时。
尝试次数
我重新检查了连接,它们都是正确的。此外,在 运行 查询之前,我有 运行 一个 VACUUM ANALYZE
到 table,但它没有解决任何问题。我还检查了是否有锁以任何方式阻止了查询,或者连接在任何情况下都很慢,但它们不是故障的情况。
因此,我去检查EXPLAIN
的输出。阅读结果后,我发现在缓慢的情况下,它会进行不必要的额外排序,并且会卡在一个嵌套的 for 循环中,而在我有更多数据的情况下,这个循环是不存在的。我不确定如何告诉 postgres 执行与更大数据集方案相同的计划。
根据评论,我也尝试不使用 CTE,但它也无济于事:仍然进行嵌套循环和排序。
详情:
- Postgres 版本:
PostgreSQL 12.3
- 完整查询文本:
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
;
- Table 定义:
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)
- 基数:慢的情况~50k,快的情况~180k
- 查询计划:两种情况下
EXPLAIN (BUFFERS, ANALYZE)
的输出 - 慢速情况 https://explain.depesz.com/s/5JDw, fast case: https://explain.depesz.com/s/JMIL - 附加信息:相关内存配置为:
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。
您可以通过选择 temp-tables 而不是使用 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