In genere, sconsiglio di utilizzare i suggerimenti di join per tutti i motivi standard. Di recente, tuttavia, ho trovato uno schema in cui trovo quasi sempre un join ad anello forzato per ottenere prestazioni migliori. In effetti, sto iniziando a usarlo e lo consiglio così tanto che volevo ottenere una seconda opinione per assicurarmi che non mi mancasse qualcosa. Ecco uno scenario rappresentativo (alla fine il codice molto specifico per generare un esempio):
--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 ha 1 milione di righe e il suo PK è ID.
Tabella temporanea #Driver ha solo una colonna, ID, nessun indice e 50K righe.
Quello che trovo costantemente è il seguente:
Caso 1: NO SUGGERIMENTO
Scansione indice su SampleTable
Hash Unisci
durata maggiore (avg 333ms)
CPU superiore (avg 331ms)
Letture logiche inferiori (4714)
Caso 2: LOOP JOIN HINT
Index Cerca su SampleTable
Loop Partecipa a
durata inferiore (media 204 ms, 39% in meno)
CPU inferiore (media 206, 38% in meno)
Letture logiche molto più alte (160015, 34X in più)
All'inizio, le letture molto più alte del secondo caso mi spaventavano un po 'perché abbassare le letture è spesso considerata una misura decente delle prestazioni. Ma più penso a ciò che sta realmente accadendo, non mi riguarda. Ecco il mio pensiero:
La SampleTable è contenuta in 4714 pagine, impiegando circa 36 MB. Il caso 1 li analizza tutti, motivo per cui otteniamo 4714 letture. Inoltre, deve eseguire 1 milione di hash, che sono ad alta intensità di CPU e che alla fine aumentano il tempo in modo proporzionale. È tutto questo hashing che sembra aumentare il tempo nel caso 1.
Ora prendi in considerazione il caso 2. Non sta facendo alcun hash, ma sta facendo 50000 ricerche separate, che è ciò che sta guidando le letture. Ma quanto costano le letture comparativamente? Si potrebbe dire che se si tratta di letture fisiche, potrebbe essere piuttosto costoso. Ma tieni presente 1) solo la prima lettura di una determinata pagina potrebbe essere fisica, e 2) anche così, il caso 1 avrebbe lo stesso o peggio problema poiché è garantito che colpisca ogni pagina.
Quindi, tenendo conto del fatto che entrambi i casi devono accedere a ciascuna pagina almeno una volta, sembra essere una domanda di quale sia più veloce, 1 milione di hash o circa 155000 letture contro la memoria? I miei test sembrano dire quest'ultimo, ma SQL Server sceglie coerentemente il primo.
Domanda
Quindi, tornando alla mia domanda: dovrei continuare a forzare questo suggerimento LOOP JOIN quando il test mostra questo tipo di risultati o mi manca qualcosa nella mia analisi? Sono titubante nell'andare contro l'ottimizzatore di SQL Server, ma sembra che passi a usare un hash join molto prima di quanto dovrebbe in casi come questi.
Aggiornamento 28-04-2014
Ho fatto qualche altro test e ho scoperto che i risultati che stavo ottenendo sopra (su una VM con 2 CPU) non potevo replicare in altri ambienti (ho provato su 2 diverse macchine fisiche con 8 e 12 CPU). L'ottimizzatore ha fatto molto meglio negli ultimi casi fino al punto in cui non si è verificato un problema così pronunciato. Immagino che la lezione appresa, che sembra ovvia in retrospettiva, sia che l'ambiente può influenzare in modo significativo il funzionamento dell'ottimizzatore.
Piani di esecuzione
Piano di esecuzione caso 1
Piano di esecuzione caso 2

Codice per generare un caso di esempio
------------------------------------------------------------
-- 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
*/