Thông thường, tôi khuyên bạn không nên sử dụng gợi ý tham gia vì tất cả các lý do tiêu chuẩn. Tuy nhiên, gần đây, tôi đã tìm thấy một mô hình mà hầu như tôi luôn tìm thấy một vòng lặp bắt buộc để thực hiện tốt hơn. Trên thực tế, tôi đang bắt đầu sử dụng và giới thiệu nó rất nhiều đến mức tôi muốn có ý kiến thứ hai để đảm bảo rằng tôi không thiếu thứ gì. Đây là một kịch bản đại diện (mã rất cụ thể để tạo một ví dụ ở cuối):
--Case 1: NO HINT
SELECT S.*
INTO #Results
FROM #Driver AS D
JOIN SampleTable AS S ON S.ID = D.ID
--Case 2: LOOP JOIN HINT
SELECT S.*
INTO #Results
FROM #Driver AS D
INNER LOOP JOIN SampleTable AS S ON S.ID = D.ID
SampleTable có 1 triệu hàng và PK của nó là ID.
Bảng tạm thời #Driver chỉ có một cột, ID, không có chỉ mục và 50K hàng.
Những gì tôi luôn tìm thấy là như sau:
Trường hợp 1: KHÔNG CÓ GỢI Ý
Chỉ mục Quét trên mẫu
Hash Hash Tham gia
thời lượng cao hơn (avg 333ms)
CPU cao hơn (avg 331ms)
Đọc logic thấp hơn (4714)
Trường hợp 2: LOOP THAM GIA GỢI Ý
Index Tìm kiếm trên SampleTable
Vòng Tham
thấp hơn Thời gian (204ms trung bình, 39% ít hơn)
thấp hơn CPU (trung bình 206, 38% ít hơn)
Phần lớn cao hơn logic Reads (160.015, 34X hơn)
Lúc đầu, số lần đọc cao hơn của trường hợp thứ hai làm tôi sợ một chút vì việc đọc thấp hơn thường được coi là một thước đo hiệu quả. Nhưng tôi càng nghĩ về những gì đang thực sự xảy ra, nó không liên quan đến tôi. Đây là suy nghĩ của tôi:
SampleTable được chứa trên 4714 trang, chiếm khoảng 36MB. Trường hợp 1 quét tất cả chúng là lý do tại sao chúng tôi nhận được 4714 lượt đọc. Hơn nữa, nó phải thực hiện 1 triệu băm, rất tốn CPU và cuối cùng sẽ tăng thời gian theo tỷ lệ. Đó là tất cả các băm này dường như làm tăng thời gian trong trường hợp 1.
Bây giờ hãy xem xét trường hợp 2. Nó không thực hiện bất kỳ băm nào, mà thay vào đó, nó đang thực hiện 50000 tìm kiếm riêng biệt, đó là những gì đang thúc đẩy việc đọc. Nhưng làm thế nào đắt là đọc tương đối? Người ta có thể nói rằng nếu đó là những lần đọc vật lý, nó có thể khá tốn kém. Nhưng hãy nhớ rằng 1) chỉ đọc lần đầu tiên của một trang nhất định có thể là vật lý và 2) ngay cả như vậy, trường hợp 1 sẽ có vấn đề tương tự hoặc tồi tệ hơn vì nó được đảm bảo đánh vào mọi trang.
Vì vậy, tính toán cho thực tế là cả hai trường hợp phải truy cập mỗi trang ít nhất một lần, nó dường như là một câu hỏi trong đó nhanh hơn, 1 triệu băm hoặc khoảng 155000 lần đọc so với bộ nhớ? Các thử nghiệm của tôi dường như nói cái sau, nhưng SQL Server luôn chọn cái trước.
Câu hỏi
Vì vậy, trở lại câu hỏi của tôi: Tôi có nên tiếp tục ép buộc gợi ý LOOP THAM GIA này khi thử nghiệm cho thấy các loại kết quả này, hoặc tôi có thiếu điều gì trong phân tích của mình không? Tôi ngần ngại chống lại trình tối ưu hóa của SQL Server, nhưng có vẻ như nó chuyển sang sử dụng hàm băm sớm hơn nhiều so với trường hợp như thế này.
Cập nhật 2014-04-28
Tôi đã thực hiện thêm một số thử nghiệm và phát hiện ra rằng kết quả tôi đạt được ở trên (trên CPU VM w / 2) Tôi không thể sao chép trong các môi trường khác (tôi đã thử trên 2 máy vật lý khác nhau với CPU 8 & 12). Trình tối ưu hóa đã làm tốt hơn nhiều trong các trường hợp sau đến mức không có vấn đề rõ rệt như vậy. Tôi đoán bài học kinh nghiệm, có vẻ hiển nhiên khi nhìn lại, là môi trường có thể ảnh hưởng đáng kể đến việc trình tối ưu hóa hoạt động tốt như thế nào.
Kế hoạch thực hiện
Kế hoạch thực hiện Trường hợp 1
Kế hoạch thực hiện Trường hợp 2

Mã để tạo trường hợp mẫu
------------------------------------------------------------
-- 1. Create SampleTable with 1,000,000 rows
------------------------------------------------------------
CREATE TABLE SampleTable
(
ID INT NOT NULL PRIMARY KEY CLUSTERED
, Number1 INT NOT NULL
, Number2 INT NOT NULL
, Number3 INT NOT NULL
, Number4 INT NOT NULL
, Number5 INT NOT NULL
)
--Add 1 million rows
;WITH
Cte0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows
Cte1 AS (SELECT 1 AS C FROM Cte0 AS A, Cte0 AS B),--4 rows
Cte2 AS (SELECT 1 AS C FROM Cte1 AS A ,Cte1 AS B),--16 rows
Cte3 AS (SELECT 1 AS C FROM Cte2 AS A ,Cte2 AS B),--256 rows
Cte4 AS (SELECT 1 AS C FROM Cte3 AS A ,Cte3 AS B),--65536 rows
Cte5 AS (SELECT 1 AS C FROM Cte4 AS A ,Cte2 AS B),--1048576 rows
FinalCte AS (SELECT ROW_NUMBER() OVER (ORDER BY C) AS Number FROM Cte5)
INSERT INTO SampleTable
SELECT Number, Number, Number, Number, Number, Number
FROM FinalCte
WHERE Number <= 1000000
------------------------------------------------------------
-- Create 2 SPs that join from #Driver to SampleTable.
------------------------------------------------------------
GO
IF OBJECT_ID('JoinTest_NoHint') IS NOT NULL DROP PROCEDURE JoinTest_NoHint
GO
CREATE PROC JoinTest_NoHint
AS
SELECT S.*
INTO #Results
FROM #Driver AS D
JOIN SampleTable AS S ON S.ID = D.ID
GO
IF OBJECT_ID('JoinTest_LoopHint') IS NOT NULL DROP PROCEDURE JoinTest_LoopHint
GO
CREATE PROC JoinTest_LoopHint
AS
SELECT S.*
INTO #Results
FROM #Driver AS D
INNER LOOP JOIN SampleTable AS S ON S.ID = D.ID
GO
------------------------------------------------------------
-- Create driver table with 50K rows
------------------------------------------------------------
GO
IF OBJECT_ID('tempdb..#Driver') IS NOT NULL DROP TABLE #Driver
SELECT ID
INTO #Driver
FROM SampleTable
WHERE ID % 20 = 0
------------------------------------------------------------
-- Run each test and run Profiler
------------------------------------------------------------
GO
/*Reg*/ EXEC JoinTest_NoHint
GO
/*Loop*/ EXEC JoinTest_LoopHint
------------------------------------------------------------
-- Results
------------------------------------------------------------
/*
Duration CPU Reads TextData
315 313 4714 /*Reg*/ EXEC JoinTest_NoHint
309 296 4713 /*Reg*/ EXEC JoinTest_NoHint
327 329 4713 /*Reg*/ EXEC JoinTest_NoHint
398 406 4715 /*Reg*/ EXEC JoinTest_NoHint
316 312 4714 /*Reg*/ EXEC JoinTest_NoHint
217 219 160017 /*Loop*/ EXEC JoinTest_LoopHint
211 219 160014 /*Loop*/ EXEC JoinTest_LoopHint
217 219 160013 /*Loop*/ EXEC JoinTest_LoopHint
190 188 160013 /*Loop*/ EXEC JoinTest_LoopHint
187 187 160015 /*Loop*/ EXEC JoinTest_LoopHint
*/