Operatör tahminlerini iyileştirmek için sorguyu değiştirme


14

Kabul edilebilir bir süre içinde çalışan bir sorgu var ama ondan mümkün olan en yüksek performansı sıkmak istiyorum.

Geliştirmeye çalıştığım operasyon, planın sağındaki Düğüm 17'den "Dizin Araması".

resim açıklamasını buraya girin

Uygun indeksler ekledim, ancak bu işlem için aldığım tahminler, olması gerekenlerin yarısı.

Dizinlerimi değiştirmek ve geçici bir tablo eklemek ve sorguyu yeniden yazmak için baktım, ama doğru tahminleri almak için bundan daha basit olamazdı.

Başka ne deneyebilirim hakkında herhangi bir öneriniz var mı?

Planın tamamını ve detaylarını burada bulabilirsiniz .

Anonimleştirilmemiş plan burada bulunabilir.

Güncelleme:

Sorunun ilk sürümü çok fazla karışıklık yarattı, bu yüzden bazı açıklamalar ile orijinal kodu ekleyeceğim bir duygu var.

create procedure [dbo].[someProcedure] @asType int, @customAttrValIds idlist readonly
as
begin
    set nocount on;

    declare @dist_ca_id int;

    select *
    into #temp
    from @customAttrValIds
        where id is not null;

    select @dist_ca_id = count(distinct CustomAttrID) 
    from CustomAttributeValues c
        inner join #temp a on c.Id = a.id;

    select a.Id
        , a.AssortmentId 
    from Assortments a
        inner join AssortmentCustomAttributeValues acav
            on a.Id = acav.Assortment_Id
        inner join CustomAttributeValues cav 
            on cav.Id = acav.CustomAttributeValue_Id
    where a.AssortmentType = @asType
        and acav.CustomAttributeValue_Id in (select id from #temp)
    group by a.AssortmentId
        , a.Id
    having count(distinct cav.CustomAttrID) = @dist_ca_id
    option(recompile);

end

Yanıtlar:

  1. PasteThePlan bağlantısında neden tek adlandırma?

    Yanıt : SQL Sentry Plan Explorer'dan anonimleştirme planı kullandığım için.

  2. Neden OPTION RECOMPILE?

    Cevap : Parametre koklamasını önlemek için yeniden derleme yapabildiğim için (veriler çarpık olabilir / eğrilebilir). Test ettik ve Optimizer'ın kullanırken ürettiği plandan memnunum OPTION RECOMPILE.

  3. WITH SCHEMABINDING?

    Cevap : Gerçekten bundan kaçınmak istiyorum ve sadece dizinlenmiş bir görünümüm olduğunda kullanacağım. Her neyse, bu bir sistem fonksiyonudur ( COUNT()) bu yüzden SCHEMABINDINGburada kullanmayın .

Daha olası soruların cevapları:

  1. Neden kullanıyorum INSERT INTO #temp FROM @customAttrributeValues?

    Cevap : Fark ettim ve şimdi kullanarak değişkenler bir sorgunun takıldığında, bir değişken ile çalışma çıkıp herhangi tahminler her zaman 1'dir olduğunu biliyoruz Ve ben bir geçici tabloya veri koyarak test edilmiş ve Çünkü Tahmini o zaman ile eşit Fiili Satırlar .

  2. Neden kullandım and acav.CustomAttributeValue_Id in (select id from #temp)?

    Cevap : #temp üzerinde bir JOIN ile değiştirebilirdim, ancak geliştiriciler çok karıştı ve INseçeneği sağladılar . Değiştirerek bile bir fark olacağını düşünmüyorum ve her iki durumda da, bununla ilgili bir sorun yok.


#tempYaratılışın ve kullanımın bir kazanç değil performans için bir sorun olacağını tahmin ediyorum. Dizine eklenmemiş bir tabloya yalnızca bir kez kullanılmak üzere kaydediyorsunuz. Tamamen kaldırmayı deneyin (ve muhtemelen in (select id from #temp)bir existsalt
sorguyla

@ ypercubeᵀᴹ Doğru, geçici tablo yerine değişken kullanılarak daha az sayfa okunur.
Radu Gheorghiu

Bu arada, bir tablo değişkeni Option (Yeniden Derleme) ile kullanıldığında doğru satır sayısı tahminini sağlar - ancak yine de ayrıntılı istatistikler, kardinalite vb.
TH

@TH select id from @customAttrValIdsBunun yerine , tahminlerde gerçek yürütme planına baktım select id from #tempve tahmini satır sayısı 1değişken ve 3#temp (gerçek satır sayısını eşleştirdi) içindi . Ben yerini yüzden @birlikte #. Ve DO onlar tbl değişkeni kullanırken bunun için tahminler her zaman 1. olacak Ve bir iyileştirme olarak daha iyi tahminler onlar geçici bir tablo kullanmak getirmek için söylemiştim (Brent O veya Aaron Bertrand itibaren) bir konuşma hatırlıyorum.
Radu Gheorghiu

@RaduGheorghiu Evet ama bu çocukların dünyasında, seçenek (yeniden derleme) nadiren bir seçenektir ve ayrıca geçerli diğer nedenlerden dolayı geçici tabloları tercih ederler. Belki de tahmin, her zaman yanlış bir şekilde 1 olarak gösterilir, çünkü burada görüldüğü gibi planı değiştirir: theboreddba.com/Categories/FunWithFlags/…
TH

Yanıtlar:


12

Plan, bir SQL Server 2008 R2 RTM örneğinde derlendi (yapı 10.50.1600). Sen yüklemeniz gerekir Service Pack 3 (güncel) en son yapı 10.50.6542 e kadar getirmek için en son yamaları takip (build 10.50.6000). Bu, güvenlik, hata düzeltmeleri ve yeni özellikler dahil olmak üzere birçok nedenden dolayı önemlidir.

Parametre Gömme Optimizasyonu

Bu soruyla ilgili olarak, SQL Server 2008 R2 RTM için Parametre Gömme Optimizasyonu'nu (PEO) desteklemedi OPTION (RECOMPILE). Şu anda, ana avantajlardan birini gerçekleştirmeden yeniden derleme maliyetini ödüyorsunuz.

PEO kullanılabilir olduğunda, SQL Server yerel değişkenlerde ve parametrelerde depolanan değişmez değerleri doğrudan sorgu planında kullanabilir. Bu çarpıcı basitleştirmelere ve performans artışlarına yol açabilir. Bu konuda daha fazla bilgi var, Parametre Koklama, Gömme ve TAVSİYE Seçenekleri .

Dökülmeleri Karıştır, Sırala ve Takas Et

Bunlar yalnızca sorgu SQL Server 2012 veya sonraki sürümlerde derlendiğinde yürütme planlarında görüntülenir. Önceki sürümlerde, sorgu Profiler veya Genişletilmiş Etkinlikler kullanılarak yürütülürken dökülmeleri izlemek zorundaydık. Dökülmeler her zaman , özellikle dökülme büyükse veya G / Ç yolu basınç altındaysa, önemli performans sonuçlarına neden olabilecek kalıcı depolama destek tempdb'ye (ve buradan) fiziksel G / Ç ile sonuçlanır.

Yürütme planınızda iki Hash Match (Aggregate) operatörü var. Karma tablosu için ayrılan bellek , çıktı satırlarının tahminine dayanır (başka bir deyişle, çalışma zamanında bulunan grup sayısı ile orantılıdır). Verilen bellek, yürütme başlamadan hemen önce sabitlenir ve örneğin ne kadar boş hafızaya sahip olduğuna bakılmaksızın yürütme sırasında büyüyemez. Sağlanan planda, her iki Hash Match (Aggregate) operatörü, optimize ediciden beklenenden daha fazla satır üretir ve bu nedenle çalışma zamanında tempdb'ye dökülme yaşayabilir .

Planda ayrıca bir Hash Match (Inner Join) operatörü var. Karma tablosu için ayrılan bellek , prob tarafı giriş satırlarının tahminine dayanır . Prob girişi 847.399 satırı tahmin eder, ancak çalışma zamanında 1.223.636 ile karşılaşılır. Bu fazlalık ayrıca karma dökülmesine neden olabilir.

Gereksiz Toplam

Düğüm 8'deki Karma Eşleşme (Toplama) üzerinde bir gruplama işlemi gerçekleştirir (Assortment_Id, CustomAttrID), ancak giriş satırları çıkış satırlarına eşittir:

Düğüm 8 Karma Eşleşmesi (Toplam)

Bu, sütun kombinasyonunun bir anahtar olduğunu gösterir (bu nedenle gruplandırma anlamsal olarak gereksizdir). Gereksiz toplamı gerçekleştirmenin maliyeti, 1,4 milyon satırı karma bölme borsalarında (her iki taraftaki Paralellik operatörleri) iki kez geçirme ihtiyacı ile artar.

İlgili sütunların farklı tablolardan geldiği göz önüne alındığında, bu benzersizlik bilgisini optimize ediciye iletmek normalden daha zordur, böylece gereksiz gruplama işleminden ve gereksiz değişimlerden kaçınabilir.

Verimsiz iplik dağılımı

Joe Obbish'in cevabında belirtildiği gibi , 14 nolu düğümdeki değişim, satırları iş parçacıkları arasında dağıtmak için karma bölümlemeyi kullanır. Ne yazık ki, az sayıda satır ve mevcut zamanlayıcılar, üç satırın da tek bir iş parçacığında sonlandığı anlamına gelir. Görünüşte paralel plan, düğüm 9'daki değişime kadar seri olarak (paralel tepegöz ile) çalışır.

Düğüm 13'teki Farklı Sıralama'yı ortadan kaldırarak bunu (yuvarlak döngü veya yayın bölümlemesi elde etmek için) ele alabilirsiniz. Bunu yapmanın en kolay yolu, #temptablo üzerinde kümelenmiş bir birincil anahtar oluşturmak ve tabloyu yüklerken farklı işlemi gerçekleştirmektir:

CREATE TABLE #Temp
(
    id integer NOT NULL PRIMARY KEY CLUSTERED
);

INSERT #Temp
(
    id
)
SELECT DISTINCT
    CAV.id
FROM @customAttrValIds AS CAV
WHERE
    CAV.id IS NOT NULL;

Geçici tablo istatistiklerinin önbelleğe alınması

OPTION (RECOMPILE)SQL Server'ın kullanılmasına rağmen, yordam çağrıları arasında geçici tablo nesnesini ve ilişkili istatistiklerini yine de önbelleğe alabilir . Bu genellikle hoş bir performans optimizasyonudur, ancak geçici tablo bitişik yordam çağrılarında benzer miktarda veri ile doldurulursa, yeniden derlenen plan yanlış istatistiklere (bir önceki yürütmeden önbelleğe alınmış) dayalı olabilir. Bu benim makalelerde ayrıntılı olarak, Saklı Usul Geçici Tablolar ve Geçici Tablo Caching Açıklaması .

Bunu önlemek için , geçici tablo doldurulduktan sonra ve bir sorguda başvurulmadan önce OPTION (RECOMPILE)bir belirtik ile birlikte UPDATE STATISTICS #TempTablekullanın.

Sorgu yeniden yazma

Bu bölüm #Temptablonun oluşturulmasında yapılan değişikliklerin önceden yapıldığını varsayar .

Olası karma dökülme maliyetleri ve fazlalık agrega (ve çevresindeki borsalar) göz önüne alındığında, setin düğüm 10'da gerçekleştirilmesi için ödeme yapılabilir:

CREATE TABLE #Temp2
(
    CustomAttrID integer NOT NULL,
    Assortment_Id integer NOT NULL,
);

INSERT #Temp2
(
    Assortment_Id,
    CustomAttrID
)
SELECT
    ACAV.Assortment_Id,
    CAV.CustomAttrID
FROM #temp AS T
JOIN dbo.CustomAttributeValues AS CAV
    ON CAV.Id = T.id
JOIN dbo.AssortmentCustomAttributeValues AS ACAV
    ON T.id = ACAV.CustomAttributeValue_Id;

ALTER TABLE #Temp2
ADD CONSTRAINT PK_#Temp2_Assortment_Id_CustomAttrID
PRIMARY KEY CLUSTERED (Assortment_Id, CustomAttrID);

Bu PRIMARY KEY, dizin oluşturma işleminin doğru kardinalite bilgisine sahip olmasını sağlamak ve geçici tablo istatistiklerinin önbelleğe alma sorununu önlemek için ayrı bir adımda eklenir.

Bu materyalizasyon, örneğin yeterli miktarda kullanılabilir belleğe sahip olması durumunda bellekte ( tempdb I / O'dan kaçınılması) gerçekleşme olasılığı yüksektir . Bu, Eager Write davranışını iyileştiren SQL Server 2012'ye (SP1 CU10 / SP2 CU1 veya üzeri) yükselttiğinizde daha da olasıdır .

Bu eylem, iyileştiriciye ara küme doğru kardinalite bilgileri verir, istatistik oluşturmasına izin verir ve (Assortment_Id, CustomAttrID)bir anahtar olarak bildirmemize izin verir .

Popülasyon planı şu şekilde #Temp2görünmelidir (Kümelenmiş dizin taramasına dikkat edin, #TempFarklı Sıralama yok ve değişim artık yuvarlak robin satır bölümleme kullanıyor):

# Temp2 nüfusu

Bu küme kullanılabilir olduğunda, son sorgu:

SELECT
    A.Id,
    A.AssortmentId
FROM
(
    SELECT
        T.Assortment_Id
    FROM #Temp2 AS T
    GROUP BY
        T.Assortment_Id
    HAVING
        COUNT_BIG(DISTINCT T.CustomAttrID) = @dist_ca_id
) AS DT
JOIN dbo.Assortments AS A
    ON A.Id = DT.Assortment_Id
WHERE
    A.AssortmentType = @asType
OPTION (RECOMPILE);

Manuel COUNT_BIG(DISTINCT...olarak basit bir şekilde yeniden yazabiliriz COUNT_BIG(*), ancak yeni anahtar bilgilerle optimize edici bunu bizim için yapar:

Nihai plan

Son plan, erişimim olmayan veriler hakkındaki istatistiksel bilgilere bağlı olarak bir döngü / karma / birleştirme birleşimi kullanabilir. Bir küçük not daha: Benzer bir indeksin CREATE [UNIQUE?] NONCLUSTERED INDEX IX_ ON dbo.Assortments (AssortmentType, Id, AssortmentId);var olduğunu varsaydım .

Her neyse, nihai planlarla ilgili önemli olan şey, tahminlerin çok daha iyi olması ve karmaşık gruplama işlemlerinin tek bir Akış Toplayıcısına (bellek gerektirmeyen ve bu nedenle diske dökülemeyen) indirgenmiş olmasıdır.

Ekstra geçici tablo ile bu durumda performansın gerçekten daha iyi olacağını söylemek zor , ancak tahminler ve plan seçimleri, veri hacmi ve zaman içindeki dağılımındaki değişikliklere çok daha dayanıklı olacaktır. Bu, uzun vadede bugün küçük bir performans artışından daha değerli olabilir. Her durumda, artık nihai kararınızı dayandıracağınız çok daha fazla bilgiye sahipsiniz.


9

Sorgunuzdaki temel tahminler aslında çok iyi. Tahmini satır sayısını, özellikle de bu kadar çok birleştirmeye sahip olduğunuzda, gerçek satır sayısıyla tam olarak eşleştirmek nadirdir. Birleştirme kardinalite tahminleri optimize edicinin doğru olması zor. Dikkat edilmesi gereken önemli bir nokta, iç içe döngünün iç kısmı için tahmini satır sayısının, o döngünün yürütülmesi başına olmasıdır. SQL Server 463869 satırının dizinle alınacağını söylediğinde, bu durumda gerçek tahmin yürütme sayısıdır (2) * 463869 = 927738, gerçek satır sayısından çok uzak değildir, 1391608. Şaşırtıcı bir şekilde, tahmin edilen satır sayısı, düğüm kimliği 10'da iç içe döngü birleşiminden hemen sonra mükemmeldir.

Sorgu iyileştirici yanlış planı seçtiğinde veya plana yeterli bellek sağlamadığında, zayıf kardinalite tahminleri çoğunlukla bir sorundur. Bu plan için tempdb'ye dökülme görmüyorum, bu yüzden bellek iyi görünüyor. Dediğiniz iç içe döngü birleşimi için küçük bir dış tablonuz ve dizinlenmiş bir iç tablonuz var. Bunun ne sorunu var? Kesin olmak gerekirse, sorgu optimize edicinin burada farklı yapmasını beklersiniz?

Performansı artırmak açısından, bana göze çarpan şey, SQL Server, hepsinin aynı iş parçacığında olmasına neden olan paralel satırları dağıtmak için bir karma algoritması kullanıyor olmasıdır:

iplik dengesizliği

Sonuç olarak, bir iş parçacığı dizin arama ile tüm işi yapar:

iş parçacığı dengesizliği arama

Bu, sorgunuzun, düğüm kimliği 9'daki yeniden bölüm akışları işlecine kadar etkin bir şekilde paralel çalışmadığı anlamına gelir. Bu, iki iş parçacığının 17 numaralı düğüm kimliği için dizin araması yapmasına olanak tanır TOP. İsterseniz buraya detay ekleyebilirim.

Gerçekten kardinalite tahminlerine odaklanmak istiyorsanız, ilk birleştirmeden sonra satırları geçici bir tabloya koyabilirsiniz. Geçici tablo üzerinde, en iyi duruma getirdiğiniz iç içe döngü birleşimi için dış tablo hakkında daha fazla bilgi veren istatistikleri toplarsanız. Ayrıca, yuvarlak robin bölünmesine de neden olabilir.

4199 veya 2301 izleme bayraklarını kullanmıyorsanız bunları dikkate alabilirsiniz. İzleme bayrağı 4199 çok çeşitli iyileştirici düzeltmeleri sunar, ancak bazı iş yüklerini azaltabilirler. İzleme bayrağı 2301 , sorgu optimize edicisinin birleştirme kardinalite varsayımlarından bazılarını değiştirir ve daha sıkı çalışmasını sağlar. Her iki durumda da etkinleştirmeden önce dikkatlice test edin.


-2

1.4 millet, optimizasyon hash veya birleştirme birleştirme ile bir dizin (küme değil) tarama yapmak için yeterli bir parçası olmadığı sürece, bu birleştirme hakkında daha iyi bir tahmin planı değiştirmek olmaz inanıyorum. Ben burada durum olmazdı şüpheli, ne de aslında yararlı, ancak değiştirerek etkisini test edebilirsiniz iç birleşim ile CustomAttributeValues karşı iç karma katılmak ve iç birleştirme katılmak .

Ben de kod daha geniş baktı ve onu geliştirmek için herhangi bir yol göremiyorum - tabii ki yanlış kanıtlanmış ilgilenen olacaktır. Ve başarmaya çalıştığınız şeyin tam mantığını yayınlamak istiyorsanız, başka bir görünüme ilgi duyarım.


3
Bu sorgu için, derleme sırası ve yuvalama, paralellik, yerel / küresel toplama vb. Gibi birçok seçenek içeren çok geniş bir plan alanı vardır, bunların çoğu türetilmiş istatistiklerdeki değişikliklerden (dağıtım ve ham kardinalite) etkilenecektir. Ayrıca, birleştiricinin ipuçlarından genellikle kaçınılması gerektiğinden OPTION(FORCE ORDER), optimize edicinin metin dizisinden yeniden sıralamayı ve ayrıca diğer birçok optimizasyonu engellediğinden de kaçınılması gerektiğini unutmayın .
Paul White 9

-12

[Kümelenmemiş] Dizin Araması'ndan ilerleme kaydedemezsiniz. Kümelenmemiş dizin aramasından daha iyi olan tek şey Kümelenmiş Dizin Aramasıdır.

Ayrıca, son on yıldır bir SQL DBA ve ondan önce beş yıldır bir SQL geliştiricisi oldum ve deneyimlerime göre, yapamayacağınız yürütme planını inceleyerek bir SQL Sorgusunda bir iyileştirme bulmak son derece nadir ' t başka yollarla bulmak. Yürütme planını oluşturmanın temel nedeni, genellikle performansı artırmak için ekleyebileceğiniz eksik dizinleri önermesidir.

Ana performans kazançları, orada herhangi bir verimsizlik varsa SQL Sorgusu'nun kendisinin ayarlanmasında olacaktır. Örneğin, birkaç ay önce bir SELECT UNION SELECTstandart pivot tabloyu standart SQL PIVOToperatörünü kullanmak için yeniden yazarak 160 kat daha hızlı çalışacak bir SQL fonksiyonum var .

insert into Variable1 values (?), (?), (?)


select *
    into Object1
    from Variable2
        where Column1 is not null;



select Variable3 = Function1(distinct Column2) 
    from Object2 Object3
        inner join Object1 Object4 on Object3.Column1 = Object4.Column1;



select Object4.Column1
        , Object4.Column3 
    from Object5 Object4
        inner join Object6 Object7
            on Object4.Column1 = Object7.Column4
        inner join Object2 Object8 
            on Object8.Column1 = Object7.Column5
    where Object4.Column6 = Variable4
        and Object7.Column5 in (select Column1 from Object1)
    group by Object4.Column3
        , Object4.Column1
    having Function1(distinct Object8.Column2) = Variable3
    option(recompile);

Bakalım, SELECT * INTOgenellikle bir standarttan daha az verimlidir INSERT Object1 (column list) SELECT column list. Bunu yeniden yazardım. Sonra, eğer Function1 a olmadan tanımlanmışsa WITH SCHEMABINDING, bir WITH SCHEMABINDINGcümle eklemek daha hızlı çalışmasına izin vermelidir.

Object2'yi Object3 olarak adlandırmak gibi anlamsız birçok takma ad seçtiniz. Kodu şaşırtmayan daha iyi takma adlar seçmelisiniz. "Object7.Column5 içinde (Object1'den Column1'i seçin)" var.

INbu nitelikteki hükümler her zaman daha verimli yazılır EXISTS (SELECT 1 FROM Object1 o1 WHERE o1.Column1 = Object7.Column5). Belki de bunu başka şekilde yazmalıydım. EXISTSher zaman en az kadar iyi olacak IN. Her zaman daha iyi değil, ama genellikle.

Ayrıca, option(recompile)burada sorgu performansını artıyor şüpheliyim . Sökmeyi test ederdim.


6
Kümelenmemiş bir dizin araması sorguyu kapsıyorsa, neredeyse her zaman kümelenmiş bir dizin aramasından daha iyi olacaktır, çünkü tanım olarak, kümelenmiş dizinin içinde tüm sütunlar vardır ve kümelenmemiş dizin daha az sütun içerir, bu nedenle daha az sayfa araması gerekir (ve b-ağacı içine daha az adım) veri almak için. Bu nedenle, kümelenmiş bir dizin aramasının her zaman daha iyi olacağını söylemek doğru değildir .
ErikE
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.