Bu birleşme kardinalite tahmini neden bu kadar büyük?


18

Aşağıdaki sorgu için inanılmaz derecede yüksek bir kardinalite tahmini olduğunu düşünüyorum yaşıyorum:

SELECT dm.PRIMARY_ID
FROM
(
    SELECT COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID) PRIMARY_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID
    LEFT OUTER JOIN X_DETAIL_2 d2 ON dt.ID = d2.ID
    LEFT OUTER JOIN X_DETAIL_3 d3 ON dt.ID = d3.ID
) dm
INNER JOIN X_LAST_TABLE lst ON dm.PRIMARY_ID = lst.JOIN_ID;

Tahmini plan burada . Gerçek bir plan ekleyemediğim için tabloların bir istatistik kopyası üzerinde çalışıyorum. Ancak, bu sorun için çok alakalı olduğunu düşünmüyorum.

SQL Server 481577 satır "dm" türetilmiş tablodan döndürüleceğini tahmin ediyor. Daha sonra, X_LAST_TABLE birleştirmesi gerçekleştirildikten sonra 4528030000 satırın döndürüleceğini tahmin eder, ancak JOIN_ID, X_LAST_TIME'ın birincil anahtarıdır. 0 ve 481577 satır arasında birleştirme kardinalite tahmini beklenir. Bunun yerine, satır tahmini, dış ve iç tabloları çapraz birleştirirken alacağım satır sayısının% 10'u gibi görünüyor. Bunun için matematik yuvarlama ile çalışır: 481577 * 94025 * 0.1 = 45280277425 ki bu 4528030000'e yuvarlanır.

Öncelikle bu davranış için temel bir neden arıyorum. Basit geçici çözümlerle de ilgileniyorum, ancak lütfen veri modelini değiştirmeyi veya geçici tabloları kullanmanızı önermeyin. Bu sorgu, bir görünüm içindeki mantığın basitleştirilmesidir. Birkaç sütun üzerinde COALESCE yapmanın ve bunlara katılmanın iyi bir uygulama olmadığını biliyorum. Bu sorunun amacı, veri modelinin yeniden tasarlanmasını önermem gerekip gerekmediğini bulmaktır.

Eski kardinalite tahmincisi etkinken Microsoft SQL Server 2014 üzerinde test yapıyorum. TF 4199 ve diğerleri açık. Alakalı olursa izleme bayraklarının tam bir listesini verebilirim.

İşte en alakalı tablo tanımı:

CREATE TABLE X_LAST_TABLE (
JOIN_ID NUMERIC(18, 0) NOT NULL
    CONSTRAINT PK_X_LAST_TABLE PRIMARY KEY CLUSTERED (JOIN_ID ASC)
);

Ayrıca tüm tablo oluşturma komut dosyalarını istatistikleriyle birlikte betikledimSorunu sunucularından birinde yeniden isterse, .

Birkaç gözlemimi eklemek için TF 2312 kullanmak tahminleri düzeltiyor, ancak bu benim için bir seçenek değil. TF 2301 tahmini düzeltmez. Tablolardan birinin kaldırılması tahmini düzeltir. Tuhaf bir şekilde, X_DETAIL_LINK birleşme sırasını değiştirmek de tahmini giderir. Birleştirme sırasını değiştirerek, sorguyu yeniden yazmak ve birleştirme siparişini bir ipucu ile zorlamak istemiyorum. Birleştirmelerin sırasını değiştirirken tahmini bir sorgu planı .


PS bigintBunun yerine herhangi bir şekilde geçiş yapabiliyorsanız decimal(18, 0)avantaj elde edersiniz: 1) her değer için 9 yerine 8 bayt kullanın ve 2) sonuçları olabilecek paket veri türü yerine baytla karşılaştırılabilir bir veri türü kullanın değerleri karşılaştırırken CPU zamanı için.
ErikE

@ErikE Bahşiş için teşekkürler ama bunu zaten biliyordum. Maalesef eski nedenlerden ötürü BIGINT üzerinde NUMERIC (18,0) ile sıkıştık.
Joe Obbish

Denemeye değerdi!
ErikE

Eğer null değilse X_DETAIL2ve X_DETAIL3tablolara hiç ihtiyacınız var mı? JOIN_IDX_DETAIL1
ErikE

@ErikE Bu bir MCVE'dir, bu nedenle sorgu tam olarak bu noktada anlam ifade etmez.
Joe Obbish

Yanıtlar:


14

Bunu biliyorum COALESCEBirkaç sütun üzerinde ve bunlara katılmanın iyi bir uygulama olmadığını .

Şema 3NF + (anahtarlar ve kısıtlamalar ile) olduğunda ve sorgu ilişkisel ve öncelikle SPJG (seçim-projeksiyon-birleştirme-gruba göre) olduğunda iyi kardinalite ve dağıtım tahminleri üretmek yeterince zordur. CE modeli bu ilkeler üzerine inşa edilmiştir. Bir sorguda ne kadar olağandışı veya ilişkisel olmayan özellikler varsa, kardinalite ve seçicilik çerçevesinin ele alabileceği sınırlara yaklaşır. Çok ileri git ve CE pes edecek ve tahmin edecek .

MCVE örneğinin çoğu, basit iç equijoin (veya semijoin) yerine ağırlıklı olarak dış equijoins (iç birleşim artı anti-semijoin olarak modellenmiş) olsa da basit SPJ'dir (G yok). Tüm ilişkilerin anahtarları vardır, ancak yabancı anahtarlar veya başka kısıtlamalar yoktur. Birleşimler dışında hepsi bire çok, bu iyi.

İstisnadır birçok çoğa dış arasına katılmak X_DETAIL_1ve X_DETAIL_LINK. MCVE'deki bu birleştirmenin tek işlevi, satırları potansiyel olarak çoğaltmaktır X_DETAIL_1. Bu alışılmadık bir şey bir şey.

Basit eşitlik tahminleri (seçimler) ve skaler operatörler de daha iyidir. Örneğin, karşılaştır-eşit öznitelik / sabit özniteliği modelde normalde iyi çalışır. Bu tür tahminlerin uygulanmasını yansıtacak şekilde histogramları ve frekans istatistiklerini değiştirmek nispeten "kolaydır".

COALESCEüzerine kurulu CASE, sırayla olarak dahili olarak uygulandığı IIF(ve bu çok önce doğruydu IIFTransact-SQL dilinde çıktı). CE modelleri IIFbir şekildeUNION , her biri giriş ilişkisi üzerine bir seçim üzerine bir projeden oluşan, birbirini dışlayan iki çocuklu . Listelenen bileşenlerin her birinin model desteği vardır, bu nedenle bunları birleştirmek nispeten kolaydır. Yine de, ne kadar çok katman soyutlaması olursa, sonuç o kadar az doğrudur - daha büyük uygulama planlarının daha az kararlı ve güvenilir olma eğiliminde olmasının bir nedeni.

ISNULLÖte yandan, bir içsel motora. Daha temel bileşenler kullanılarak oluşturulmaz. ISNULLÖrneğin, bir histograma efekt uygulamak, NULLdeğerler için adımın değiştirilmesi (ve gerektiğinde sıkıştırma) kadar basittir . Skaler operatörler gittikçe nispeten opaktır ve mümkün olan en iyi şekilde önlenir. Bununla birlikte, genellikle daha iyi optimizasyon dostu (daha az optimize edici dostu olmayan)CASE temelli bir alternatiften .

CE (70 ve 120+), SQL Server standartlarına göre bile çok karmaşıktır. Her operatöre basit bir mantık (gizli formülle) uygulamak söz konusu değildir. CE anahtarları ve fonksiyonel bağımlılıkları bilir; frekansları, çok değişkenli istatistikleri ve histogramları kullanarak nasıl tahmin edileceğini bilir; ve tonlarca özel durum, iyileştirme, kontrol ve bakiye ve destekleyici yapılar var. Sık sık, örneğin, çoklu yollarla (frekans, histogram) birleşmeleri tahmin eder ve ikisi arasındaki farklara dayanarak bir sonuç veya düzenlemeye karar verir.

Ele alınması gereken son bir temel şey: Başlangıç ​​kardinalite tahmini, sorgu ağacındaki her işlem için aşağıdan yukarıya doğru çalışır. Önce yaprak operatörleri için seçicilik ve kardinalite elde edilir (temel ilişkiler). Ana operatörler için değiştirilmiş histogramlar ve yoğunluk / frekans bilgileri türetilir. Ağaca ne kadar yaklaşırsak, hataların birikme eğilimi olduğu için tahminlerin kalitesi o kadar düşük olur.

Bu tek kapsamlı kapsamlı tahmin, bir başlangıç ​​noktası sağlar ve nihai bir yürütme planına herhangi bir dikkat verilmeden önce gerçekleşir (önemsiz plan derleme aşamasından bile önce bir şekilde gerçekleşir). Bu noktada sorgu ağacı, sorgunun yazılı biçimini oldukça yakından yansıtma eğilimindedir (alt sorgular kaldırılmış ve basitleştirmeler uygulanmış olsa da).

İlk tahminden hemen sonra, SQL Server sezgisel birleştirme yeniden sıralaması gerçekleştirir; Ayrıca iç birleşimleri dış birleşmelerden ve çapraz ürünlerden önce konumlandırmaya çalışır. Yetenekleri kapsamlı değildir; çabaları kapsamlı değildir; ve fiziksel maliyetleri dikkate almaz (henüz mevcut olmadıkları için - sadece istatistiksel bilgiler ve meta veri bilgileri mevcuttur). Sezgisel yeniden sıralama, basit iç equijoin ağaçlarında en başarılı olanıdır. Maliyete dayalı optimizasyon için "daha iyi" bir başlangıç ​​noktası sağlamak için vardır.

Bu birleşme kardinalite tahmini neden bu kadar büyük?

MCVE bir "olağandışı" çoğunlukla yedekli sahiptir birçok çoğa katılmak ve bir eş ile katılmak COALESCEyüklemi. Operatör ağacı da bir iç birleşim sahip son sezgisel yeniden sıralama daha tercih edilen bir konuma ağacı hareket edemedi birleştiği. Tüm skaler ve projeksiyonları bir kenara bırakarak, birleştirme ağacı:

LogOp_Join [ Card=4.52803e+009 ]
    LogOp_LeftOuterJoin [ Card=481577 ]
        LogOp_LeftOuterJoin [ Card=481577 ]
            LogOp_LeftOuterJoin [ Card=481577 ]
                LogOp_LeftOuterJoin [ Card=481577 ]
                LogOp_Get TBL: X_DRIVING_TABLE(alias TBL: dt) [ Card=481577 ]
                LogOp_Get TBL: X_DETAIL_1(alias TBL: d1) [ Card=70 ]
                LogOp_Get TBL: X_DETAIL_LINK(alias TBL: lnk) [ Card=47 ]
            LogOp_Get TBL: X_DETAIL_2(alias TBL: d2) X_DETAIL_2 [ Card=119 ]
        LogOp_Get TBL: X_DETAIL_3(alias TBL: d3) X_DETAIL_3 [ Card=281 ]
    LogOp_Get TBL: X_LAST_TABLE(alias TBL: lst) X_LAST_TABLE [ Card=94025 ]

Hatalı nihai tahminin zaten mevcut olduğuna dikkat edin. Card=4.52803e+009Dahili olarak çift hassasiyetli kayan nokta değeri 4.5280277425e + 9 (ondalık olarak 4528027742.5) olarak yazdırılır ve dahili olarak saklanır.

Özgün sorgudaki türetilmiş tablo kaldırıldı ve projeksiyonlar normalleştirildi. İlk kardinalite ve seçicilik tahmininin yapıldığı ağacın SQL gösterimi:

SELECT 
    PRIMARY_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
FROM X_DRIVING_TABLE dt
LEFT OUTER JOIN X_DETAIL_1 d1
    ON dt.ID = d1.ID
LEFT OUTER JOIN X_DETAIL_LINK lnk 
    ON d1.LINK_ID = lnk.LINK_ID
LEFT OUTER JOIN X_DETAIL_2 d2 
    ON dt.ID = d2.ID
LEFT OUTER JOIN X_DETAIL_3 d3 
    ON dt.ID = d3.ID
INNER JOIN X_LAST_TABLE lst 
    ON lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)

(Bir taraf olarak, tekrarlananlar COALESCEda nihai planda bulunur - bir kez nihai Hesaplama Skalerinde ve bir kez iç birleşimin iç tarafında).

Son birleştirmeye dikkat edin. Bu iç birleşim (tanım gereği) uygulanan X_LAST_TABLEbir seçim (birleşim yüklemesi) ile kartezyen ürünü ve önceki birleşim çıktısıdır lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID). Kartezyen ürünün asıl özelliği sadece 481577 * 94025 = 45280277425'dir.

Bunun için yüklemin seçiciliğini belirlemeli ve uygulamalıyız. Opak genişletilmiş kombinasyonu COALESCE(açısından ağaç UNIONve IIFönemli bilgileri üzerindeki etki ile birlikte, unutmayın), elde edilen histogramlar ve daha önce "sıradışı" çok artıklı çok-sayıda dış sıklıkları birleştirilmiş araçları birleştirme CE yapamaz normal yollardan herhangi birinde kabul edilebilir bir tahmin elde edebilir.

Sonuç olarak, Tahmin Mantığına girer. Tahmin mantığı orta derecede karmaşıktır, "eğitimli" tahmin katmanları ve "çok eğitilmemiş" tahmin algoritmaları denenmiştir. Bir tahmin için daha iyi bir temel bulunamazsa, model bir eşitlik karşılaştırması için son çare tahminini kullanır: sqllang!x_Selectivity_Equal= sabit 0.1 seçicilik (% 10 tahmin):

Çağrı yığını

-- the moment of doom
movsd xmm0,mmword ptr [sqllang!x_Selectivity_Equal

Sonuç, kartezyen üründe 0.1 seçiciliktir: 481577 * 94025 * 0.1 = 4528027742.5 (~ 4.52803e + 009), daha önce belirtildiği gibi.

Yeniden yazımlar

Sorunlu birleşme yorumlandığında , daha iyi bir tahmin üretilir çünkü sabit son seçicilik "son çare tahmini" önlenir (temel bilgiler 1-M birleşimleri tarafından saklanır). Tahminin kalitesi hala düşük güvenlidir, çünkü bir COALESCEbirleştirme yüklemesi CE dostu değildir. Gözden geçirilmiş tahmin, en azından insanlar için daha makul görünüyor .

Sorgu X_DETAIL_LINK son yerleştirilecek dış birleşim ile yazıldığında , sezgisel yeniden sıralama, son iç birleşim ile değiştirebilir X_LAST_TABLE. İç birleşimi problemin hemen dış birleşimine yerleştirmek, erken yeniden sıralamanın sınırlı yeteneklerini nihai tahminin iyileştirilmesi için fırsat verir, çünkü çoğunlukla gereksiz "olağandışı" çoktan çoğa dış birleşmenin etkileri zor seçicilik tahmininden sonra gelir için COALESCE. Yine, tahminler sabit tahminlerden biraz daha iyidir ve muhtemelen bir mahkemede belirlenen çapraz incelemeye dayanmaz.

İç ve dış birleşimlerin bir karışımını yeniden düzenlemek zor ve zaman alıcıdır (aşama 2 tam optimizasyon bile sadece sınırlı bir teorik hareket alt kümesini dener).

ISNULLMax Vernon'un cevabında önerilen yuvalanmış , kefaletle sabit tahminden kaçınmayı başarır, ancak nihai tahmin, olası bir sıfır satırdır (ahlaksızlık için bir satıra yükseltilmiş). Bu, hesaplamanın sahip olduğu tüm istatistiksel temeller için 1 satırlık sabit bir tahmin olabilir.

0 ve 481577 satır arasında birleştirme kardinalite tahmini beklenir.

Fiziksel olarak farklı, ancak mantıksal ve anlamsal olarak özdeş alt ağaçlarda farklı zamanlarda (maliyete dayalı optimizasyon sırasında) kardinalite tahmininin gerçekleşebileceğini kabul etse bile bu makul bir beklentidir - son plan bir çeşit dikişli en iyisidir. en iyi (not grubu başına). Plan çapında tutarlılık garantisinin olmaması, bireysel bir katılımın saygınlığı savurabileceği anlamına gelmez, anladım.

Öte yandan, eğer son çare tahmininde kalırsak , umut zaten kaybolur, öyleyse neden rahatsız. Bildiğimiz tüm püf noktaları denedik ve vazgeçtik. Başka bir şey yoksa, vahşi son tahmin, bu sorgunun derlenmesi ve optimizasyonu sırasında her şeyin CE içinde iyi gitmediğine dair büyük bir uyarı işaretidir.

MCVE'yi denediğimde, 120+ CE ISNULL, orijinal sorgu için sıfır (= 1) satır nihai tahmini (iç içe geçmiş gibi) üretti .

Gerçek çözüm, muhtemelen COALESCEveya olmadan basit ek birleşimlere ISNULLve ideal olarak yabancı anahtarlar ve sorgu derlemesi için faydalı diğer kısıtlamalara izin veren bir tasarım değişikliğini içerir .


10

İnanıyorum Compute Scalarkaynaklanan operatör COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)varlığa katıldı X_LAST_TABLE.JOIN_IDsorunun kök nedenidir. Tarihsel olarak, hesaplama skalerlerinin doğru bir şekilde maliyeti zor olmuştur 1 , 2 .

Doğru istatistiklerle minimal düzeyde doğrulanabilir bir örnek (teşekkürler!) Sağladığınızdan, birleştirmenin artık genişletilmiş CASEişlevsellik gerektirmeyeceği, COALESCEçok daha doğru satır tahminleri ve görünüşe göre daha fazla olması için sorguyu yeniden yazabiliyorum. doğru genel maliyetlendirme Sondaki eke bakınız. :

SELECT COALESCE(dm.d1ID, dm.d2ID, dm.d3ID)
FROM
(
    SELECT d1ID = d1.JOIN_ID
        , d2ID = d2.JOIN_ID
        , d3ID = d3.JOIN_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID
    LEFT OUTER JOIN X_DETAIL_2 d2 ON dt.ID = d2.ID
    LEFT OUTER JOIN X_DETAIL_3 d3 ON dt.ID = d3.ID
) dm
INNER JOIN X_LAST_TABLE lst 
    ON (dm.d1ID IS NOT NULL AND dm.d1ID = lst.JOIN_ID)
    OR (dm.d1ID IS NULL AND dm.d2ID IS NOT NULL AND dm.d2ID = lst.JOIN_ID)
    OR (dm.d1ID IS NULL AND dm.d2ID IS NULL AND dm.d3ID IS NOT NULL AND dm.d3ID = lst.JOIN_ID);

İken xID IS NOT NULLteknik olarak gerekli değildir, çünkü ID = JOIN_IDboş değerlere katılmayacak daha net bir şekilde niyet canlandırıyor beri, onları dahil.

Plan 1 ve Plan 2

Plan 1:

resim açıklamasını buraya girin

Plan 2:

resim açıklamasını buraya girin

Yeni sorgu (?) Paralelleştirmeden yararlanır. Ayrıca, yeni sorgunun satırların tahmini çıktı sayısı 1'dir, bu da günün sonunda, orijinal sorgu için 4528030000 tahmininden daha kötü olabilir. Yeni sorgudaki seçme operatörünün alt ağaç maliyeti 243210'da gelirken, orijinal 536.535'te açıktır, bu da açıkça daha azdır. Bunu söyledikten sonra, ilk tahminin gerçeğe yakın bir yerde olduğuna inanmıyorum.


Zeyilname 1.

@Lamak ile yapılan bir tartışma ile The Heap ™ üzerine çeşitli kişilerle daha fazla istişareden sonra , yukarıdaki gözlemsel sorgulama paralellikle bile korkunç bir performans sergiliyor gibi görünüyor. İyi bir performans hem de olanak sağlayan bir çözüm ve iyi bir önem düzeyi tahminleri değiştirilmesi oluşur COALESCE(x,y,z), bir ile ISNULL(ISNULL(x, y), z)olduğu gibi,:

SELECT dm.PRIMARY_ID
FROM
(
    SELECT ISNULL(ISNULL(d1.JOIN_ID, d2.JOIN_ID), d3.JOIN_ID) PRIMARY_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID
    LEFT OUTER JOIN X_DETAIL_2 d2 ON dt.ID = d2.ID
    LEFT OUTER JOIN X_DETAIL_3 d3 ON dt.ID = d3.ID
) dm
INNER JOIN X_LAST_TABLE lst ON dm.PRIMARY_ID = lst.JOIN_ID;

COALESCE, CASEsorgu optimize edici tarafından "kapakların altında" ifadesine dönüştürülür . Bu nedenle, kardinalite tahmin edicisinin içine gömülü sütunlar için güvenilir istatistikler bulmakta daha zorlanması vardır COALESCE. ISNULLiçkin bir işlev olmak, kardinalite tahmincisi için çok daha "açık" tır. Ayrıca ISNULL, hedefin sıfırlanamayacağı biliniyorsa, optimize edilebilecek hiçbir şeye değmez .

ISNULLVaryantın planı şuna benziyor:

resim açıklamasını buraya girin

(Plan sürümünü buraya yapıştırın ).

FYI, yukarıdaki grafik planları ürettiğim harika Plan Gezgini için Sentry One'a sahne verdi .


-1

Birleştirme koşulunuza göre, tablo pek çok şekilde düzenlenebilir, bu da "belirli bir şekilde değişir" sonucu sabitler.

Yalnızca bir tabloya katılmanın size doğru sonucu verdiğini varsayalım.

SELECT COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID) PRIMARY_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID

Burada, X_DETAIL_1ya X_DETAIL_2da kullanabilirsiniz X_DETAIL_3.

Bu nedenle dinlenme 2 tablolarının amacı net değildir.

Masayı X_DETAIL_12 parçaya ayırmışsınız gibi.

Büyük olasılıkla " orada. O tabloları doldurma hata olduğunu İdeal" X_DETAIL_1, X_DETAIL_2ve X_DETAIL_3satırların eşit miktarda içermelidir.

Ancak bir veya daha fazla tablo istenmeyen sayıda satır içeriyor.

Yanılıyorsam özür dilerim.

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.