Genellikle, tüm standart nedenlerle birleştirme ipuçlarını kullanmamanızı öneririm. Ancak son zamanlarda, neredeyse her zaman daha iyi performans için zorla döngü birleştirme bulduğum bir desen buldum. Aslında, o kadar çok kullanmaya ve tavsiye etmeye başlıyorum ki, bir şey eksik olmadığımdan emin olmak için ikinci bir görüş almak istedim. İşte temsili bir senaryo (bir örnek oluşturmak için çok özel kod sonunda):
--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'ın 1 milyon satırı vardır ve PK'sı ID'dir.
Sıcaklık tablosu # Sürücüde yalnızca bir sütun, ID, dizin yok ve 50K satır var.
Sürekli olarak bulduğum şey şu:
Durum 1:
SampleTable
Karma üzerinde İPUCU Dizin Taraması
Daha Yüksek Süreye Katıl (ortalama 333ms)
Daha yüksek CPU (ortalama 331ms)
Düşük Mantıksal Okumalar (4714)
Durum 2: LOOP BİRLEŞTİRME İPUCU
Dizini SampleTable
Döngüsüne Ara
Daha Düşük Süreye Katılın (ortalama 204ms,% 39 daha az)
Daha düşük CPU (ortalama 206,% 38 daha az)
Çok Daha Yüksek Mantıksal Okuma (160015, 34X daha fazla)
İlk başta, ikinci davanın çok daha yüksek okumaları beni korkuttu, çünkü okumaları düşürmek genellikle iyi bir performans ölçüsü olarak kabul edilir. Ama gerçekte ne olduğu hakkında ne kadar çok düşünürsem, beni ilgilendirmiyor. İşte benim düşüncem:
SampleTable yaklaşık 36MB alan 4714 sayfada yer almaktadır. Durum 1 hepsini tarar, bu yüzden 4714 okuma elde ederiz. Ayrıca, CPU yoğun olan ve sonuçta zamanı orantılı olarak artıran 1 milyon karma gerçekleştirmelidir. Vaka 1'de zamanı hızlandıran tüm bu karmadır.
Şimdi durum 2'yi düşünün. Herhangi bir karma işi yapmıyor, bunun yerine 50000 ayrı arama yapıyor, bu da okumaları artıran şey. Ancak okumalar karşılaştırmalı olarak ne kadar pahalıdır? Bunların fiziksel okumalar olması oldukça pahalı olabileceğini söyleyebiliriz. Ancak unutmayın 1) belirli bir sayfanın yalnızca ilk okunması fiziksel olabilir ve 2) buna rağmen, durum 1'in her sayfaya vurulacağı garanti edildiği için aynı veya daha kötü bir soruna sahip olacaktır.
Her iki durumun da her sayfaya en az bir kez erişmek zorunda olduğu gerçeğini hesaba katarsak, sorusu daha hızlı, 1 milyon karması ya da belleğe karşı yaklaşık 155000 okuma meselesi gibi görünüyor? Testlerim ikincisini söylüyor gibi görünüyor, ancak SQL Server sürekli olarak öncekini seçiyor.
Soru
Bu yüzden soruma geri dönelim: Testler bu tür sonuçları gösterdiğinde bu LOOP JOIN ipucunu zorlamaya devam etmeli miyim yoksa analizimde bir şey mi eksik? SQL Server'ın optimizer karşı gitmek için tereddüt, ama bu gibi durumlarda olması gerektiği daha erken bir karma birleştirmek kullanmaya geçer gibi geliyor.
2014-04-28 Güncellemesi
Daha fazla test yaptım ve yukarıda elde ettiğim sonuçların (2 CPU'lu bir VM'de) diğer ortamlarda çoğaltılamadığımı keşfettim (8 ve 12 CPU'lu 2 farklı fiziksel makinede denedim). Optimize edici, sonraki durumlarda böyle belirgin bir sorunun olmadığı noktaya kadar çok daha iyi bir performans gösterdi. Sanırım geçmişe bakıldığında bariz görünen öğrenilen ders, ortamın optimize edicinin ne kadar iyi çalıştığını önemli ölçüde etkileyebileceğidir.
Uygulama Planları
Uygulama Planı Vaka 1
Uygulama Planı Vaka 2

Örnek Vaka Oluşturma Kodu
------------------------------------------------------------
-- 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
*/