Sakila 데이터베이스에서 가져온 다음 테이블이 있습니다.
- 영화 : film_id는 pkey입니다
- 배우 : actor_id는 pkey입니다
- film_actor : film_id 및 actor_id는 film / actor의 핵심 요소입니다.
특정 영화를 선택하고 있습니다. 이 영화에서는 모든 배우가 그 영화에 참여하기를 원합니다. 이에 대한 두 가지 쿼리가 있습니다. 하나는 a LEFT JOIN
와 하나는 a LEFT JOIN LATERAL
입니다.
select film.film_id, film.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
where film.title = 'ACADEMY DINOSAUR'
order by film.title;
select film.film_id, film.title, a.actors
from film
left join lateral
(
select array_agg(first_name) as actors
from actor
inner join film_actor using(actor_id)
where film_actor.film_id = film.film_id
) as a
on true
where film.title = 'ACADEMY DINOSAUR'
order by film.title;
쿼리 계획을 비교할 때 첫 번째 쿼리는 두 번째 쿼리보다 훨씬 나쁩니다 (20x).
Merge Left Join (cost=507.20..573.11 rows=1 width=51) (actual time=15.087..15.089 rows=1 loops=1)
Merge Cond: (film.film_id = film_actor.film_id)
-> Sort (cost=8.30..8.31 rows=1 width=19) (actual time=0.075..0.075 rows=1 loops=1)
Sort Key: film.film_id
Sort Method: quicksort Memory: 25kB
-> Index Scan using idx_title on film (cost=0.28..8.29 rows=1 width=19) (actual time=0.044..0.058 rows=1 loops=1)
Index Cond: ((title)::text = 'ACADEMY DINOSAUR'::text)
-> GroupAggregate (cost=498.90..552.33 rows=997 width=34) (actual time=15.004..15.004 rows=1 loops=1)
Group Key: film_actor.film_id
-> Sort (cost=498.90..512.55 rows=5462 width=8) (actual time=14.934..14.937 rows=11 loops=1)
Sort Key: film_actor.film_id
Sort Method: quicksort Memory: 449kB
-> Hash Join (cost=6.50..159.84 rows=5462 width=8) (actual time=0.355..8.359 rows=5462 loops=1)
Hash Cond: (film_actor.actor_id = actor.actor_id)
-> Seq Scan on film_actor (cost=0.00..84.62 rows=5462 width=4) (actual time=0.035..2.205 rows=5462 loops=1)
-> Hash (cost=4.00..4.00 rows=200 width=10) (actual time=0.303..0.303 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.027..0.143 rows=200 loops=1)
Planning time: 1.495 ms
Execution time: 15.426 ms
Nested Loop Left Join (cost=25.11..33.16 rows=1 width=51) (actual time=0.849..0.854 rows=1 loops=1)
-> Index Scan using idx_title on film (cost=0.28..8.29 rows=1 width=19) (actual time=0.045..0.048 rows=1 loops=1)
Index Cond: ((title)::text = 'ACADEMY DINOSAUR'::text)
-> Aggregate (cost=24.84..24.85 rows=1 width=32) (actual time=0.797..0.797 rows=1 loops=1)
-> Hash Join (cost=10.82..24.82 rows=5 width=6) (actual time=0.672..0.764 rows=10 loops=1)
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.072..0.150 rows=10 loops=1)
Recheck Cond: (film_id = film.film_id)
Heap Blocks: exact=10
-> Bitmap Index Scan on idx_fk_film_id (cost=0.00..4.32 rows=5 width=0) (actual time=0.041..0.041 rows=10 loops=1)
Index Cond: (film_id = film.film_id)
-> Hash (cost=4.00..4.00 rows=200 width=10) (actual time=0.561..0.561 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.039..0.275 rows=200 loops=1)
Planning time: 1.722 ms
Execution time: 1.087 ms
왜 이런거야? 나는 이것에 대해 추론하고 싶어서 무슨 일이 일어나고 있는지 이해하고 데이터 크기가 커질 때 쿼리가 어떻게 작동하는지, 그리고 어떤 조건에서 플래너가 어떤 결정을 내릴지를 예측할 수 있습니다.
내 생각 : 첫 번째 LEFT JOIN
쿼리에서는 하나의 특정 필름에만 관심이있는 외부 쿼리의 필터링을 고려하지 않고 데이터베이스의 모든 필름에 대해 하위 쿼리가 실행되는 것처럼 보입니다. 플래너가 서브 쿼리에서 해당 지식을 가질 수없는 이유는 무엇입니까?
에서 LEFT JOIN LATERAL
쿼리, 우리는 더 많거나 적은 '밀어'필터링 아래로한다는 것이다. 따라서 첫 번째 쿼리에서 발생한 문제는 여기에 없으므로 성능이 향상됩니다.
나는 주로 엄지 손가락의 규칙, 일반적인 지혜를 찾고 있다고 생각합니다 ... 그래서이 플래너 마술 은 제 2의 자연이됩니다.
업데이트 (1)
LEFT JOIN
다음과 같이 다시 작성하면 성능이 향상됩니다 ( LEFT JOIN LATERAL
.
select film.film_id, film.title, array_agg(a.first_name) as actors
from film
left join
(
select film_actor.film_id, actor.first_name
from actor
inner join film_actor using(actor_id)
) as a
on a.film_id = film.film_id
where film.title = 'ACADEMY DINOSAUR'
group by film.film_id
order by film.title;
GroupAggregate (cost=29.44..29.49 rows=1 width=51) (actual time=0.470..0.471 rows=1 loops=1)
Group Key: film.film_id
-> Sort (cost=29.44..29.45 rows=5 width=25) (actual time=0.428..0.430 rows=10 loops=1)
Sort Key: film.film_id
Sort Method: quicksort Memory: 25kB
-> Nested Loop Left Join (cost=4.74..29.38 rows=5 width=25) (actual time=0.149..0.386 rows=10 loops=1)
-> Index Scan using idx_title on film (cost=0.28..8.29 rows=1 width=19) (actual time=0.056..0.057 rows=1 loops=1)
Index Cond: ((title)::text = 'ACADEMY DINOSAUR'::text)
-> Nested Loop (cost=4.47..19.09 rows=200 width=8) (actual time=0.087..0.316 rows=10 loops=1)
-> Bitmap Heap Scan on film_actor (cost=4.32..18.26 rows=5 width=4) (actual time=0.052..0.089 rows=10 loops=1)
Recheck Cond: (film_id = film.film_id)
Heap Blocks: exact=10
-> Bitmap Index Scan on idx_fk_film_id (cost=0.00..4.32 rows=5 width=0) (actual time=0.035..0.035 rows=10 loops=1)
Index Cond: (film_id = film.film_id)
-> Index Scan using actor_pkey on actor (cost=0.14..0.17 rows=1 width=10) (actual time=0.011..0.011 rows=1 loops=10)
Index Cond: (actor_id = film_actor.actor_id)
Planning time: 1.833 ms
Execution time: 0.706 ms
우리는 이것을 어떻게 추론 할 수 있습니까?
업데이트 (2)
나는 몇 가지 실험을 계속했고 흥미로운 경험 법칙은 집계 함수를 가능한 한 높거나 늦게 적용하는 것 입니다. 업데이트 (1)의 쿼리는 아마도 더 이상 내부 쿼리가 아닌 외부 쿼리에서 집계하기 때문에 성능이 더 좋습니다.
LEFT JOIN LATERAL
위 의 내용을 다음과 같이 다시 작성하면 동일하게 적용됩니다 .
select film.film_id, film.title, array_agg(a.first_name) as actors
from film
left join lateral
(
select actor.first_name
from actor
inner join film_actor using(actor_id)
where film_actor.film_id = film.film_id
) as a
on true
where film.title = 'ACADEMY DINOSAUR'
group by film.film_id
order by film.title;
GroupAggregate (cost=29.44..29.49 rows=1 width=51) (actual time=0.088..0.088 rows=1 loops=1)
Group Key: film.film_id
-> Sort (cost=29.44..29.45 rows=5 width=25) (actual time=0.076..0.077 rows=10 loops=1)
Sort Key: film.film_id
Sort Method: quicksort Memory: 25kB
-> Nested Loop Left Join (cost=4.74..29.38 rows=5 width=25) (actual time=0.031..0.066 rows=10 loops=1)
-> Index Scan using idx_title on film (cost=0.28..8.29 rows=1 width=19) (actual time=0.010..0.010 rows=1 loops=1)
Index Cond: ((title)::text = 'ACADEMY DINOSAUR'::text)
-> Nested Loop (cost=4.47..19.09 rows=200 width=8) (actual time=0.019..0.052 rows=10 loops=1)
-> Bitmap Heap Scan on film_actor (cost=4.32..18.26 rows=5 width=4) (actual time=0.013..0.024 rows=10 loops=1)
Recheck Cond: (film_id = film.film_id)
Heap Blocks: exact=10
-> Bitmap Index Scan on idx_fk_film_id (cost=0.00..4.32 rows=5 width=0) (actual time=0.007..0.007 rows=10 loops=1)
Index Cond: (film_id = film.film_id)
-> Index Scan using actor_pkey on actor (cost=0.14..0.17 rows=1 width=10) (actual time=0.002..0.002 rows=1 loops=10)
Index Cond: (actor_id = film_actor.actor_id)
Planning time: 0.440 ms
Execution time: 0.136 ms
여기, 우리는 array_agg()
위로 움직였다 . 보시다시피이 계획은 원본보다 좋습니다 LEFT JOIN LATERAL
.
즉,이 자체 발명 된 경험 법칙 ( 가능하면 집계 기능을 가능한 한 높거나 늦게 적용 )이 다른 경우에 해당 되는지 확실하지 않습니다 .
추가 정보
바이올린 : https://dbfiddle.uk/?rdbms=postgres_10&fiddle=4ec4f2fffd969d9e4b949bb2ca765ffb
버전 : xcc_64-pc-linux-musl의 PostgreSQL 10.4, gcc (Alpine 6.4.0) 6.4.0, 64 비트로 컴파일 됨
환경 : 도커 : docker run -e POSTGRES_PASSWORD=sakila -p 5432:5432 -d frantiseks/postgres-sakila
. Docker 허브의 이미지는 구식이므로 build -t frantiseks/postgres-sakila
git 저장소를 복제 한 후 로컬에서 먼저 빌드를 수행했습니다 .
테이블 정의 :
필름
film_id | integer | not null default nextval('film_film_id_seq'::regclass)
title | character varying(255) | not null
Indexes:
"film_pkey" PRIMARY KEY, btree (film_id)
"idx_title" btree (title)
Referenced by:
TABLE "film_actor" CONSTRAINT "film_actor_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
배우
actor_id | integer | not null default nextval('actor_actor_id_seq'::regclass)
first_name | character varying(45) | not null
Indexes:
"actor_pkey" PRIMARY KEY, btree (actor_id)
Referenced by:
TABLE "film_actor" CONSTRAINT "film_actor_actor_id_fkey" FOREIGN KEY (actor_id) REFERENCES actor(actor_id) ON UPDATE CASCADE ON DELETE RESTRICT
film_actor
actor_id | smallint | not null
film_id | smallint | not null
Indexes:
"film_actor_pkey" PRIMARY KEY, btree (actor_id, film_id)
"idx_fk_film_id" btree (film_id)
Foreign-key constraints:
"film_actor_actor_id_fkey" FOREIGN KEY (actor_id) REFERENCES actor(actor_id) ON UPDATE CASCADE ON DELETE RESTRICT
"film_actor_film_id_fkey" FOREIGN KEY (film_id) REFERENCES film(film_id) ON UPDATE CASCADE ON DELETE RESTRICT
데이터 : Sakila 샘플 데이터베이스에서 가져온 것입니다. 이 질문은 실제 사례가 아니며,이 데이터베이스를 주로 학습 샘플 데이터베이스로 사용하고 있습니다. 몇 달 전에 SQL을 소개 받았으며 지식을 넓히려 고합니다. 다음과 같은 배포판이 있습니다.
select count(*) from film: 1000
select count(*) from actor: 200
select avg(a) from (select film_id, count(actor_id) a from film_actor group by film_id) a: 5.47