Як правило, я рекомендую не використовувати підказки для приєднання з усіх стандартних причин. Проте останнім часом я знайшов схему, за якою майже завжди знаходжу примусовий цикл приєднання, щоб краще працювати. Насправді я починаю користуватися і рекомендувати його настільки, що мені хотілося отримати другу думку, щоб переконатися, що я чогось не пропускаю. Ось репрезентативний сценарій (дуже специфічний код для створення прикладу знаходиться в кінці):
--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 має 1 мільйон рядків, і його ПК - ID.
У таблиці темп #Driver є лише один стовпець, ідентифікатор, відсутні індекси та 50K рядків.
Я послідовно знаходжу таке:
Випадок 1: НЕ
підказки сканування індексу на
Hash SampleTable приєднатися з більшою
тривалістю (середньо 333 мс)
Вища CPU (середня 331 мс)
Нижня логічна читання (4714)
Випадок 2:
Індекс LOOP JOIN HINT ( Шукати підказку LOOP JOIN) Шукайте нижчу
тривалість приєднання
циклу SampleTable
(середнє 204 мс, на 39% менше)
Нижчий процесор (середнє 206, 38% менше)
Набагато більше логічних читань (160015, 34X більше)
По-перше, набагато вищі показники другого випадку мене трохи налякали, оскільки зниження читання часто вважається гідною мірою продуктивності. Але чим більше я думаю про те, що насправді відбувається, це мене не стосується. Ось моє мислення:
SampleTable міститься на 4714 сторінках, що займає близько 36 Мб. Випадок 1 сканує їх усі, тому ми отримуємо 4714 прочитаних. Крім того, він повинен виконати 1 мільйон хешів, які є інтенсивними процесорами, і в кінцевому підсумку пропорційно збільшує час. Саме це хеширование, здається, призводить до збільшення часу у випадку 1.
Тепер розглянемо випадок 2. Він не робить хешування, але натомість робить 50000 окремих пошуків, і це те, що сприяє читанню. Але наскільки дорого коштують прочитані? Можна сказати, що якщо це фізичні показання, це може бути досить дорогим. Але майте на увазі: 1) лише перше прочитання даної сторінки може бути фізичним, і 2) навіть у тому випадку, якщо випадок 1 матиме ту саму чи гіршу проблему, оскільки гарантовано потрапляти на кожну сторінку.
Отже, враховуючи той факт, що обидва випадки мають хоча б один раз отримати доступ до кожної сторінки, здається, питання про те, що швидше, 1 мільйон хешів або близько 155000 зчитувань проти пам'яті? Мої тести, здається, говорять про останнє, але SQL Server послідовно вибирає перший.
Питання
Повертаємось до мого запитання: чи варто продовжувати змушувати цей підказку LOOP JOIN, коли тестування показує такі результати, чи я щось пропускаю в своєму аналізі? Я вагаюся проти оптимізатора SQL Server, але відчуваю, що він переходить на використання хеш-з'єднання набагато раніше, ніж слід у таких випадках.
Оновлення 2014-04-28
Я зробив ще кілька тестувань і виявив, що результати, які я отримував вище (на VM w / 2 процесорах), я не міг повторювати в інших середовищах (я спробував на двох різних фізичних машинах з 8 та 12 процесорами). Оптимізатор зробив набагато краще в останніх випадках до того моменту, коли не було такого яскраво вираженого питання. Я здогадуюсь, що засвоєний урок, який здається очевидним у ретроспективі, полягає в тому, що навколишнє середовище може суттєво впливати на ефективність роботи оптимізатора.
Плани виконання
План виконання Випадок 1
План виконання Випадок 2

Код для створення зразка
------------------------------------------------------------
-- 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
*/