Tải bản đầy đủ (.pdf) (6 trang)

Các Cơ Chế Thực Thi Lệnh JOIN Tiếp theo bài Các Loại JOIN Trong SQL Server, bài này docx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (276.22 KB, 6 trang )

Các Cơ Chế Thực Thi Lệnh JOIN

Tiếp theo bài Các Loại JOIN Trong SQL Server, bài này giới thiệu về các cơ chế bên
trong SQL Server sử dụng để xử lý các câu truy vấn JOIN. Về cơ bản khi thực hiện
câu lệnh JOIN, SQL Server duyệt qua hai bảng tham gia vào, lấy ra từng cặp bản
ghi để so sánh, rồi trả về tập kết quả nếu thỏa mãn điều kiện JOIN hoặc loại bỏ nếu
không thỏa mãn. SQL Server cài đặt một vài thuật toán khác nhau, thích hợp với các
tình huống khác nhau (như số lượng bản ghi cần so sánh nhiều hay ít, cột JOIN có
index hay không…). Các thuật toán đó là Nested Loop Join, Merge Join, và Hash
Join.

Nested Loop Join
Đây là thuật toán rất đơn giản và cũng rất hiệu quả đối với tập dữ liệu nhỏ, nó lấy
mỗi bản ghi trong một bảng (gọi là inner table) và so sánh với từng bản ghi của bảng
kia (gọi là outer table) để tìm ra bản ghi thỏa mãn. Thuật toán này có thể được viết ở
dạng pseodo-code như sau:
Code:
for each row R1 in the outer table
for each row R2 in the inner table
if R1.join_column = R2.join_column
return (R1, R2)

*copy từ Craig Freedman’s Blog)
Ví dụ 1:
Code:
USE AdventureWorkds
GO
SELECT a.SalesOrderDetailID, b.Name
FROM Sales.SalesOrderDetail a
JOIN Production.Product b
ON a.ProductID = b.ProductID


WHERE a.SalesOrderID = 43659

Trong phương án thực thi ở hình trên, bạn thấy hai thao tác Clustered index seek,
bảng ở thao tác trên luôn luôn là outer table, và bảng dưới luôn là inner table. Vì số
bản ghi được trả về là nhỏ, bộ Optimizer đã chọn Nested Loop Join. Nói chung
Nested Loop Join thích hợp khi outer table có số bản ghi nhỏ và inner table đã được
sắp xếp theo trường được JOIN (ví dụ trường này có clustered index). Khi đó, việc
quét bảng inner table (vòng lặp trong) trở thành index seek.

Khi xây dựng phương án thực thi, bộ Optimizer sẽ tự động chọn một bảng làm outer
table và bảng kia làm inner table theo cách để đảm bảo chi phí là nhỏ nhất.

Nested Loop Join là thao tác join bạn mong đợi xuất hiện nhất trong phương án thực
thi, vì nó chứng tỏ câu truy vấn được thực hiện hiệu quả và có chi phí thấp. Khi số
bản ghi tăng lên hoặc bảng inner table không được sắp thứ tự (vì không có index hỗ
trợ), bộ Optimizer sẽ xem xét đến Merge Join hoặc Hash Join, vì lúc đó các thuật
toán này có thể sẽ hiệu quả hơn so với Nested Loop Join.

Cần phân biệt các thuật toán kể trên với các loại JOIN mà bạn dùng khi viết code
như INNER JOIN, OUTER JOIN. Theo thuật ngữ của Microsoft, các loại JOIN đó
được gọi là logical operator (các toán tử ở mức logic); còn các thuật toán trên gọi là
physical operator (các toán tử ở mức vật lý). Khi viết code, bạn dùng các toán tử
mức logic để diễn đạt yêu cầu, hệ thống sẽ xem xét và sử dụng một toán tử mức vật
lý (một trong ba thuật toán) thích hợp để thực thi câu lệnh.

Merge Join
Kỹ thuật này đòi hỏi hai bảng phải cùng được sắp xếp theo thứ tự của trường JOIN.
Nó đọc từng cặp bản ghi của mỗi bảng và so sánh với nhau. Nếu khớp thì gửi ra tập
kết quả. Nếu không thì nó loại bản ghi có trường JOIN nhỏ hơn, đọc tới bản ghi tiếp
theo của bảng tương ứng và tiếp tục quá trình. Với thuật toán này, hai bảng được

đọc từ đầu và cùng tiến lên song song với nhau. Pseodo-code của thuật toán Merge
Join như sau:
Code:
get first row R1 from table 1
get first row R2 from table 2
while not at the end of either table
begin
if R1.join_column = R2.join_column
begin
return (R1, R2)
get next row R2 from table 2
end
else if R1.join_column < R2.join_column
get next row R1 from table 1
else
get next row R2 from table 2
end

(*copy từ Craig Freedman’s Blog)
Ví dụ 2: Cùng câu truy vấn như ở phần trước, nhưng bỏ qua mệnh đề WHERE
Code:
SELECT a.SalesOrderDetailID, b.Name
FROM Sales.SalesOrderDetail a
JOIN Production.Product b
ON a.ProductID = b.ProductID
WHERE a.SalesOrderID = 43659


Với câu lệnh trên thì Merge Join trở nên thích hợp, vì số bản ghi trả về là lớn và cả
hai bảng đều đã được sắp xếp (nói chính xác ra là, đối với bảng SalesOrderDetail

nó chỉ cần đọc index trên trường ProductID, và tất nhiên index đã sắp xếp sẵn).

Số phép so sánh, và do đó, chi phí của thuật toán này, tương đương với tổng của số
bản ghi trong hai bảng. Do đó thuật toán này hoạt động hiệu quả hơn Nested Loop
khi số bản ghi tăng cao. Trong nhiều trường hợp, thuật toán kết thúc khi nó mới chỉ
quét xong bảng nhỏ hơn, vì bảng kia nếu có quét tiếp cũng không tìm được bản ghi
nào thỏa mãn nữa. Khi đó số lần so sánh chỉ bằng hai lần số bản ghi của bảng nhỏ.

Nếu một trong hai bảng không được sắp sẵn thứ tự, bộ Optimizer có hai lựa chọn:
(1) sắp xếp lại bảng theo thứ tự trường JOIN rồi áp dụng Merge Join hoặc (2)
chuyển sang dùng Hash Join. Phương án nào rẻ hơn sẽ được chọn.

Hash Join
Thuật toán này phát huy hiệu quả nhất đối với lượng dữ liệu lớn và không được sắp
xếp sẵn. Nó được thực hiện làm hai giai đoạn: xây dựng (build) và dò tìm (probe).

Ở bước xây dựng, nó quét qua một bảng (gọi là build table), và băm (hash) các bản
ghi dựa vào trường JOIN, rồi xây dựng một bảng băm (hash table) trong bộ nhớ.

Đến bước dò tìm, nó đọc bảng thứ hai (gọi là probe table) và cũng băm các bản ghi
dùng trường JOIN, rồi dùng giá trị băm đó để tìm trên bảng băm. Mỗi lần tìm được
nó gửi cặp bản ghi tương ứng ra tập kết quả. Pseodo-code:
Code:
for each row R1 in the build table
begin
calculate hash value on R1.join_column
insert R1 into the appropriate hash bucket
end
for each row R2 in the probe table
begin

calculate hash value on R2.join_column
for each row R1 in the corresponding hash bucket
if R1.join_column = R2.join_column
return (R1, R2)
end

(*copy từ Craig Freedman’s Blog)
Ví dụ 3: giống như ví dụ 2 nhưng thêm một trường OrderQty vào phần SELECT
Code:
SELECT a.SalesOrderDetailID, a.OrderQty, b.Name
FROM Sales.SalesOrderDetail a
JOIN Production.Product b
ON a.ProductID = b.ProductID
WHERE a.SalesOrderID = 43659

Ở ví dụ này Hash Join đã được sử dụng, mặc dù số bản ghi được trả về giống như
ở ví dụ 2. Lưu ý ở ví dụ 2, bảng SalesOrderDetail chỉ cần đọc index trên trường
ProductID là đủ, và vì input cho việc join đã được sắp xêp nên Merge Join đã được
dùng. Nhưng ở ví dụ 3, vì có thêm trường OrderQty nên chỉ đọc index trên trường
ProductID là không đủ mà hệ thống phải đọc cả vào bảng nữa. Input cho thao tác
join lúc này không còn được sắp xếp nữa và do đó, Hash Join trở nên thích hợp
hơn.

Trong các tình huống như thế này Hash Join có ưu thế hơn Merge Join vì việc xây
dựng bảng băm nhanh hơn sắp xếp lại bảng, hơn nữa nó chỉ cần áp dụng đối với
một bảng.

Thông thường bộ Optimizer chọn bảng nhỏ để xây dựng bảng băm để không cần
chiếm quá nhiều bộ nhớ. Tuy vậy so với Nested Loop Join và Merge Join thì thuật
toán này đòi hỏi rất nhiều tài nguyên CPU và bộ nhớ. Khi Hash Join xuất hiện trong

phương án thực thi, đó là chỉ dấu cho thấy lượng dữ liệu cần xử lý khá lớn (do
không có mệnh đề WHERE, chủ ý hoặc do quên không đưa vào) và không có index
hỗ trợ.

Trong các hệ thống OLTP, vốn đặc trưng bởi nhiều giao dịch được thực hiện nhanh,
Hash Join cho thấy nhiều khả năng là câu lệnh chưa được thực hiện tối ưu. Còn
trong môi trường data warehouse, các thao tác xử lý thường trên một lượng dữ liệu
lớn, do đó Hash Join được sử dụng rất thường xuyên.

Lời kết
Trên đây giới thiệu các thuật toán SQL Server dùng để thực thi câu lệnh JOIN. Trên
thực tế các thuật toán phức tạp hơn và còn có nhiều biến thể để bộ Optimizer tinh
chỉnh trong từng tình huống cụ thể. Tuy nhiên mức sâu nhất mà bạn có thể nhìn vào
hệ thống là biết thuật toán nào đã được sử dụng cho câu lệnh, do Microsoft che dấu
toàn bộ các chi tiết bên dưới. Việc hiểu biết cơ chế hoạt động của các thuật toán
giúp bạn có thêm một công cụ để tối ưu hóa câu lệnh. Ví dụ, với câu truy vấn ở
phần Hash Join, khi quan sát kế hoạch thực thi và thấy Hash Join được sử dụng
bạn hiểu rằng đây có thể là chỉ dấu câu lệnh chưa được thực hiện tối ưu. Bạn cố
gắng tạo thay đổi để hệ thống chuyển sang chọn Merge Join (vì số bản ghi trả về
lớn nên Nested Loop Join chắc chắn không thích hợp). Để dùng Merge Join thì đầu
vào phải được sắp xếp. Vì thế bạn có thể sửa lại index trên trường ProductID để nó
cover cả trường OrderQty. Và giờ câu lệnh đã được thực hiện bằng Merge Join và
hiệu năng đã được cải thiện đáng kể:
Code:
Tạo một bảng copy của Sales.SalesOrderDetail
SELECT *
INTO Sales.SalesOrderDetail_2
FROM Sales.SalesOrderDetail
GO
tạo các index trên bảng copy

CREATE CLUSTERED INDEX
IX_SalesOrderDetail_SalesOrderID_SalesOrderDetailID_2
ON Sales.SalesOrderDetail_2 (
SalesOrderID,
SalesOrderDetailID)
GO
CREATE NONCLUSTERED INDEX IX_SalesOrderDetail_ProductID_2
ON Sales.SalesOrderDetail_2(ProductID)
INCLUDE(OrderQty)

so sánh hai câu lệnh:
Query 1: câu lệnh trên bảng cũ
SELECT a.SalesOrderDetailID, a.OrderQty, b.Name
FROM Sales.SalesOrderDetail a
JOIN Production.Product b
ON a.ProductID = b.ProductID

Query 2: câu lệnh trên bảng copy
SELECT a.SalesOrderDetailID, a.OrderQty, b.Name
FROM Sales.SalesOrderDetail_2 a
JOIN Production.Product b
ON a.ProductID = b.ProductID


×