Đây là một phần của loạt bài toán tử có vấn đề về nội bộ của máy chủ SQL. Để đọc bài đăng đầu tiên, hãy nhấp vào đây.
SQL Server đã có hơn 30 năm và tôi đã làm việc với SQL Server gần như lâu rồi. Tôi đã thấy rất nhiều thay đổi trong nhiều năm (và nhiều thập kỷ!) Và các phiên bản của sản phẩm đáng kinh ngạc này. Trong các bài đăng này, tôi sẽ chia sẻ với bạn cách tôi xem xét một số tính năng hoặc khía cạnh của SQL Server, đôi khi cùng với một chút quan điểm lịch sử.
Lần trước, tôi đã nói về thao tác quét trong kế hoạch truy vấn SQL Server với tư cách là toán tử có khả năng có vấn đề trong chẩn đoán SQL Server. Mặc dù quét thường xuyên chỉ được sử dụng vì không có chỉ mục hữu ích, nhưng đôi khi quá trình quét thực sự là lựa chọn tốt hơn so với hoạt động tìm kiếm chỉ mục.
Trong bài viết này, tôi sẽ cho bạn biết về một nhóm toán tử khác mà đôi khi được coi là có vấn đề:băm. Hashing là một thuật toán xử lý dữ liệu rất nổi tiếng đã có từ nhiều thập kỷ trước. Tôi đã nghiên cứu nó trong các lớp cấu trúc dữ liệu của mình từ khi tôi học khoa học máy tính lần đầu tiên tại trường Đại học. Nếu bạn muốn biết thông tin cơ bản về hàm băm và hàm băm, bạn có thể xem bài viết này trên Wikipedia. Tuy nhiên, SQL Server đã không thêm băm vào danh mục các tùy chọn xử lý truy vấn của nó cho đến SQL Server 7. (Ngoài ra, tôi sẽ đề cập đến việc SQL Server đã sử dụng băm trong một số thuật toán tìm kiếm nội bộ của riêng nó. Như bài viết trên Wikipedia đã đề cập , băm sử dụng một chức năng đặc biệt để ánh xạ dữ liệu có kích thước tùy ý sang dữ liệu có kích thước cố định. SQL sử dụng băm như một kỹ thuật tìm kiếm để ánh xạ từng trang từ cơ sở dữ liệu có kích thước tùy ý vào bộ đệm trong bộ nhớ, có kích thước cố định. Thực tế , đã từng có một tùy chọn cho sp_configure được gọi là 'nhóm băm', cho phép bạn kiểm soát số lượng nhóm được sử dụng để băm các trang cơ sở dữ liệu vào bộ đệm bộ nhớ.)
Hashing là gì?
Băm là một kỹ thuật tìm kiếm không yêu cầu dữ liệu phải được sắp xếp theo thứ tự. SQL Server có thể sử dụng nó cho các hoạt động JOIN, các hoạt động tổng hợp (DISTINCT hoặc GROUP BY) hoặc các hoạt động UNION. Điểm chung của ba hoạt động này là trong quá trình thực thi, công cụ truy vấn đang tìm kiếm các giá trị phù hợp. Trong một JOIN, chúng ta muốn tìm các hàng trong một bảng (hoặc tập hợp hàng) có các giá trị khớp với các hàng trong một bảng khác. (Và vâng, tôi biết về các phép nối không so sánh các hàng dựa trên sự bình đẳng, nhưng các liên kết không tương đương đó không liên quan đến cuộc thảo luận này.) Đối với GROUP BY, chúng tôi nhận thấy các giá trị phù hợp để đưa vào cùng một nhóm và cho UNION và DISTINCT, chúng tôi tìm kiếm các giá trị phù hợp để loại trừ chúng. (Vâng, tôi biết UNION ALL là một ngoại lệ.)
Trước SQL Server 7, cách duy nhất các thao tác này có thể dễ dàng tìm thấy các giá trị phù hợp là nếu dữ liệu được sắp xếp. Vì vậy, nếu không có chỉ mục hiện có nào duy trì dữ liệu theo thứ tự được sắp xếp, kế hoạch truy vấn sẽ thêm thao tác SORT vào kế hoạch. Hashing tổ chức dữ liệu của bạn để tìm kiếm hiệu quả bằng cách đặt tất cả các hàng có cùng kết quả từ hàm băm nội bộ vào cùng một ‘nhóm băm’.
Để có giải thích chi tiết hơn về hoạt động hash JOIN của SQL Server, bao gồm cả sơ đồ, hãy xem bài đăng blog này từ SQL Shack.
Sau khi băm trở thành một tùy chọn, SQL Server không hoàn toàn bỏ qua khả năng sắp xếp dữ liệu trước khi kết hợp hoặc tổng hợp, nhưng nó chỉ trở thành một khả năng để trình tối ưu hóa xem xét. Tuy nhiên, nói chung, nếu bạn đang cố gắng tham gia, tổng hợp hoặc thực hiện UNION trên dữ liệu chưa được sắp xếp, trình tối ưu hóa thường sẽ chọn hoạt động băm. Vì vậy, nhiều người cho rằng HASH JOIN (hoặc hoạt động HASH khác) trong một kế hoạch có nghĩa là bạn không có các chỉ mục thích hợp và bạn nên xây dựng các chỉ mục thích hợp để tránh thao tác băm.
Hãy xem một ví dụ. Trước tiên, tôi sẽ tạo hai bảng chưa lập chỉ mục.
USE AdventureWorks2016 GO DROP TABLE IF EXISTS Details;
GO
SELECT * INTO Details FROM Sales.SalesOrderDetail;
GO
DROP TABLE IF EXISTS Headers;
GO
SELECT * INTO Headers FROM Sales.SalesOrderHeader;
GO
Now, I’ll join these two tables together and filter the rows in the Details table:
SELECT *
FROM Details d JOIN Headers h
ON d.SalesOrderID = h.SalesOrderID
WHERE SalesOrderDetailID < 100;
Quest Spotlight Tuning Pack dường như không chỉ ra rằng tham gia băm là một vấn đề. Nó chỉ làm nổi bật hai lần quét bảng.
Các đề xuất khuyên bạn nên xây dựng một chỉ mục trên mỗi bảng bao gồm mọi cột không quan trọng dưới dạng cột BAO GỒM. Tôi hiếm khi thực hiện những khuyến nghị đó (như tôi đã đề cập trong bài viết trước của mình). Tôi sẽ chỉ xây dựng chỉ mục trên Chi tiết trên cột kết hợp và không có bất kỳ cột nào được bao gồm.
CREATE INDEX Header_index on Headers(SalesOrderID)
;
Sau khi chỉ mục đó được tạo, HASH JOIN sẽ biến mất. Chỉ mục sắp xếp dữ liệu trong Tiêu đề bảng và cho phép SQL Server tìm các hàng phù hợp trong bảng bên trong bằng cách sử dụng trình tự sắp xếp của chỉ mục. Bây giờ, phần đắt nhất của kế hoạch là quét trên bảng bên ngoài ( Chi tiết ) có thể được giảm bớt bằng cách tạo chỉ mục trên SalesOrderID cột trong bảng đó. Tôi sẽ để đó như một bài tập cho người đọc.
Tuy nhiên, một kế hoạch với HASH JOIN không phải lúc nào cũng là một điều xấu. Toán tử thay thế (trừ những trường hợp đặc biệt) là NESTED LOOPS JOIN, và đó thường là lựa chọn khi có các chỉ mục tốt. Tuy nhiên, hoạt động vòng lặp NESTED yêu cầu nhiều lần tìm kiếm bảng bên trong. Mã giả sau đây hiển thị thuật toán nối vòng lặp lồng nhau:
for each row R1 in the outer table
for each row R2 in the inner table
if R1 joins with R2
return (R1, R2)
Như tên cho biết, một THAM GIA NESTED LOOP được thực hiện như một vòng lặp lồng nhau. Việc tìm kiếm bảng bên trong thường sẽ được thực hiện nhiều lần, một lần cho mỗi hàng đủ điều kiện trong bảng bên ngoài. Ngay cả khi chỉ có một vài phần trăm số hàng đủ điều kiện, nếu bảng rất lớn (có thể trong hàng trăm triệu, hoặc hàng tỷ hoặc hàng) thì sẽ có rất nhiều hàng để đọc. Trong một hệ thống bị ràng buộc I / O, hàng triệu hoặc hàng tỷ lượt đọc này có thể là một nút thắt cổ chai thực sự.
Mặt khác, một HASH JOIN không thực hiện nhiều lần đọc một trong hai bảng. Nó đọc bảng bên ngoài một lần để tạo các nhóm băm và sau đó đọc bảng bên trong một lần, kiểm tra các nhóm băm để xem có hàng nào phù hợp hay không. Chúng tôi có giới hạn trên của một lần đi qua mỗi bảng. Có, cần có tài nguyên CPU để tính toán hàm băm và quản lý nội dung của nhóm. Có tài nguyên bộ nhớ cần thiết để lưu trữ thông tin đã băm. Tuy nhiên, nếu bạn có hệ thống liên kết I / O, bạn có thể còn bộ nhớ và tài nguyên CPU. HASH JOIN có thể là một lựa chọn hợp lý cho trình tối ưu hóa trong những trường hợp này khi tài nguyên I / O của bạn bị hạn chế và bạn đang tham gia các bảng rất lớn.
Đây là mã giả cho thuật toán tham gia băm:
for each row R1 in the build table
begin
calculate hash value on R1 join key(s)
insert R1 into the appropriate hash bucket
end
for each row R2 in the probe table
begin
calculate hash value on R2 join key(s)
for each row R1 in the corresponding hash bucket
if R1 joins with R2
output (R1, R2)
end
Như đã đề cập trước đó, băm cũng có thể được sử dụng cho các hoạt động tổng hợp (cũng như UNION). Một lần nữa, nếu có một chỉ mục hữu ích đã có dữ liệu được sắp xếp, việc nhóm dữ liệu có thể được thực hiện rất hiệu quả. Tuy nhiên, cũng có nhiều tình huống mà băm không phải là một toán tử tồi. Hãy xem xét một truy vấn như sau, nhóm dữ liệu trong phần Chi tiết bảng (được tạo ở trên) bởi ProductID cột. Có 121.317 hàng trong bảng và chỉ có 266 ProductID khác nhau giá trị.
SELECT ProductID, count(*)
FROM Details
GROUP BY ProductID;
GO
Sử dụng thao tác băm
Để sử dụng hàm băm, SQL Server chỉ phải tạo và duy trì 266 nhóm, con số này không phải là nhiều. Trên thực tế, Gói điều chỉnh tiêu điểm nhiệm vụ không chỉ ra rằng đó là bất kỳ vấn đề nào với truy vấn này.
Đúng, nó phải thực hiện quét bảng, nhưng đó là vì chúng tôi cần kiểm tra mọi hàng trong bảng và chúng tôi biết rằng quét không phải lúc nào cũng là điều xấu. Một chỉ mục sẽ chỉ giúp ích cho việc sắp xếp trước dữ liệu, nhưng việc sử dụng tổng hợp băm cho một số lượng nhỏ các nhóm như vậy thường sẽ vẫn cho hiệu suất hợp lý ngay cả khi không có sẵn chỉ mục hữu ích.
Giống như quét bảng, các hoạt động băm thường được coi là một toán tử 'xấu' cần có trong một kế hoạch. Có những trường hợp bạn có thể cải thiện đáng kể hiệu suất bằng cách thêm các chỉ mục hữu ích để loại bỏ các hoạt động băm, nhưng điều đó không phải lúc nào cũng đúng. Và nếu bạn đang cố gắng giới hạn số lượng chỉ mục trên các bảng được cập nhật nhiều, bạn nên lưu ý rằng các hoạt động băm không phải lúc nào cũng phải được 'cố định', vì vậy việc để truy vấn sử dụng hàm băm có thể là một điều hợp lý. làm. Ngoài ra, đối với một số truy vấn nhất định trên các bảng lớn chạy trên các hệ thống liên kết I / O, băm thực sự có thể mang lại hiệu suất tốt hơn các thuật toán thay thế vì số lần đọc cần được thực hiện hạn chế. Cách duy nhất để biết chắc chắn, đó là kiểm tra các khả năng khác nhau trên hệ thống của bạn, với các truy vấn và dữ liệu của bạn.
Trong bài đăng sau của loạt bài này, tôi sẽ cho bạn biết về các toán tử có vấn đề khác có thể hiển thị trong các kế hoạch truy vấn của bạn, vì vậy hãy kiểm tra lại sớm!