Câu 1Điều nào mô tả đúng nested loop join?
Thuật toán Join và Thứ tự Join
Trên perf_sales join với employee, bạn sẽ đọc vòng lặp outer / inner của nested loop join từ kế hoạch, thêm chỉ mục để lật phía inner từ SCAN sang SEARCH, và đi qua hình ảnh minh họa hash join và sort-merge join dùng bởi các cơ sở dữ liệu khác.
Bộ dữ liệu cho bài này — perf_sales và employee
Khi bạn join hai bảng, cơ sở dữ liệu chọn một thuật toán join bên trong — quy trình nó dùng để khớp các dòng từ hai bảng.
Các phương thức join khác nhau đi kèm chi phí khác nhau.
Bộ dữ liệu là bảng bán hàng perf_sales (50.000 dòng; emp_id trỏ tới nhân viên bán) và bảng nhân viên employee (30 dòng; emp_id thực sự là khóa chính).
Khi bạn join hai bảng trên emp_id, bạn sẽ xem cái nào hiện là vòng lặp ngoài trong kế hoạch và thứ tự join thay đổi thế nào tùy có chỉ mục hay không.
Nested loop join và thứ tự join
Một nested loop join đi qua bảng ngoài từng dòng một và, với mỗi dòng, tra cứu các dòng trong bảng trong thỏa mãn điều kiện join — một vòng lặp lồng nhau đôi.
-- So sánh cùng truy vấn join qua ba phương thức join
SELECT e.name, s.amount
FROM employee e
JOIN perf_sales s ON s.emp_id = e.emp_id;
-- Dòng chảy nội bộ của nested loop join
-- for each e in employee: -- vòng lặp ngoài (30 dòng)
-- for each s in perf_sales: -- trong (không có chỉ mục, quét cả 50.000 dòng)
-- if s.emp_id = e.emp_id then add to the join result
- Vòng lặp ngoài chạy số lần bằng số dòng của bảng, nên đặt bảng nhỏ hơn ở ngoài
- Phía trong được tra cứu lặp lại từ mỗi dòng ngoài, nên lập chỉ mục khóa join để biến thành tra cứu một lần
- Trong kế hoạch, bảng xuất hiện đầu là ngoài, và cái xuất hiện sau là trong
- Bộ tối ưu chọn outer / inner tự động từ thống kê, nên thứ tự bạn viết trong
FROMkhông nhất thiết được giữ
-- Join perf_sales và employee theo emp_id (tổng bán hàng mỗi nhân viên ở dept 1)
-- Không có chỉ mục trên perf_sales.emp_id, kế hoạch có xu hướng quét perf_sales trong toàn bộ
EXPLAIN QUERY PLAN
SELECT e.name, SUM(s.amount) AS total
FROM employee e
JOIN perf_sales s ON s.emp_id = e.emp_id
WHERE e.dept_id = 1
GROUP BY e.name;
--> SCAN employee AS e
--> SCAN perf_sales AS s (không chỉ mục, nên trong cũng quét toàn bộ)
-- Thêm chỉ mục trên perf_sales.emp_id và trong có thể tra qua chỉ mục
DROP INDEX IF EXISTS ix_sales_emp;
CREATE INDEX ix_sales_emp ON perf_sales(emp_id);
EXPLAIN QUERY PLAN
SELECT e.name, SUM(s.amount) AS total
FROM employee e
JOIN perf_sales s ON s.emp_id = e.emp_id
WHERE e.dept_id = 1
GROUP BY e.name;
--> SCAN employee AS e
--> SEARCH perf_sales AS s USING INDEX ix_sales_emp (emp_id=?)
Bộ tối ưu chọn thứ tự join tự động từ thống kê
Thứ tự join — bảng nào lên vòng lặp ngoài — về quy tắc được chọn tự động bởi bộ tối ưu dựa trên thống kê.
Nếu bạn đã thu thập thống kê với ANALYZE (đề cập ở bài trước), bộ tối ưu có thể ước lượng chính xác hơn "bảng nào nhỏ hơn" và "liệu phía trong có tra qua chỉ mục được không" và tối ưu việc gán outer / inner.
Thứ tự bạn viết trong FROM có thể bị bộ tối ưu xáo lại.
Mẹo đọc thứ tự join: trong đầu ra EXPLAIN QUERY PLAN, bảng cao hơn gần với vòng lặp ngoài hơn.
Khi bảng sai lên vòng lặp ngoài và mọi thứ chậm lại, điều đầu tiên cần kiểm tra là liệu khóa join trên bảng bạn muốn làm trong có chỉ mục không.
-- Sau ANALYZE, outer / inner được quyết định chính xác hơn từ thống kê
DROP INDEX IF EXISTS ix_sales_emp;
CREATE INDEX ix_sales_emp ON perf_sales(emp_id);
ANALYZE;
-- Ngay cả với perf_sales đầu trong FROM, bộ tối ưu có thể đặt employee nhỏ hơn ở ngoài
EXPLAIN QUERY PLAN
SELECT e.name, SUM(s.amount) AS total
FROM perf_sales s
JOIN employee e ON e.emp_id = s.emp_id
WHERE e.dept_id = 1
GROUP BY e.name;
--> SCAN employee AS e
--> SEARCH perf_sales AS s USING INDEX ix_sales_emp (emp_id=?)
Hash join và sort-merge join — phương thức join dùng bởi cơ sở dữ liệu khác
Cơ sở dữ liệu lớn (PostgreSQL / Oracle / SQL Server và các loại) cũng dùng hash join (xây bảng hash từ phía nhỏ hơn và quét cái còn lại vào nó) và sort-merge join (sắp xếp cả hai phía theo khóa join và đi qua đồng bộ) cùng với nested loop join.
Engine vận hành console trình duyệt của khóa học này chỉ dùng nested loop, nên phần này nói về hai phương thức kia ở mức sơ đồ và khái niệm.
Hash join — biến phía nhỏ hơn thành bảng hash và truyền cái còn lại qua
-- Cùng truy vấn, về khái niệm chạy như hash join
SELECT e.name, s.amount
FROM employee e
JOIN perf_sales s ON s.emp_id = e.emp_id;
-- Dòng chảy nội bộ của hash join
-- H := { e.emp_id => e | e in employee } -- (1) Build (biến employee thành bảng hash)
-- for each s in perf_sales: -- (2) Probe (truyền perf_sales qua)
-- if H[s.emp_id] exists: add to the join result
- Đặt bảng nhỏ hơn ở phía build giữ bảng hash đủ nhỏ để vừa bộ nhớ, làm mọi thứ nhanh
- Chỉ hoạt động cho join bằng (
=); điều kiện phạm vi (</BETWEEN) không thể join theo cách này - Có thể join số dòng lớn ở cả hai phía với một lần đi qua mỗi phía ngay cả không có chỉ mục trong, nên thường nhanh hơn nested loop
- Xuất hiện như
Hash JointrongEXPLAINcủa cơ sở dữ liệu production
Sort-merge join — sắp xếp cả hai phía, rồi đi qua cùng nhau
-- Cùng truy vấn, về khái niệm chạy như sort-merge join
SELECT e.name, s.amount
FROM employee e
JOIN perf_sales s ON s.emp_id = e.emp_id;
-- Dòng chảy nội bộ của sort-merge join
-- E := sort(employee by emp_id) -- (1) Sort (sắp xếp cả hai theo emp_id)
-- S := sort(perf_sales by emp_id)
-- i, j := 0, 0
-- while i < |E| and j < |S|: -- (2) Merge
-- if E[i].emp_id = S[j].emp_id: add to the join result, j++
-- else if E[i].emp_id < S[j].emp_id: i++
-- else: j++
- Nếu cả hai phía đã sắp xếp (xếp theo chỉ mục chẳng hạn), chi phí sort về cơ bản bằng không và cực nhanh
- Xử lý cả join phạm vi (điều kiện join bao gồm
<hayBETWEEN) — sức mạnh hash join không có - Kết quả đến đã sắp theo khóa join, tiện cho các tổng hợp và merge sau
- Xuất hiện như
Merge JointrongEXPLAINcủa cơ sở dữ liệu production
Hash join và sort-merge join là phương thức join của các RDBMS khác
Hash join và sort-merge join là các phương thức join cung cấp bởi cơ sở dữ liệu lớn như PostgreSQL / Oracle / SQL Server.
Engine vận hành console trình duyệt của khóa học này chỉ dùng nested loop làm thuật toán join (nó có thể tạo chỉ mục nội bộ tạm để hỗ trợ join nếu cần), nên hash join và sort-merge join không thể trình diễn trực tiếp trong console này.
Với những cái đó chúng ta hài lòng với các sơ đồ và code chỉ đọc ở trên để nắm ý tưởng.
Trong khi đó, vòng lặp outer / inner của nested loop join, thứ tự join, và sự lật từ SCAN sang SEARCH ở phía trong do chỉ mục dẫn dắt, tất cả đề cập trước trong bài này, là quan sát được thật.
Khả năng đọc cơ sở dữ liệu chọn phương thức join nào dựa trên cùng hiểu biết về nested loop và thứ tự join.
Cơ sở dữ liệu production sẽ hiển thị các thuật ngữ như Hash Join / Merge Join trong EXPLAIN của chúng, nên bắt đầu bằng việc thoải mái đọc kế hoạch nested loop trong khóa học này.
-- Dưới đây là cách EXPLAIN có thể trông như trong PostgreSQL (chỉ đọc; không chạy trong console khóa học này)
-- EXPLAIN SELECT e.name, SUM(s.amount) FROM employee e
-- JOIN perf_sales s ON s.emp_id = e.emp_id GROUP BY e.name;
-- Kế hoạch ví dụ:
-- Hash Join (xây bảng hash từ employee, rồi truyền perf_sales)
-- hoặc Merge Join (sắp cả hai theo emp_id, rồi khớp lên)
-- PostgreSQL chọn giữa các phương thức này tự động dựa trên thống kê và chi phí
-- Điều console khóa học này có thể thực sự trình diễn là (nested loop):
DROP INDEX IF EXISTS ix_sales_emp;
CREATE INDEX ix_sales_emp ON perf_sales(emp_id);
EXPLAIN QUERY PLAN
SELECT e.name, SUM(s.amount) AS total
FROM employee e
JOIN perf_sales s ON s.emp_id = e.emp_id
WHERE e.dept_id = 1
GROUP BY e.name;
--> SCAN employee / SEARCH perf_sales USING INDEX ix_sales_emp (emp_id=?)
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Điều nào mô tả ý tưởng cơ bản đằng sau việc chọn thứ tự join cho nested loop join?
Câu 3Điều nào mô tả đúng hash join và sort-merge join?