Bu sorgunun sonuçta ortaya çıkan tüm sütunlarını seçmek neden önem verdiğim bir sütunu seçmekten daha hızlı?


13

select *Sadece çok daha az okuma kullanarak değil, aynı zamanda kullanmaktan çok daha az CPU zamanı kullanan bir sorgu var select c.Foo.

Bu sorgu:

select top 1000 c.ID
from ATable a
    join BTable b on b.OrderKey = a.OrderKey and b.ClientId = a.ClientId
    join CTable c on c.OrderId = b.OrderId and c.ShipKey = a.ShipKey
where (a.NextAnalysisDate is null or a.NextAnalysisDate < @dateCutOff)
    and b.IsVoided = 0
    and c.ComplianceStatus in (3, 5)
    and c.ShipmentStatus in (1, 5, 6)
order by a.LastAnalyzedDate

Bu, çoğunlukla Tablo B'de 2,473,658 mantıksal okuma ile sona erdi. 26,562 CPU kullandı ve 7,965 sürdü.

Oluşturulan sorgu planı şudur:

Tek bir sütunun değerini seçerek planlama PasteThePlan'da: https://www.brentozar.com/pastetheplan/?id=BJAp2mQIQ

Ben değiştirdiğinizde c.IDiçin *, sorgu mantıksal 107.049 oldukça eşit üç tablo arasında yayılmış, okur ile tamamladı. 4.266 CPU kullandı ve 1.147 süresine sahipti.

Oluşturulan sorgu planı şudur:

Tüm değerleri seçerek planlama PasteThePlan'da: https://www.brentozar.com/pastetheplan/?id=SyZYn7QUQ

Joe Obbish tarafından önerilen sorgu ipuçlarını şu sonuçlarla kullanmaya çalıştım:
select c.IDipucu olmadan: https://www.brentozar.com/pastetheplan/?id=SJfBdOELm
select c.ID ipucu ile: https://www.brentozar.com/pastetheplan/ ? id = B1W ___
select * İpucu olmadan ipucu: https://www.brentozar.com/pastetheplan/?id=HJ6qddEIm
select * ile ipucu: https://www.brentozar.com/pastetheplan/?id=rJhhudNIQ

İpucunun kullanılması, OPTION(LOOP JOIN)ipucu select c.IDolmadan sürüme kıyasla okuma sayısını önemli ölçüde azalttı, ancak yine de select *herhangi bir ipucu olmadan sorgu okuma sayısını yaklaşık 4 kat yapıyor . Sorgu eklemek OPTION(RECOMPILE, HASH JOIN), select *ben denedim başka bir şey çok daha kötü performans yaptı.

Tablolar ve kullanarak endeksler ilgili istatistikleri güncelledikten sonra WITH FULLSCAN, select c.IDsorgu çalışırken çok daha hızlı:
select c.IDgüncellemeden önce: https://www.brentozar.com/pastetheplan/?id=SkiYoOEUm
select * güncellemeden önce: https://www.brentozar.com/
select c.ID Güncellemeden sonra pastetheplan /? id = ryrvodEUX : https://www.brentozar.com/pastetheplan/?id=B1MRoO487
select * güncelleme sonrası: https://www.brentozar.com/pastetheplan/?id=Hk7si_V8m

select *select c.IDtoplam süre ve toplam okuma sayısı bakımından hala daha iyi performans gösteriyor (okumaların select *yaklaşık yarısı var) ancak daha fazla CPU kullanıyor. Genel olarak, güncellemeden çok daha yakınlar, ancak planlar hala farklı.

Aynı davranış, 2014 Uyumluluk modunda çalışan 2014 ve 2014'te de görülür. İki plan arasındaki eşitsizliği ne açıklayabilir? "Doğru" indeksler oluşturulmamış olabilir mi? İstatistikler biraz güncelliğini yitirmiş olabilir mi?

Tahminleri ONbirleştirme kısmına kadar birden çok şekilde taşımaya çalıştım , ancak sorgu planı her seferinde aynı.

Endeks Yeniden Oluşturulduktan Sonra

Sorguda yer alan üç tablodaki tüm dizinleri yeniden oluşturdum. c.IDhala en çok okunan değeri (iki katından fazla *) yapıyor, ancak CPU kullanımı *sürümün yaklaşık yarısı . c.IDSürümü de malzemelerin tasnifi üzerinde tempdb içine dökülen ATable:
c.ID: https://www.brentozar.com/pastetheplan/?id=HyHIeDO87
* : https://www.brentozar.com/pastetheplan/?id=rJ4deDOIQ

Ayrıca paralellik olmadan çalışmaya zorlamaya çalıştım ve bu bana en iyi performans gösteren sorguyu verdi: https://www.brentozar.com/pastetheplan/?id=SJn9-vuLX

Tek iş parçacıklı sürümde yalnızca 1.000 kez yürütülen, ancak çeşitli işleçlerin 2.622 ve 4.315 yürütme arasında Paralelleştirilmiş sürümde yapılan büyük dizin aramasından SONRA operatörlerin yürütme sayısını fark ettim.

Yanıtlar:


4

Daha fazla sütun seçmenin SQL Server'ın sorgunun istenen sonuçlarını almak için daha fazla çalışması gerekebileceği anlamına geldiği doğrudur. Sorgu optimize edici her iki sorgu için mükemmel bir sorgu planı ile gelebildiyse, o zaman beklemek mantıklı olacaktırSELECT *sorgu tüm tablolardan tüm sütunları seçen sorgudan daha uzun çalışacak. Sorgu çiftiniz için bunun tersini gözlemlediniz. Maliyetleri karşılaştırırken dikkatli olmanız gerekir, ancak yavaş sorgunun toplam tahmini maliyeti 1090.08 iyileştirici birimdir ve hızlı sorgunun toplam tahmini maliyeti 6823.11 iyileştirici birimidir. Bu durumda, optimize edicinin toplam sorgu maliyetlerini tahmin etmekle kötü bir iş çıkardığı söylenebilir. SELECT * sorgunuz için farklı bir plan seçti ve bu planın daha pahalı olmasını bekledi, ancak burada durum böyle değildi. Bu tür uyumsuzluk birçok nedenden dolayı olabilir ve en yaygın nedenlerden biri kardinalite tahmin problemleridir. Operatör maliyetleri büyük ölçüde kardinalite tahminleriyle belirlenir. Bir planın önemli bir noktasında bir kardinalite tahmini yanlışsa, planın toplam maliyeti gerçeği yansıtmayabilir. Bu büyük bir aşırı basitleştirme ama umarım burada neler olduğunu anlamak için faydalı olacaktır.

Bir SELECT *sorgunun neden tek bir sütun seçmekten daha pahalı olabileceğini tartışarak başlayalım . SELECT *Sorgu en iyi duruma ihtiyacı sütunların tümünü almak için bir ek iş yapmak gerekiyor yoksa daha büyük bir dizinden okumak gerekebilir anlamına gelebilir noncovering endeksler, bazı kapsayan endeksler dönüşebilir.SELECT *ayrıca sorgu yürütme sırasında işlenmesi gereken daha büyük ara sonuç kümeleriyle sonuçlanabilir. Her iki sorgudaki tahmini satır boyutlarına bakarak bunu çalışırken görebilirsiniz. Hızlı sorguda satır boyutlarınız 664 bayt ile 3019 bayt arasında değişir. Yavaş sorguda satır boyutlarınız 19 ila 36 bayt arasında değişir. Sıralama veya karma oluşturma gibi engelleme işleçleri, daha büyük satır boyutuna sahip veriler için daha yüksek maliyetlere sahip olacaktır, çünkü SQL Server daha büyük miktarlardaki verileri sıralamanın veya bir karma tablosuna dönüştürmenin daha pahalı olduğunu bilir.

Hızlı sorguyu inceleyen optimizer, 2,4 milyon endeks arayışında olması gerektiğini tahmin ediyor Database1.Schema1.Object5.Index3. Plan maliyetinin çoğu buradan gelir. Ancak gerçek plan, bu operatörde sadece 1332 endeks arayışının yapıldığını göstermektedir. Eğer bu döngü birleşimlerinin dış kısımları için gerçekleri tahmini satırlarla karşılaştırırsanız, büyük farklılıklar görürsünüz. Optimize edici, sorgunun sonuçları için gereken ilk 1000 satırı bulmak için daha fazla dizin aramasının gerekli olacağını düşünüyor. Bu nedenle, sorgunun nispeten yüksek bir maliyet planı vardır, ancak çok hızlı bir şekilde biter: en pahalı olduğu tahmin edilen operatör, beklenen çalışmasının% 0,1'inden daha azını yaptı.

Yavaş sorguya bakarak, çoğunlukla karma birleşimleri ile bir plan olsun (döngü katılmak sadece yerel değişken ile başa çıkmak için orada olduğuna inanıyorum). Kardinalite tahminleri kesinlikle mükemmel değil, ancak tek gerçek tahmin problemi sonunda doğru. Çoğu zaman yüz milyonlarca satır içeren tabloların taramalarında harcandığından şüpheleniyorum.

Diğer sürümle ilişkili sorgu planını zorlamak için, sorgunun her iki sürümüne de sorgu ipucu eklemeyi yararlı bulabilirsiniz. Sorgu ipuçları, optimize edicinin neden bazı seçimler yaptığını anlamak için iyi bir araç olabilir. Eğer eklerseniz OPTION (RECOMPILE, HASH JOIN)için SELECT *sorgunun sana karma benzer sorgu planı sorgu katılmak göreceksiniz bekliyoruz. Ayrıca satır boyutlarınız çok daha büyük olduğundan sorgu maliyetlerinin karma birleştirme planı için çok daha yüksek olacağını umuyorum. Bu nedenle, sorgu için karma birleştirme sorgusu seçilmemiş olabilir SELECT *. OPTION (LOOP JOIN)Yalnızca bir sütun seçen sorguyu eklerseniz , bunun için benzer bir sorgu planı görmenizi beklerim.SELECT *sorgu. Bu durumda, satır boyutunu küçültmenin toplam sorgu maliyeti üzerinde çok fazla etkisi olmamalıdır. Önemli aramaları atlayabilirsiniz, ancak bu, tahmini maliyetin küçük bir yüzdesidir.

Özetle, SELECT *sorguyu tatmin etmek için gereken daha büyük satır boyutlarının , iyileştiriciyi bir karma birleştirme planı yerine bir döngü birleştirme planına doğru itmesini bekliyorum. Döngü birleştirme planı, kardinalite tahmini sorunları nedeniyle olması gerekenden daha yüksek maliyetlidir. Yalnızca bir sütun seçerek satır boyutlarını azaltmak, bir karma birleştirme planının maliyetini büyük ölçüde azaltır, ancak bir döngü birleştirme planı maliyeti üzerinde büyük bir etkisi olmayacaktır, bu nedenle daha az verimli karma birleştirme planı ile sonuçlanırsınız. Anonimleştirilmiş bir plan için bundan daha fazlasını söylemek zor.


Geniş ve bilgilendirici yanıtınız için çok teşekkür ederim. Önerdiğiniz ipuçlarını eklemeyi denedim. select c.IDSorguyu çok daha hızlı hale getirdi, ancak hala select *ipucu olmadan sorgunun yaptığı bazı ekstra işler yapıyor.
L. Miller

2

Eski istatistikler kesinlikle optimize edicinin verileri bulmak için kötü bir yöntem seçmesine neden olabilir. Dizinde bir UPDATE STATISTICS ... WITH FULLSCANveya bir tam yapmayı denediniz REBUILDmi? Bunu deneyin ve yardımcı olup olmadığını görün.

GÜNCELLEME

OP'nin bir güncellemesine göre:

Tablolar ve kullanarak endeksler ilgili istatistikleri güncelledikten sonra WITH FULLSCAN, select c.IDsorgu çok daha hızlı çalışıyor

Alınan tek eylem olsaydı Yani, şimdi, UPDATE STATISTICSdaha sonra bir dizin yapmayı deneyin REBUILD(değil REORGANIZEtahmini satır sayıları her ikisi ile yardım olduğunu gördüğüm gibi) UPDATE STATISTICSve endeks REORGANIZEyoktu.


Hafta sonu boyunca yeniden oluşturmak için üç tablodaki tüm dizinleri elde edebildim ve bu sonuçları yansıtacak şekilde yazımı güncelledim.
L. Miller

-1
  1. Lütfen dizin betiklerini ekleyebilir misiniz?
  2. "Parametre koklama" ile ilgili olası sorunları ortadan kaldırdınız mı? https://www.mssqltips.com/sqlservertip/3257/different-approaches-to-correct-sql-server-parameter-sniffing/
  3. Bu tekniği bazı durumlarda yardımcı buldum:
    a) her bir tabloyu bir alt sorgu olarak yeniden yazın, şu kuralları takip edin:
    b) SELECT - önce birleştirme sütunları koy
    c) PREDICATES - kendi alt sorgularına gitme
    d) ORDER BY - İLK KOLONLARA KATILIN
    e) Son sıralama ve SELECT için bir sarıcı sorgusu ekleyin.

Buradaki fikir, her bir alt seçimin içindeki birleştirme sütunlarını önceden sıralamak ve her seçim listesinde birleştirme sütunlarını ilk sıraya koymaktır.

İşte demek istediğim ....

SELECT ... wrapper query
FROM
(
    SELECT ...
    FROM
        (SELECT ClientID, ShipKey, NextAnalysisDate
         FROM ATABLE
         WHERE (a.NextAnalysisDate is null or a.NextAnalysisDate < @dateCutOff) -- Predicates
         ORDER BY OrderKey, ClientID, LastAnalyzedDate  ---- Pre-sort the join columns
        ) as a
        JOIN 
        (SELECT OrderKey, ClientID, OrderID, IsVoided
         FROM BTABLE
         WHERE IsVoided = 0             ---- Include all predicates
         ORDER BY OrderKey, OrderID, IsVoided       ---- Pre-sort the join columns
        ) as b ON b.OrderKey = a.OrderKey and b.ClientId = a.ClientId
        JOIN
        (SELECT OrderID, ShipKey, ComplianceStatus, ShipmentStatus, ID
         FROM CTABLE
         WHERE ComplianceStatus in (3, 5)       ---- Include all predicates
             AND ShipmentStatus in (1, 5, 6)        ---- Include all predicates
         ORDER BY OrderID, ShipKey          ---- Pre-sort the join columns
        ) as c ON c.OrderId = b.OrderId and c.ShipKey = a.ShipKey
) as d
ORDER BY d.LastAnalyzedDate

1
1. Orijinal yazıya "DDL" betikleri eklemeye çalışacağım, bunları "fırçalamak" biraz zaman alabilir. 2. Bu olasılığı hem çalıştırmadan önce plan önbelleğini temizleyerek hem de bind parametresini gerçek bir değerle değiştirerek test ettim. 3. Bunu ORDER BYdenedim , ancak bir TOP, FORXML, vb olmadan bir alt sorguda geçersiz. Ben ORDER BYyan tümceleri olmadan denedim ama aynı plan oldu.
L. Miller
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.