Tôi có các bảng sau (lấy từ cơ sở dữ liệu Sakila):
- phim: film_id là pkey
- diễn viên: diễn viên_id là pkey
- film_actor: film_id và Act_id là những người thích làm phim / diễn viên
Tôi đang chọn một bộ phim cụ thể. Đối với bộ phim này, tôi cũng muốn tất cả các diễn viên tham gia vào bộ phim đó. Tôi có hai truy vấn cho điều này: một với a LEFT JOIN
và một với 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;
Khi so sánh kế hoạch truy vấn, truy vấn đầu tiên thực hiện kém hơn (20 lần) so với truy vấn thứ hai:
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
Tại sao lại thế này? Tôi muốn tìm hiểu lý do về điều này, vì vậy tôi có thể hiểu những gì đang diễn ra và có thể dự đoán cách truy vấn sẽ hành xử khi kích thước dữ liệu tăng và quyết định nào mà người lập kế hoạch sẽ đưa ra trong những điều kiện nhất định.
Suy nghĩ của tôi: trong LEFT JOIN
truy vấn đầu tiên , có vẻ như truy vấn con được thực thi cho tất cả các phim trong cơ sở dữ liệu, mà không tính đến việc lọc trong truy vấn bên ngoài mà chúng ta chỉ quan tâm đến một phim cụ thể. Tại sao người lập kế hoạch không thể có kiến thức đó trong truy vấn con?
Trong LEFT JOIN LATERAL
truy vấn, chúng tôi ít nhiều 'đẩy' việc lọc xuống dưới. Vì vậy, vấn đề chúng tôi gặp phải trong truy vấn đầu tiên không có ở đây, do đó hiệu suất tốt hơn.
Tôi đoán tôi chủ yếu tìm kiếm quy tắc của ngón tay cái, trí tuệ chung, ... vì vậy điều này phép thuật kế hoạch trở thành bản chất thứ hai - nếu điều đó có ý nghĩa.
cập nhật (1)
Viết lại LEFT JOIN
như sau cũng cho hiệu suất tốt hơn (tốt hơn một chút so với 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
Làm thế nào chúng ta có thể lý do về điều này?
cập nhật (2)
Tôi tiếp tục với một số thí nghiệm và tôi nghĩ một quy tắc thú vị là: áp dụng hàm tổng hợp càng cao / muộn càng tốt . Truy vấn trong bản cập nhật (1) có thể hoạt động tốt hơn vì chúng tôi đang tổng hợp trong truy vấn bên ngoài, không còn trong truy vấn bên trong.
Điều tương tự cũng áp dụng nếu chúng ta viết lại LEFT JOIN LATERAL
như trên:
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
Ở đây, chúng tôi di chuyển array_agg()
lên trên. Như bạn thấy, kế hoạch này cũng tốt hơn so với ban đầu LEFT JOIN LATERAL
.
Điều đó nói rằng, tôi không chắc liệu quy tắc tự phát minh này ( áp dụng hàm tổng hợp càng cao / muộn càng tốt ) có đúng trong các trường hợp khác hay không.
thông tin thêm
Fiddle: https://dbfiddle.uk/?rdbms=postgres_10&fiddle=4ec4f2fffd969d9e4b949bb2ca765ffb
Phiên bản: PostgreSQL 10.4 trên x86_64-pc-linux-musl, được biên dịch bởi gcc (Alpine 6.4.0) 6.4.0, 64-bit
Môi trường: Docker : docker run -e POSTGRES_PASSWORD=sakila -p 5432:5432 -d frantiseks/postgres-sakila
. Xin lưu ý rằng hình ảnh trên Docker hub đã lỗi thời, vì vậy tôi đã xây dựng cục bộ trước: build -t frantiseks/postgres-sakila
sau khi nhân bản kho lưu trữ git.
Bảng định nghĩa:
phim ảnh
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
diễn viên
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
Diễn viên điện ảnh
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
Dữ liệu: đây là từ cơ sở dữ liệu mẫu Sakila. Câu hỏi này không phải là một trường hợp thực tế, tôi đang sử dụng cơ sở dữ liệu này chủ yếu như một cơ sở dữ liệu mẫu học tập. Tôi đã được giới thiệu về SQL vài tháng trước và tôi đang cố gắng mở rộng kiến thức của mình. Nó có các bản phân phối sau:
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