为什么查询规划器无法转换相关子查询?
Why is the query planner unable to transform a correlated subquery?
在中,我了解到相关子查询可以重写为左连接:
select film_id, title,
(
select array_agg(first_name)
from actor
inner join film_actor using(actor_id)
where film_actor.film_id = film.film_id
) as actors
from film
order by title;
至
select f.film_id, f.title, array_agg(a.first_name)
from film f
left join film_actor fa using(film_id)
left join actor a using(actor_id)
group by f.film_id
order by f.title;
机器人查询 return 相同的结果,但第二个查询执行得更好。
这让我想知道:为什么查询规划器不能自己做这样的转换?
我明白为什么不是所有相关的子查询都可以转换为连接,但我没有发现这个特定查询有任何问题。
更新性能
我尝试比较性能如下。我执行了第一个查询 100 次的 2 个连续循环,然后是第二个查询的 2 个连续循环 100 次。在这两种情况下,我都忽略了第一个循环,因为我认为这是一个热身循环。
第一个查询的 100 倍我得到 16 秒,第二个查询的 100 倍得到 11 秒。
解释如下:
相关子查询:
Index Scan using idx_title on film (cost=0.28..24949.50 rows=1000 width=51) (actual time=0.690..74.828 rows=1000 loops=1)
SubPlan 1
-> Aggregate (cost=24.84..24.85 rows=1 width=32) (actual time=0.068..0.068 rows=1 loops=1000)
-> Hash Join (cost=10.82..24.82 rows=5 width=6) (actual time=0.034..0.055 rows=5 loops=1000)
Hash Cond: (film_actor.actor_id = actor.actor_id)
-> Bitmap Heap Scan on film_actor (cost=4.32..18.26 rows=5 width=2) (actual time=0.025..0.040 rows=5 loops=1000)
Recheck Cond: (film_id = film.film_id)
Heap Blocks: exact=5075
-> Bitmap Index Scan on idx_fk_film_id (cost=0.00..4.32 rows=5 width=0) (actual time=0.015..0.015 rows=5 loops=1000)
Index Cond: (film_id = film.film_id)
-> Hash (cost=4.00..4.00 rows=200 width=10) (actual time=0.338..0.338 rows=200 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 17kB
-> Seq Scan on actor (cost=0.00..4.00 rows=200 width=10) (actual time=0.021..0.133 rows=200 loops=1)
Planning time: 1.277 ms
Execution time: 75.525 ms
加入:
Sort (cost=748.60..751.10 rows=1000 width=51) (actual time=35.865..36.060 rows=1000 loops=1)
Sort Key: f.title
Sort Method: quicksort Memory: 199kB
-> GroupAggregate (cost=645.31..698.78 rows=1000 width=51) (actual time=23.953..34.204 rows=1000 loops=1)
Group Key: f.film_id
-> Sort (cost=645.31..658.97 rows=5462 width=25) (actual time=23.910..25.210 rows=5465 loops=1)
Sort Key: f.film_id
Sort Method: quicksort Memory: 619kB
-> Hash Left Join (cost=84.00..306.25 rows=5462 width=25) (actual time=2.098..16.237 rows=5465 loops=1)
Hash Cond: (fa.actor_id = a.actor_id)
-> Hash Right Join (cost=77.50..231.03 rows=5462 width=21) (actual time=1.786..10.636 rows=5465 loops=1)
Hash Cond: (fa.film_id = f.film_id)
-> Seq Scan on film_actor fa (cost=0.00..84.62 rows=5462 width=4) (actual time=0.018..2.221 rows=5462 loops=1)
-> Hash (cost=65.00..65.00 rows=1000 width=19) (actual time=1.753..1.753 rows=1000 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 59kB
-> Seq Scan on film f (cost=0.00..65.00 rows=1000 width=19) (actual time=0.029..0.819 rows=1000 loops=1)
-> Hash (cost=4.00..4.00 rows=200 width=10) (actual time=0.286..0.286 rows=200 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 17kB
-> Seq Scan on actor a (cost=0.00..4.00 rows=200 width=10) (actual time=0.016..0.114 rows=200 loops=1)
Planning time: 1.648 ms
Execution time: 36.599 ms
我有点惊讶第二个表现更好。例如,第二个应该出现语法错误,因为 order by
在 group by
之前——但我明白你的意思。
但是您的问题的答案是,尽管 SQL 是一种描述性语言而不是过程性语言,但对于某些数据库而言,查询的结构可能会影响执行计划。如果您看过说明,那么这两个查询显然就是这种情况。
更重要的答案是,尽管查询看起来相同,但它们在语义上并不相同。特别是,如果 film.film_id
不是唯一的,他们会 return 不同的答案。
评论太多了。
你的相关子查询的重写应该是这样的:
select film_id, title, a.actors
from film
left join
(
select film_actor.film_id, array_agg(first_name) as actors
from actor
inner join film_actor using(actor_id)
group by film_actor.film_id
) as a
on a.film_id = film.film_id
order by title;
关于性能,标量相关子查询 对优化器来说似乎很难,我不希望它们的性能与手动重写相同或更好。
在
select film_id, title,
(
select array_agg(first_name)
from actor
inner join film_actor using(actor_id)
where film_actor.film_id = film.film_id
) as actors
from film
order by title;
至
select f.film_id, f.title, array_agg(a.first_name)
from film f
left join film_actor fa using(film_id)
left join actor a using(actor_id)
group by f.film_id
order by f.title;
机器人查询 return 相同的结果,但第二个查询执行得更好。
这让我想知道:为什么查询规划器不能自己做这样的转换?
我明白为什么不是所有相关的子查询都可以转换为连接,但我没有发现这个特定查询有任何问题。
更新性能
我尝试比较性能如下。我执行了第一个查询 100 次的 2 个连续循环,然后是第二个查询的 2 个连续循环 100 次。在这两种情况下,我都忽略了第一个循环,因为我认为这是一个热身循环。
第一个查询的 100 倍我得到 16 秒,第二个查询的 100 倍得到 11 秒。
解释如下:
相关子查询:
Index Scan using idx_title on film (cost=0.28..24949.50 rows=1000 width=51) (actual time=0.690..74.828 rows=1000 loops=1)
SubPlan 1
-> Aggregate (cost=24.84..24.85 rows=1 width=32) (actual time=0.068..0.068 rows=1 loops=1000)
-> Hash Join (cost=10.82..24.82 rows=5 width=6) (actual time=0.034..0.055 rows=5 loops=1000)
Hash Cond: (film_actor.actor_id = actor.actor_id)
-> Bitmap Heap Scan on film_actor (cost=4.32..18.26 rows=5 width=2) (actual time=0.025..0.040 rows=5 loops=1000)
Recheck Cond: (film_id = film.film_id)
Heap Blocks: exact=5075
-> Bitmap Index Scan on idx_fk_film_id (cost=0.00..4.32 rows=5 width=0) (actual time=0.015..0.015 rows=5 loops=1000)
Index Cond: (film_id = film.film_id)
-> Hash (cost=4.00..4.00 rows=200 width=10) (actual time=0.338..0.338 rows=200 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 17kB
-> Seq Scan on actor (cost=0.00..4.00 rows=200 width=10) (actual time=0.021..0.133 rows=200 loops=1)
Planning time: 1.277 ms
Execution time: 75.525 ms
加入:
Sort (cost=748.60..751.10 rows=1000 width=51) (actual time=35.865..36.060 rows=1000 loops=1)
Sort Key: f.title
Sort Method: quicksort Memory: 199kB
-> GroupAggregate (cost=645.31..698.78 rows=1000 width=51) (actual time=23.953..34.204 rows=1000 loops=1)
Group Key: f.film_id
-> Sort (cost=645.31..658.97 rows=5462 width=25) (actual time=23.910..25.210 rows=5465 loops=1)
Sort Key: f.film_id
Sort Method: quicksort Memory: 619kB
-> Hash Left Join (cost=84.00..306.25 rows=5462 width=25) (actual time=2.098..16.237 rows=5465 loops=1)
Hash Cond: (fa.actor_id = a.actor_id)
-> Hash Right Join (cost=77.50..231.03 rows=5462 width=21) (actual time=1.786..10.636 rows=5465 loops=1)
Hash Cond: (fa.film_id = f.film_id)
-> Seq Scan on film_actor fa (cost=0.00..84.62 rows=5462 width=4) (actual time=0.018..2.221 rows=5462 loops=1)
-> Hash (cost=65.00..65.00 rows=1000 width=19) (actual time=1.753..1.753 rows=1000 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 59kB
-> Seq Scan on film f (cost=0.00..65.00 rows=1000 width=19) (actual time=0.029..0.819 rows=1000 loops=1)
-> Hash (cost=4.00..4.00 rows=200 width=10) (actual time=0.286..0.286 rows=200 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 17kB
-> Seq Scan on actor a (cost=0.00..4.00 rows=200 width=10) (actual time=0.016..0.114 rows=200 loops=1)
Planning time: 1.648 ms
Execution time: 36.599 ms
我有点惊讶第二个表现更好。例如,第二个应该出现语法错误,因为 order by
在 group by
之前——但我明白你的意思。
但是您的问题的答案是,尽管 SQL 是一种描述性语言而不是过程性语言,但对于某些数据库而言,查询的结构可能会影响执行计划。如果您看过说明,那么这两个查询显然就是这种情况。
更重要的答案是,尽管查询看起来相同,但它们在语义上并不相同。特别是,如果 film.film_id
不是唯一的,他们会 return 不同的答案。
评论太多了。
你的相关子查询的重写应该是这样的:
select film_id, title, a.actors
from film
left join
(
select film_actor.film_id, array_agg(first_name) as actors
from actor
inner join film_actor using(actor_id)
group by film_actor.film_id
) as a
on a.film_id = film.film_id
order by title;
关于性能,标量相关子查询 对优化器来说似乎很难,我不希望它们的性能与手动重写相同或更好。