TOP 1 eklemek neden performansı önemli ölçüde kötüleştiriyor?


39

Oldukça basit bir sorgu var

SELECT TOP 1 dc.DOCUMENT_ID,
        dc.COPIES,
        dc.REQUESTOR,
        dc.D_ID,
        cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
    ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
  AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER

Bu bana korkunç bir performans kazandırıyor (bitmesini beklemekten asla sıkılmadığı gibi). Sorgu planı şöyle görünür:

görüntü tanımını buraya girin

Ancak, kaldırırsam TOP 1böyle görünen bir plan alırım ve 1-2 saniye içinde çalışır:

görüntü tanımını buraya girin

Doğru PK ve aşağıda indeksleme.

TOP 1Sorgu planını değiştirdiğim gerçeği beni şaşırtmadı, onu daha da kötüleştirmesine şaşırdım.

Not: Ben bu sonuçları okumam sonrası ve kavramını Row GoalYaklaşık Daha iyi bir plan kullanması için sorgu değiştirme hakkında gitmek nasıl olduğunu merak ediyorum ne vb. Şu anda verileri geçici bir tabloya aktarıyorum, ardından ilk satırı çekiyorum. Daha iyi bir yöntem olup olmadığını merak ediyorum.

Düzenleme Bu gerçeği sonra bunu okuyan insanlar için birkaç ekstra bilgi parçası var.

  • Document_Queue - PK / CI D_ID ve ~ 5k satır var.
  • Yazışma_Journal - PK / CI, FILE_NUMBER, CORRESPONDENCE_ID ve ~ 1.4 mil satırlık bir değere sahip.

Başladığımda başka indeks yoktu. Correspondence_Journal'da bir tanesine son verdim (Document_Id, File_Number)


1
DOCUMENT_IDİki tablo arasındaki ilişkiyi zorlayan yabancı bir anahtar kısıtınız var mı (ya da her kayıtta CORRESPONDENCE_JOURNALeşleşen bir kayıt var DOCUMENT_QUEUEmı)?
Daniel Hutmacher,

Yanıtlar:


28

Bir karma birleştirmeyi zorlamayı deneyin *

SELECT TOP 1 
       dc.DOCUMENT_ID,
       dc.COPIES,
       dc.REQUESTOR,
       dc.D_ID,
       cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
INNER HASH JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
       AND dc.QUEUE_DATE <= GETDATE()
       AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER

Optimizer muhtemelen bir döngünün ilk 1 ile daha iyi olacağını düşündü ve bu tür mantıklı ama gerçekte burada işe yaramadı. Burada bir tahminde bulunun, ancak belki de bu makara tahmininin maliyeti düşmüştür - TEMPDB kullanır - düşük performanslı bir TEMPDB olabilir.


* Birleştirme ipuçlarına dikkat edin , çünkü tablo erişim sırasını sorgudaki tabloların yazılı sırasına uymaya zorlarlar (tıpkı OPTION (FORCE ORDER)belirtilmiş gibi). Belgeleme bağlantısından:

BOL özü

Bu, örnekte istenmeyen herhangi bir etki yaratmayabilir, ancak genel olarak çok iyi olabilir. FORCE ORDER(ima edilen veya açık olan) düzenin ötesine geçen çok güçlü bir ipucu; kısmi toplamalar ve yeniden sıralama dahil olmak üzere geniş bir yelpazede optimize edici tekniklerin uygulanmasını önler.

Bir OPTION (HASH JOIN) sorgu ipucu uygun durumlarda daha az müdahaleci olabilir, çünkü bunun anlamı yoktur FORCE ORDER. Ancak, sorgudaki tüm katılımlar için geçerlidir. Başka çözümler de var.


1
Doğru cevaba benziyor ve bununla daha basit plan arasındaki tek fark, öndeki ilave bir Sıralama idi.
Kenneth Fisher

3
Bu cevabı sevdiğimden emin değilim. Birleştirme ipuçları çok istilacı. Bazı basit indeksleme değişiklikleri önce denenmelidir, örneğin tarih sütununda indeks.
usr

@ usr Bir saniye içinde çalışan basit bir PK birleşimidir. Burada oldukça güvenli bir bahis.
paparazzo,

4
Bir karma birleşmeye zorlamada, büyük tablonun taranmasını zorlarsınız. Daha iyi seçenekler var.
Rob Farley

30

Doğru planladığın için ORDER BY, belki kendi TOPoperatörünü atabilirsin ?

SELECT DOCUMENT_ID, COPIES, REQUESTOR, D_ID, FILE_NUMBER
FROM (
    SELECT dc.DOCUMENT_ID,
           dc.COPIES,
           dc.REQUESTOR,
           dc.D_ID,
           cj.FILE_NUMBER,
           ROW_NUMBER() OVER (ORDER BY cj.FILE_NUMBER) AS _rownum
    FROM DOCUMENT_QUEUE dc
    INNER JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
    WHERE dc.QUEUE_DATE <= GETDATE()
      AND dc.PRINT_LOCATION = 2
) AS sub
WHERE _rownum=1;

Benim düşünceme göre, ROW_NUMBER()yukarıdakiler için sorgu planı , sahip olduğunuzla aynı olmalıdır ORDER BY. Sorgu planında artık bir Segment, Sıra Projesi ve son olarak bir Filtre operatörü bulunmalı, gerisi de sizin iyi planınıza benzemeli.


3
Aslında en iyi operatöre (ve bir sürü başka şey (bir sıra projesi, segment ve sıralama)) verirken hala ikinci sırada koştu. Frizbi için doğru cevabı vereceğim ama ilk ve daha basittir. Yine de harika cevap.
Kenneth Fisher

10
@KennethFisher, frizbinin cevabı daha basittir, ancak bu şekilde bir balyoz, bitirme çivisini standart çerçeveleme çekiçinden daha basit bir şekilde sürmektedir. Aynı zamanda, özellikle uzun mesafe için yerinde bırakıldığında, büyük bir risk ile geliyor. Testler dışında belki de ipucu kullanmam, belki de bir saçak istisnası olabilir.
Steve Mangiameli,

@SteveMangiameli Bu özel durumda, yalnızca bir üye var, bu yüzden bazı endişeler gider. Bir katılma ipucu (veya sorgu ipucu) kullanmanın risklerinin farkındayım. Sadece bu durumda haklı olduğunu düşünüyorum.
Kenneth Fisher

5
@KennethFisher İmo , sorgu ipuçlarının ana riski, verileriniz büyüdükçe veya değiştikçe, uyguladığınız sorgu planının, sistemin kendi başına bulacağından daha kötü olabileceğidir. Plandaki küçük bir hatanın performansı ciddi şekilde nasıl etkileyebileceğini zaten gördünüz. Üretimde bir ipucu ilan edilir kullanma "bu plan her zaman olacaktır ben biliyorum, hep bu yüzden tamamen planlayıcısı anlamak ve benim veri üretiminde bu sorgunun yaşamı boyunca nasıl davranacağını çünkü en iyi olmak." Bir sorgu hakkında hiç bu kadar emin olmamıştım.
jpmc26

29

Düzenleme: +1 bu durumda çalışır, çünkü bunun FILE_NUMBERbir tamsayı'nın sıfır dolgulu dize sürümüdür. Buradaki dizgiler için daha iyi bir çözüm '', bir değer eklemek sırasını etkileyebileceği için veya (sabit dize gibi sabit olan ancak belirleyici olmayan bir işlev içeren bir şey eklemek için sayıların eklenmesidir (boş dize) sign(rand()+1). 'Sıralamayı bozma' fikri hala burada geçerli, sadece yöntemim ideal değildi.

+1

Hayır, demek istediğim hiçbir şeye katılmıyorum, demek istediğim bir çözüm olarak. Sorgunuzu değiştirirseniz, ORDER BY cj.FILE_NUMBER + 1o zaman TOP 1farklı davranır.

Görüyorsunuz, sıralı bir sorgu için küçük satır hedefi varken sistem, Sıralama işlecine sahip olmamak için verileri sırayla tüketmeye çalışır. Aynı zamanda bir karma tablo oluşturmaktan kaçınacak, ilk sırayı bulmak için muhtemelen çok fazla iş yapmak zorunda olmadığını belirleyecektir. Senin durumunda, bu yanlış - bu okların kalınlığından, tek bir eşleşme bulmak için çok fazla veri tüketmek zorunda gibi görünüyor.

Bu okların kalınlığı DOCUMENT_QUEUE(DQ) tablonuzun CORRESPONDENCE_JOURNAL(CJ) tablonuzdan çok daha küçük olduğunu gösterir . Ve en iyi plan aslında DQ sıraları üzerinden bir CJ sırasını bulana kadar kontrol etmek olacaktır. Aslında, Query Optimizer'ın (QO) ORDER BYorada bu sinir bozucu olmasaydı yapacağı şey buydu, bu CJ'daki bir kapak endeksi tarafından güzel bir şekilde desteklendi.

Bu yüzden, ORDER BYtamamen düşürdüyseniz, DQ'daki sıraların üzerinde yinelenen ve sıranın var olduğundan emin olmak için CJ'ye giden bir Yuvalanmış Döngü içeren bir plan elde edersiniz. Ve TOP 1bu, tek bir satır çekildikten sonra dururdu.

Ancak, ilk sıraya gerçekten ihtiyacınız varsa FILE_NUMBER, o zaman sistemi çok yardımcı gibi görünen bu dizini görmezden gelmek için kandırabilirdiniz. Bunu yaparak ORDER BY CJ.FILE_NUMBER+1- ki bunun daha önce olduğu gibi aynı derecede, ancak en önemlisi QO'yu koruyacağını biliyoruz. yapmaz. QO bütünüyle anlaşılmaya odaklanacak, böylece bir Top N Sort operatörü tatmin edilebilecekti. Bu yöntem, siparişin değerini hesaplamak için bir Hesaplama Skaler işleci ve ilk satırı almak için bir Top N Sort işleci içeren bir plan üretmelidir. Ancak bunların sağında, CJ’de çok fazla Seeks yapan hoş bir Yuvalanmış Döngü görmelisiniz. Ve DQ'da hiçbir şeye uymayan geniş bir satır tablosundan geçmekten daha iyi performans.

Karma Eşlemenin mutlaka korkunç olması gerekmez, ancak DQ'dan döndürdüğünüz satır kümesi CJ'den çok daha küçükse (olmasını beklediğim gibi), o zaman Karma Eşleşmesi çok daha fazla CJ tarıyor olacak ihtiyaç duyduğundan daha fazla.

Not: Sorgu en iyi duruma getiricisinin +0'ın hiçbir şeyi değiştirmeyeceğini fark etmesi muhtemel olduğundan +0 yerine +1 kullandım. Elbette, aynı şey + 1 için de geçerli olabilir, eğer şimdi değilse, o zaman gelecekte bir noktada.


7

Bu gönderideki sonuçları okudum ve bir Satır Hedefi vb. Kavramını anladım. Merak ettiğim şey, sorguyu daha iyi bir plan kullanması için nasıl değiştirebileceğimi merak ediyorum.

Ekleme OPTION (QUERYTRACEON 4138), son plan hakkında aşırı açıklayıcı olmadan, yalnızca bu sorgu için satır hedeflerinin etkisini kapatır ve muhtemelen en basit / en doğrudan yol olacaktır.

Bu ipucunu eklemek size bir izin hatası veriyorsa (gerekli DBCC TRACEON), bir plan kılavuzu kullanarak uygulayabilirsiniz:

Kullanılması QUERYTRACEONplanı rehberleri tarafından spaghettidba

... ya da sadece saklı bir prosedür kullanın:

Hangi izinler gerekir QUERYTRACEON? Kendra Little tarafından


3

SQL Server'ın daha yeni sürümleri, en iyileştirici satır hedefi optimizasyonları uygulayabildiğinde düşük performans gösteren sorgularla başa çıkmak için farklı (ve tartışmalı olarak daha iyi) seçenekler sunar. SQL Server 2016 SP1, DISABLE_OPTIMIZER_ROWGOAL USE HINT4138 izleme bayrağıyla aynı etkiye sahiptir. Bu sürümde değilseniz OPTIMIZE FOR, yalnızca 1 yerine tüm satırları döndürmek üzere tasarlanmış bir sorgu planı almak için sorgu ipucunu kullanmayı da düşünebilirsiniz. soruyla aynı sonuçları döndürür, ancak yalnızca 1 satır elde etmek amacıyla oluşturulmaz.

DECLARE @top INT = 1;

SELECT TOP (@top) dc.DOCUMENT_ID,
        dc.COPIES,
        dc.REQUESTOR,
        dc.D_ID,
        cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
    ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
  AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER
OPTION (OPTIMIZE FOR (@top = 987654321));

2

Yaptığınızdan beri TOP(1), ORDER BYbaşlangıç için belirleyici olmanızı öneririm . En azından bu sonuçların fonksiyonel olarak tahmin edilebilir olmasını sağlayacaktır (her zaman regresyon testi için faydalıdır). Eklemek DC.D_IDve bunun CJ.CORRESPONDENCE_IDiçin ihtiyacınız var gibi görünüyor .

Muhtemelen kardinalite üzerindeki tahmini ile sorunları ortadan kaldırmak için, önceden geçici tabloya ilgili tüm DC satırları seçin: Sorgu planları bakarken, ben bazen sorguyu basitleştirmek öğretici bulmak QUEUE_DATEve PRINT_LOCATION. Düşük satır sayısı göz önüne alındığında bu hızlı olmalıdır. Kalıcı tabloyu değiştirmeden gerekirse bu geçici tabloya dizinler ekleyebilirsiniz.

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.