=====根据反馈更新=====
由于一些初步问题需要澄清,这是一个非常简单的版本。
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中遇到了一个有趣的性能问题。
......说实话,我不太确定这是否是一个" bug"或者只是用户(即Me)对CTE /子查询和/或使用generate_series函数的理解。
我一直在使用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。
此时我一无所知,有没有人有任何理论? (......或者它是一个错误?)
答案 0 :(得分:0)
实验(我遗漏了日期,因为它们只是额外的标量常量)
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可以推翻这个猜测。