Yürütme Planı'nı kullanarak T-SQL sorgusu nasıl optimize edilir


15

Deneme-yanılma ve yürütme planı, ancak boşuna kullanarak optimize etmeye çalışırken son iki gün geçirdim bir SQL sorgusu var. Lütfen bunu yaptığım için beni affet ama tüm infaz planını buraya göndereceğim. Sorgu ve yürütme planındaki tablo ve sütun adlarının hem kısalık hem de şirketimin IP'sini korumak için genel bir çaba gösterdim. Yürütme planı SQL Sentry Plan Explorer ile açılabilir .

T-SQL adil bir miktar yaptım, ama benim sorgu optimize etmek için yürütme planları kullanarak benim için yeni bir alandır ve gerçekten bunu nasıl anlamaya çalıştım. Yani, kimse bana bu konuda yardımcı olabilir ve bu yürütme planı sorgu optimize etmek için yollar bulmak için nasıl deşifre edilebilir açıklayabilir, ben sonsuza dek minettar olacaktır. Optimize etmek için daha fazla sorgum var - sadece bu konuda bana yardımcı olacak bir sıçrama tahtası gerekiyor.

Bu sorgu:

DECLARE @Param0 DATETIME     = '2013-07-29';
DECLARE @Param1 INT          = CONVERT(INT, CONVERT(VARCHAR, @Param0, 112))
DECLARE @Param2 VARCHAR(50)  = 'ABC';
DECLARE @Param3 VARCHAR(100) = 'DEF';
DECLARE @Param4 VARCHAR(50)  = 'XYZ';
DECLARE @Param5 VARCHAR(100) = NULL;
DECLARE @Param6 VARCHAR(50)  = 'Text3';

SET NOCOUNT ON

DECLARE @MyTableVar TABLE
(
    B_Var1_PK int,
    Job_Var1 varchar(512),
    Job_Var2 varchar(50)
)

INSERT INTO @MyTableVar (B_Var1_PK, Job_Var1, Job_Var2) 
SELECT B_Var1_PK, Job_Var1, Job_Var2 FROM [fn_GetJobs] (@Param1, @Param2, @Param3, @Param4, @Param6);

CREATE TABLE #TempTable
(
    TTVar1_PK INT PRIMARY KEY,
    TTVar2_LK VARCHAR(100),
    TTVar3_LK VARCHAR(50),
    TTVar4_LK INT,
    TTVar5 VARCHAR(20)
);

INSERT INTO #TempTable
SELECT DISTINCT
    T.T1_PK,
    T.T1_Var1_LK,
    T.T1_Var2_LK,
    MAX(T.T1_Var3_LK),
    T.T1_Var4_LK
FROM
    MyTable1 T
    INNER JOIN feeds.MyTable2 A ON A.T2_Var1 = T.T1_Var4_LK
    INNER JOIN @MyTableVar B ON B.Job_Var2 = A.T2_Var2 AND B.Job_Var1 = A.T2_Var3
GROUP BY T.T1_PK, T.T1_Var1_LK, T.T1_Var2_LK, T.T1_Var4_LK

-- This is the slow statement...
SELECT 
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK,
    C.C_Var1_PK,
    SUM(CONVERT(DECIMAL(18,4), A.A_Var1) + CONVERT(DECIMAL(18,4), A.A_Var2))
FROM #TempTable T
    INNER JOIN TableA (NOLOCK) A ON A.A_Var4_FK_LK  = T.TTVar1_PK
    INNER JOIN @MyTableVar     B ON B.B_Var1_PK     = A.Job
    INNER JOIN TableC (NOLOCK) C ON C.C_Var2_PK     = A.A_Var5_FK_LK
    INNER JOIN TableD (NOLOCK) D ON D.D_Var1_PK     = A.A_Var6_FK_LK
    INNER JOIN TableE (NOLOCK) E ON E.E_Var1_PK     = A.A_Var7_FK_LK  
    LEFT OUTER JOIN feeds.TableF (NOLOCK) F ON F.F_Var1 = T.TTVar5
WHERE A.A_Var8_FK_LK = @Param1
GROUP BY
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK 
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK, 
    C.C_Var1_PK


IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END

Ne buldum üçüncü ifade (yavaş olarak yorumladı) en fazla zaman alan parçasıdır. İki ifade önce neredeyse anında geri dönüyor.

Yürütme planı bu bağlantıda XML olarak kullanılabilir .

Sağ tıklayıp kaydetmek ve tarayıcınızda açmak yerine SQL Sentry Plan Explorer'da veya başka bir görüntüleme yazılımında açmak daha iyidir.

Tablolar veya veriler hakkında benden daha fazla bilgiye ihtiyacınız varsa, lütfen sormaktan çekinmeyin.


2
İstatistikleriniz çok kapalı. Dizinleri veya güncellemeleri en son ne zaman parçaladınız? Ayrıca, optimizer gerçekten tablo değişkenleri üzerindeki istatistikleri kullanamadığından, tablo değişkeni yerine @MyTableVar geçici bir tablo kullanmaya çalışacağım.
Adam Haines

Cevabınız için teşekkürler Adam. @MyTableVar öğesinin geçici tablo olarak değiştirilmesinin herhangi bir etkisi yoktur, ancak yalnızca az sayıda satırdır (yürütme planından görülebilir). İcra planında neler var, istatistiklerimin kapalı olduğunu gösteriyor? Hangi dizinlerin yeniden düzenlenmesi veya yeniden oluşturulması gerektiğini ve hangi tabloların istatistiklerin güncellenmesi gerektiğini gösteriyor mu?
Neo

3
Sağ alttaki bu karma birleştirmenin yapı girdisinde tahmini 24.000 satır var, ancak gerçek 3.285.620'ye dökülüyor olabilir tempdb. arasında birleştirme kaynaklanan satırlar için tahminler yani TableAve @MyTableVarkapalı yol vardır. Ayrıca, sıralara giren satırların sayısı tahmin edilenden çok daha fazla olduğundan dökülmeleri de iyi olabilir.
Martin Smith

Yanıtlar:


22

Ana yanıtı almadan önce, güncellemeniz gereken iki yazılım parçası vardır.

Gerekli Yazılım Güncellemeleri

Birincisi SQL Server. SQL Server 2008 Service Pack 1 çalıştırıyorsunuz (derleme 2531). En azından geçerli Service Pack'e (SQL Server 2008 Service Pack 3 - derleme 5500) eklenmiş olmanız gerekir. Yazma sırasında SQL Server 2008'in en son derlemesi Service Pack 3, Toplu Güncelleştirme 12'dir (derleme 5844).

İkinci yazılım parçası SQL Sentry Plan Explorer . En son sürümler, uzman analizi için doğrudan bir sorgu planı yükleme yeteneği (XML'yi herhangi bir yere yapıştırmaya gerek yok!) Dahil olmak üzere önemli yeni özelliklere ve düzeltmelere sahiptir.

Sorgu Planı Analizi

Tablo düzeyinde bir yeniden düzenleme sayesinde tablo değişkeni için temel tahmin tam olarak doğrudur:

tablo değişkeni tahmini

Ne yazık ki, tablo değişkenleri dağıtım istatistiklerini tutmaz, bu nedenle tüm optimizer altı satır olduğunu bilir; bu altı satırda olabilecek değerlerin hiçbirini bilmiyor. Bir sonraki işlemin başka bir tabloya birleştirilmesi göz önüne alındığında bu bilgi çok önemlidir. Bu birleştirmeden elde edilen temel tahmin, optimize edici tarafından yapılan vahşi bir tahmine dayanır:

ilk katılma tahmini

Bu noktadan sonra, optimize edici tarafından seçilen plan yanlış bilgilere dayanmaktadır, bu nedenle performansın bu kadar zayıf olması şaşırtıcı değildir. Özellikle, karma birleşimleri için ayırma ve karma tabloları için ayrılan bellek çok küçük olacaktır. Yürütme zamanında, taşan türler ve karma işlemleri fiziksel tempdb diskine dökülecektir .

SQL Server 2008 bunu yürütme planlarında vurgulamaz; Genişletilmiş Olaylar veya Profiler Sıralama Uyarıları ve Karma Uyarıları kullanarak dökülmeleri izleyebilirsiniz . Bellek, yürütme başlamadan önce kardinalite tahminlerine göre türler ve karmalar için ayrılmıştır ve SQL Server'ınızın ne kadar yedek belleğe sahip olduğuna bakılmaksızın yürütme sırasında artırılamaz. Bu nedenle, doğru satır sayısı tahminleri, çalışma alanı bellek tüketen işlemleri içeren tüm yürütme planları için çok önemlidir.

Sorgunuz da parametrelendirilir. OPTION (RECOMPILE)Farklı parametre değerleri sorgu planını etkiliyorsa sorguyu eklemeyi düşünmelisiniz . Muhtemelen zaten kullanmayı düşünmelisiniz, böylece optimizer @Param1derleme zamanında değerini görebilir . Başka bir şey değilse, bu, tablonun çok büyük ve bölümlenmiş olması koşuluyla, optimize edicinin yukarıda gösterilen dizin araması için daha makul bir tahmin üretmesine yardımcı olabilir. Statik bölüm eliminasyonu da sağlayabilir.

Geçici tablo yerine tablo değişkeni ile tekrar sorgu deneyin ve OPTION (RECOMPILE) . Ayrıca, ilk birleştirmenin sonucunu başka bir geçici tablo haline getirmeye çalışmalı ve sorgunun geri kalanını buna karşı çalıştırmalısınız. Sıra sayısı o kadar büyük değil (3.285.620), bu yüzden bu oldukça hızlı olmalı. Optimize edici daha sonra birleştirme sonucu için kesin bir kardinalite tahmini ve dağıtım istatistiklerine sahip olacaktır. Şansla birlikte, planın geri kalanı güzel bir şekilde yerine geçecek.

Planda gösterilen özelliklerden yola çıkarak, gerçekleşen sorgu şu şekilde olur:

SELECT
    A.A_Var7_FK_LK,
    A.A_Var4_FK_LK,
    A.A_Var6_FK_LK, 
    A.A_Var5_FK_LK,
    A.A_Var1,
    A.A_Var2,
    A.A_Var3_FK_LK
INTO #AnotherTempTable
FROM @MyTableVar AS B
JOIN TableA AS A
    ON A.Job = B.B_Var1_PK
WHERE
    A_Var8_FK_LK = @Param1;

Ayrıca INSERTönceden tanımlanmış geçici bir tabloya da girebilirsiniz (doğru veri türleri planda gösterilmez, bu yüzden bu kısmı yapamam). Yeni geçici tablo kümelenmiş ve kümelenmemiş dizinlerden yararlanabilir veya faydalanmayabilir.


Bu derin cevap için çok teşekkürler. Maalesef cevaplanması bir hafta sürdü - bu konuda başka işlerle serpiştirilmiş her gün çalışıyorum. TableA'ya katılmayı somutlaştıran önerilerinizi uyguladım #AnotherTempTable. Bu en iyi etkiye sahip gibi görünüyordu - diğer öneriler (@MyTableVar için bir tablo değişkeni yerine geçici bir tablo kullanmak ve kullanmanın OPTION (RECOMPILE)fazla bir etkisi veya hiç etkisi yoktu. SQL Sentry Plan Explorer'daki seçenekler harika - Ben sadece bunları kullandım: answer.sqlperformance.com/questions/1087
Neo

-6

@MyTableVar'da bir PK olması gerektiğini fark ettim ve #MyTableVar genellikle daha iyi performans gösteriyor (özellikle daha fazla sayıda satırla).

Nerede bulunan cümledeki durum

   WHERE A.A_Var8_FK_LK = @Param1

iç birleşim A AND'e taşınmalıdır. Optimizer bunu yapmak için benim deneyim yeterince akıllı değil (üzgünüm plana bakmadı) ve büyük bir fark yaratabilir.

Bu değişiklikler iyileşme göstermezse, daha sonra A'nın başka bir geçici tablosunu ve bu gruplamanın size mantıklı gelmesi halinde A.A_Var8_FK_LK = @ Param1 tarafından kısıtlanan (güzel bir şekilde) katıldığı tüm şeyleri yaratacağım.

Ardından, bir sonraki birleştirme koşulu için bu geçici tabloda (oluşturmadan önce veya sonra) kümelenmiş bir dizin oluşturun.

Ardından bu sonucu kalan birkaç tabloya (F ve T) katılın.

Satır tahminleri kapalıyken ve bazen değilken kokuşmuş bir sorgu planına ihtiyaç duyan Bam yine de kolayca iyileştirilemediğinde ). Planda ilk kontrol ettiğim doğru endekslere sahip olduğunuzu varsayıyorum.

İz, sert bir etkiye sahip olan veya olmayan tempdb dökülmelerini gösterebilir.

Başka bir alternatif yaklaşım - en azından denemek daha hızlı - tabloları en az sayıda satırdan (A) en yükseğe sıralamak ve daha sonra birleştirmelere birleştirme, karma ve döngü eklemeye başlamaktır. İpuçları varsa, birleştirme sırası belirtildiği gibi sabitlenir. Diğer kullanıcılar bu yaklaşımdan akıllıca kaçınırlar, çünkü göreli satır sayısı önemli ölçüde değişirse uzun vadede zarar verebilir. Minimum sayıda ipucu istenir.

Bunların çoğunu yapıyorsanız, belki de ticari bir optimize edici denemeye (veya denemeye) değer ve hala iyi bir öğrenme deneyimidir.


Evet öyle. A tarafından döndürülen satırların kısıtlama ile sınırlı olmasını sağlar. Aksi takdirde, optimize edici önce katılabilir ve daha sonra kısıtlamayı uygulayabilir. Bununla her gün ilgileniyorum.
crokusek

4
@crokusek Sadece yanlışsın. SQL Server'ın optimizer, bir INNER birleşimi olduğunda sorguların eşdeğer olduğunu (bir koşulun WHERE veya ON yan tümcesinde olup olmadığını) bilmede oldukça iyidir.
ypercubeᵀᴹ

6
Paul White'ın Sorgu Optimize Edici'deki serisini yararlı bulabilirsiniz.
Martin Smith

Bu korkunç bir alışkanlık. Belki bu özel durum için (bir kısıtlamanın olduğu yerde) olacak, ancak nerede maddesinde AND koşullarını kullanan birden fazla geliştiricinin ülkesinden geliyorum. SQL Server does not sürekli "hareket" onları sizin için için katılmak destekliyor.
crokusek

Dış (ve sağ birleşimler) için yanlış kabul edin. Ancak burada bir yan tümce içinde yalnızca AND'd ifadeleri olduğunda ve her terim yalnızca belirli bir iç birleşime karşılık geldiğinde, bu terim, bir optimizasyon ve en iyi uygulama (imo) olarak güvenli bir şekilde "açık" konuma taşınabilir. "Gerçek" birleştirme koşulu mu, yoksa sadece sabit bir kısıtlama olsun, büyük bir performans kazancının ikincisidir. Bu bağlantı önemsiz bir durum için. Gerçek hayatın, convert () ve math ve böylece onları en iyi uygulamaları türetmek için daha iyi adaylar haline getiren birden fazla yeri vardır.
crokusek
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.