===== 根据反馈更新 =====
由于最初的一些问题需要澄清,这里是一个非常简单的版本。
WITH my_var AS (
SELECT date '2016-01-01' as a_date
--, generate_series(1, 40) as numbers
)
Select generate_series(1, 100000) as numbers, my_var.a_date from my_var
execution time: 411ms
"CTE Scan on my_var (cost=0.01..5.03 rows=1000 width=4)"
" CTE my_var"
" -> Result (cost=0.00..0.01 rows=1 width=0)"
现在,如果我们取消注释
中的 generate_seriesWITH my_var AS (
SELECT date '2016-01-01' as a_date
, generate_series(1, 40) as numbers
)
Select generate_series(1, 100000) as numbers, my_var.a_date from my_var
execution time: 16201ms
"CTE Scan on my_var (cost=5.01..5022.51 rows=1000000 width=4)"
" CTE my_var"
" -> Result (cost=0.00..5.01 rows=1000 width=0)"
这里的要点是,如果 generate_series(1, 40) 应该只执行一次,为什么查询要花这么长时间才能完成。在这种情况下,我什至没有使用主查询中设置的“数字”,它仍然需要花费大量时间才能完成。
=====原始查询=====
我在使用子查询和/或 CTE 的 PostgreSQL 9.x 中遇到了一个有趣的性能问题。
...老实说,我不太确定这是一个“错误”还是只是用户(即我)对 CTE/子查询和/或使用 generate_series 函数的理解。
我一直在使用 CTE 编写一些高级且较长的查询。我一直在使用一种技术,我将一个静态变量(例如日期)放入一个主 CTE 中,该 CTE 会过滤所有其他查询。当您需要使用不同参数运行它时,我们的想法是通过长查询进行一组更改而不是大量更改。
这方面的一个例子是:
WITH dates AS (
SELECT
date '2013-01-01' AS start_date,
date_trunc('month', current_date) AS end_date
)
SELECT * from dates, sometable where somedate between start_date and end_date
execution time: ~650ms
因此,我的理解是 CTE 运行一次,但在遇到性能问题后,这显然不是正在发生的事情。例如,如果我修改 CTE 以包含一个 generate_series:
WITH dates AS (
SELECT
date '2013-01-01' AS start_date,
date_trunc('month', current_date) AS end_date,
generate_series(1, 10) AS somelist
)
SELECT * from dates, sometable where somedate between start_date and end_date
and myval in (somelist)
execution time: ~23000ms
由于一些严重的性能问题(慢了数千倍),我起初认为 generate_series() 正在为 somelist 分配“generate_series”函数,然后作为主表中某个表中每一行的子查询执行询问。所以为了确认这一点,我修改了查询如下:
WITH dates AS (
SELECT
date '2013-01-01' AS start_date,
date_trunc('month', current_date) AS end_date--,
--generate_series(1, 10) AS somelist
)
SELECT * from dates, sometable where somedate between start_date and end_date
and myval in (generate_series(1, 10))
execution time: ~700ms
令我惊讶的是,这速度相对较快(而且只慢了 10%)。 generate_series 作为子查询显然不是问题所在。
然后回到原始查询并添加 generate_series 但从未在主查询中使用它。这是那个查询。
WITH dates AS (
SELECT
date '2013-01-01' AS start_date,
date_trunc('month', current_date) AS end_date,
generate_series(1, 10) AS somelist
)
SELECT * from dates, sometable where somedate between start_date and end_date
execution time: ~23000ms
这显然是确凿的证据...但我不知道为什么或就此而言到底发生了什么。这是我的问题:
总而言之,在 CTE 或子查询中使用 generate_series 会消耗大量时间/资源(即使未使用结果)。我在 Postgres v9.3 和 v9.5 中得到相同的结果。我运行的表有大约 1400 万行。结果集只有275K左右。
在这一点上我一无所知,有人有任何理论吗? (...还是错误?)
最佳答案
实验(我省略了日期,因为它们只是额外的标量常数)
EXPLAIN
WITH my_cte_b AS (
SELECT generate_series(1, 40) as b_number
)
, my_cte_c AS (
SELECT generate_series(1, 1000) AS c_number
)
Select
my_cte_b.b_number
, my_cte_c.c_number
FROM my_cte_b
JOIN my_cte_c ON (1=1)
;
结果:
QUERY PLAN
------------------------------------------------------------------
Nested Loop (cost=5.01..10020.01 rows=1000000 width=8)
CTE my_cte_b
-> Result (cost=0.00..2.50 rows=1000 width=0)
CTE my_cte_c
-> Result (cost=0.00..2.50 rows=1000 width=0)
-> CTE Scan on my_cte_b (cost=0.00..10.00 rows=1000 width=4)
-> CTE Scan on my_cte_c (cost=0.00..10.00 rows=1000 width=4)
(7 rows)
但是 EXPLAIN ANALYZE
给出了正确的结果:
-----------------------------
Nested Loop (cost=5.01..10020.01 rows=1000000 width=8) (actual time=0.029..8.953 rows=40000 loops=1)
CTE my_cte_b
-> Result (cost=0.00..2.50 rows=1000 width=0) (actual time=0.013..0.019 rows=40 loops=1)
CTE my_cte_c
-> Result (cost=0.00..2.50 rows=1000 width=0) (actual time=0.002..0.095 rows=1000 loops=1)
-> CTE Scan on my_cte_b (cost=0.00..10.00 rows=1000 width=4) (actual time=0.021..0.040 rows=40 loops=1)
-> CTE Scan on my_cte_c (cost=0.00..10.00 rows=1000 width=4) (actual time=0.000..0.104 rows=1000 loops=40)
Planning time: 0.042 ms
Execution time: 25.206 ms
(9 rows)
,所以问题似乎出在估算上,而不是在执行上。
作为奖励:您可以通过在 CTE 中放置 LIMIT xx
来提示(或:愚弄)规划器:
EXPLAIN ANALYZE
WITH my_cte_b AS (
SELECT generate_series(1, 40) as b_number
LIMIT 40
)
, my_cte_c AS (
SELECT generate_series(1, 1000) AS c_number
LIMIT 10000
)
Select
my_cte_b.b_number
, my_cte_c.c_number
FROM my_cte_b
JOIN my_cte_c ON (1=1)
;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------
Nested Loop (cost=2.60..408.00 rows=40000 width=8) (actual time=0.019..9.347 rows=40000 loops=1)
CTE my_cte_b
-> Limit (cost=0.00..0.10 rows=40 width=0) (actual time=0.008..0.018 rows=40 loops=1)
-> Result (cost=0.00..2.50 rows=1000 width=0) (actual time=0.006..0.013 rows=40 loops=1)
CTE my_cte_c
-> Limit (cost=0.00..2.50 rows=1000 width=0) (actual time=0.002..0.241 rows=1000 loops=1)
-> Result (cost=0.00..2.50 rows=1000 width=0) (actual time=0.002..0.134 rows=1000 loops=1)
-> CTE Scan on my_cte_b (cost=0.00..0.40 rows=40 width=4) (actual time=0.012..0.036 rows=40 loops=1)
-> CTE Scan on my_cte_c (cost=0.00..10.00 rows=1000 width=4) (actual time=0.000..0.112 rows=1000 loops=40)
Planning time: 0.096 ms
Execution time: 10.693 ms
(11 rows)
我的结论:规划器没有关于 CTE 的统计信息(它们不包含对物理表的任何引用),只是猜测 (1000)。可以通过在 CTE 中放置一个 LIMIT 来覆盖这个猜测。
顺便说一句:自 PG-11(左右)以来,限制(cte 总是执行一次)已被删除。如果没有副作用。
关于sql - 具有 generate_series 性能问题的 PostgreSQL CTE/子查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36460682/