Sorgu 100x yavaş SQL Server 2014, satır sayısı biriktirme satır suçlu tahmin?


13

SQL Server 2012'de 800 milisaniyede çalışan ve SQL Server 2014'te yaklaşık 170 saniye süren bir sorgu var . Bunu Row Count Spooloperatör için kötü bir kardinalite tahminine daralttığımı düşünüyorum . Makara operatörleri hakkında biraz okudum (örneğin, burada ve burada ), ancak hala birkaç şeyi anlamada sorun yaşıyorum:

  • Bu sorgu neden bir Row Count Spooloperatöre ihtiyaç duyuyor ? Doğruluk için gerekli olduğunu düşünmüyorum, bu yüzden hangi spesifik optimizasyonu sağlamaya çalışıyor?
  • SQL Server neden Row Count Spoolişleç birleştirmenin tüm satırları kaldırdığını tahmin ediyor ?
  • Bu SQL Server 2014'te bir hata mı? Öyleyse, Connect'te dosyalayacağım. Ama önce daha derin bir anlayış istiyorum.

Not: LEFT JOINSQL Server 2012 ve SQL Server 2014'te kabul edilebilir performans elde etmek için sorguyu bir olarak yeniden yazabilir veya tablolara dizinler ekleyebilirim. Yani bu soru bu özel sorguyu anlamak ve daha derinlemesine planlamakla ilgili ve daha az sorguyu farklı şekilde ifade etme.


Yavaş sorgu

Tam bir test komut dosyası için bu Pastebin'e bakın . İşte baktığım test sorgusu:

-- Prune any existing customers from the set of potential new customers
-- This query is much slower than expected in SQL Server 2014 
SELECT *
FROM #potentialNewCustomers -- 10K rows
WHERE cust_nbr NOT IN (
    SELECT cust_nbr
    FROM #existingCustomers -- 1MM rows
)


SQL Server 2014: Tahmini sorgu planı

SQL Server inanmaktadır Left Anti Semi Joiniçin Row Count Spool1 satıra 10.000 satır aşağı süzer. Bu nedenle, bir LOOP JOINsonraki birleşiminde a'yı seçer #existingCustomers.

resim açıklamasını buraya girin


SQL Server 2014: Asıl sorgu planı

Beklendiği gibi (SQL Server hariç herkes tarafından!), Row Count SpoolHerhangi bir satır kaldırılmadı. SQL Server'ın yalnızca bir kez döngü yapması bekleniyorsa 10.000 kez döngü yapıyoruz.

resim açıklamasını buraya girin


SQL Server 2012: Tahmini sorgu planı

SQL Server 2012 kullanırken (veya OPTION (QUERYTRACEON 9481)SQL Server 2014'te), Row Count Spooltahmini satır sayısını azaltmaz ve bir karma birleşimi seçilir, bu da çok daha iyi bir planla sonuçlanır.

resim açıklamasını buraya girin

SOL BİRLEŞME yeniden yazma

Başvuru için, tüm SQL Server 2012, 2014 ve 2016'da iyi performans elde etmek için sorguyu yeniden yazabileceğim bir yol var. Ancak, yine de yukarıdaki sorgunun belirli davranışı ve bunun yeni SQL Server 2014 Kardinalite Tahmincisi'nde bir hatadır.

-- Re-writing with LEFT JOIN yields much better performance in 2012/2014/2016
SELECT n.*
FROM #potentialNewCustomers n
LEFT JOIN (SELECT 1 AS test, cust_nbr FROM #existingCustomers) c
    ON c.cust_nbr = n.cust_nbr
WHERE c.test IS NULL

resim açıklamasını buraya girin

Yanıtlar:


10

Bu sorgu neden Satır Sayısı Biriktiricisi operatörüne ihtiyaç duyuyor? ... hangi spesifik optimizasyonu sağlamaya çalışıyor?

İçindeki cust_nbrsütun #existingCustomersgeçersizdir. Aslında boş değer içeriyorsa, burada doğru yanıt sıfır satır döndürmektir ( NOT IN (NULL,...) her zaman boş bir sonuç kümesi verir.).

Yani sorgu şöyle düşünülebilir

SELECT p.*
FROM   #potentialNewCustomers p
WHERE  NOT EXISTS (SELECT *
                   FROM   #existingCustomers e1
                   WHERE  p.cust_nbr = e1.cust_nbr)
       AND NOT EXISTS (SELECT *
                       FROM   #existingCustomers e2
                       WHERE  e2.cust_nbr IS NULL) 

Rowcount biriktirme ile

EXISTS (SELECT *
        FROM   #existingCustomers e2
        WHERE  e2.cust_nbr IS NULL) 

Birden fazla.

Bu sadece varsayımlardaki küçük bir farkın performansta oldukça yıkıcı bir fark yaratabileceği bir durum gibi görünüyor.

Aşağıdaki gibi tek bir satırı güncelledikten sonra ...

UPDATE #existingCustomers
SET    cust_nbr = NULL
WHERE  cust_nbr = 1;

... sorgu bir saniyeden daha kısa sürede tamamlandı. Planın gerçek ve tahmini versiyonlarındaki satır sayımları artık neredeyse yerinde.

SET STATISTICS TIME ON;
SET STATISTICS IO ON;

SELECT *
FROM   #potentialNewCustomers
WHERE  cust_nbr NOT IN (SELECT cust_nbr
                        FROM   #existingCustomers 
                       ) 

resim açıklamasını buraya girin

Sıfır sıralar yukarıda açıklandığı gibi çıkar.

SQL Server'daki İstatistik Histogramları ve otomatik güncelleme eşikleri, bu tür tek sıralı değişiklikleri algılayacak kadar ayrıntılı değildir. Büyük olasılıkla, sütun geçersiz kılınabiliyorsa NULL, istatistik histogramı şu anda herhangi biri olduğunu belirtmese bile , en az bir tane içermesi temelinde çalışmak makul olabilir .


9

Bu sorgu neden Satır Sayısı Biriktiricisi operatörüne ihtiyaç duyuyor? Doğruluk için gerekli olduğunu düşünmüyorum, bu yüzden hangi spesifik optimizasyonu sağlamaya çalışıyor?

Bu soru için Martin'in kapsamlı cevabına bakınız . Kilit noktası içinde tek satır halinde olmasıdır NOT INolduğunu NULL, boolean mantığı "Doğru yanıt sıfır satır döndürmektir" öyle ki dışarı çalışır. Row Count SpoolOperatör bu (gerekirse) mantığı optimize ediyor.

SQL Server neden Satır Sayımı Biriktiricisi işlecine katılmanın tüm satırları kaldırdığını tahmin ediyor?

Microsoft , SQL 2014 Kardinalite Tahmincisi üzerinde mükemmel bir tanıtım belgesi sunmaktadır . Bu belgede aşağıdaki bilgileri buldum:

Yeni CE, değer histogram aralığının dışında olsa bile sorgulanan değerlerin veri kümesinde bulunduğunu varsayar. Bu örnekteki yeni CE, tablo kardinalitesinin yoğunluk ile çarpılmasıyla hesaplanan ortalama bir frekans kullanır.

Genellikle böyle bir değişiklik çok iyi bir değişikliktir; artan kilit problemi büyük ölçüde hafifletir ve genellikle istatistik histogramına dayanan aralık dışı değerler için daha muhafazakar bir sorgu planı (daha yüksek satır tahmini) verir.

Bununla birlikte, bu özel durumda, bir NULLdeğerin bulunacağını varsaymak, irade'ye katılmanın Row Count Spooltüm satırları filtreleyeceği varsayımına yol açar #potentialNewCustomers. Aslında bir NULLsatır olduğu durumda , bu doğru bir tahmindir (Martin cevabında görüldüğü gibi). Ancak, bir NULLsatır olmaması durumunda etki yıkıcı olabilir çünkü SQL Server, kaç giriş satırı göründüğüne bakılmaksızın 1 satırlık birleştirme sonrası tahmini üretir. Bu, sorgu planının geri kalanında çok zayıf birleştirme seçeneklerine yol açabilir.

Bu SQL 2014'te bir hata mı? Öyleyse, Connect'te dosyalayacağım. Ama önce daha derin bir anlayış istiyorum.

Ben bir hata ve performansı etkileyen bir varsayım veya SQL Server'ın yeni Kardinalite Tahmincisi sınırlaması arasındaki gri alanda olduğunu düşünüyorum. Bununla birlikte, bu tuhaflık, NOT INherhangi bir NULLdeğere sahip olmayan bir boş değer cümlesi durumunda, SQL 2012'ye göre performansta önemli gerilemelere neden olabilir .

Bu nedenle, SQL ekibinin Kardinalite Tahmincisi üzerindeki bu olası etkilerinin farkında olması için bir Connect sorunu açtım .

Güncelleme: Şu anda SQL16 için CTP3'deyiz ve sorunun orada oluşmadığını doğruladım.


5

Martin Smith'in cevabı ve kendi cevabınız tüm ana noktaları doğru bir şekilde ele aldı, sadece gelecekteki okuyucular için bir alanı vurgulamak istiyorum:

Bu soru daha çok bu özel sorguyu anlama ve derinlemesine planlama ve sorguyu nasıl farklı şekilde ifade etme konusunda daha azdır.

Sorgunun belirtilen amacı:

-- Prune any existing customers from the set of potential new customers

Bu gereksinimin SQL'de çeşitli şekillerde ifade edilmesi kolaydır. Hangisinin seçildiği başka bir şey kadar bir stil meselesidir, ancak her durumda doğru sonuçları döndürmek için sorgu belirtimi hala yazılmalıdır. Buna boş değerlerin muhasebesi de dahildir.

Mantıksal gereksinimi tam olarak ifade etmek:

  • Zaten müşteri olmayan potansiyel müşterileri iade edin
  • Her bir potansiyel müşteriyi en fazla bir kez listeleyin
  • Boş potansiyeli ve mevcut müşterileri hariç tutun (boş bir müşteri ne anlama geliyorsa)

Daha sonra, tercih ettiğimiz sözdizimini kullanarak bu gereksinimlerle eşleşen bir sorgu yazabiliriz. Örneğin:

WITH DistinctPotentialNonNullCustomers AS
(
    SELECT DISTINCT 
        PNC.cust_nbr 
    FROM #potentialNewCustomers AS PNC
    WHERE 
        PNC.cust_nbr IS NOT NULL
)
SELECT
    DPNNC.cust_nbr
FROM DistinctPotentialNonNullCustomers AS DPNNC
WHERE
    DPNNC.cust_nbr NOT IN
    (
        SELECT 
            EC.cust_nbr 
        FROM #existingCustomers AS EC 
        WHERE 
            EC.cust_nbr IS NOT NULL
    );

Bu, doğru sonuçları döndüren verimli bir yürütme planı oluşturur:

Yürütme planı

Biz ifade edebilen NOT INolarak <> ALLveya NOT = ANYplan veya sonuçlarını etkilemeden:

WITH DistinctPotentialNonNullCustomers AS
(
    SELECT DISTINCT 
        PNC.cust_nbr 
    FROM #potentialNewCustomers AS PNC
    WHERE 
        PNC.cust_nbr IS NOT NULL
)
SELECT
    DPNNC.cust_nbr
FROM DistinctPotentialNonNullCustomers AS DPNNC
WHERE
    DPNNC.cust_nbr <> ALL
    (
        SELECT 
            EC.cust_nbr 
        FROM #existingCustomers AS EC 
        WHERE 
            EC.cust_nbr IS NOT NULL
    );
WITH DistinctPotentialNonNullCustomers AS
(
    SELECT DISTINCT 
        PNC.cust_nbr 
    FROM #potentialNewCustomers AS PNC
    WHERE 
        PNC.cust_nbr IS NOT NULL
)
SELECT
    DPNNC.cust_nbr
FROM DistinctPotentialNonNullCustomers AS DPNNC
WHERE
    NOT DPNNC.cust_nbr = ANY
    (
        SELECT 
            EC.cust_nbr 
        FROM #existingCustomers AS EC 
        WHERE 
            EC.cust_nbr IS NOT NULL
    );

Veya şunu kullanarak NOT EXISTS:

WITH DistinctPotentialNonNullCustomers AS
(
    SELECT DISTINCT 
        PNC.cust_nbr 
    FROM #potentialNewCustomers AS PNC
    WHERE 
        PNC.cust_nbr IS NOT NULL
)
SELECT
    DPNNC.cust_nbr
FROM DistinctPotentialNonNullCustomers AS DPNNC
WHERE 
    NOT EXISTS
    (
        SELECT * 
        FROM #existingCustomers AS EC
        WHERE
            EC.cust_nbr = DPNNC.cust_nbr
            AND EC.cust_nbr IS NOT NULL
    );

Orada hiçbir şey sihirli bu konuda, ya da kullanma konusunda hiçbir şey özellikle sakıncalı IN, ANYya da ALL- biz sadece her zaman doğru sonuçlar üretecek, böylece doğru sorguyu yazmak gerekir.

En kompakt form EXCEPTşunları kullanır :

SELECT 
    PNC.cust_nbr 
FROM #potentialNewCustomers AS PNC
WHERE 
    PNC.cust_nbr IS NOT NULL
EXCEPT
SELECT
    EC.cust_nbr 
FROM #existingCustomers AS EC
WHERE 
    EC.cust_nbr IS NOT NULL;

Bu, aynı zamanda doğru sonuçlar üretir, ancak yürütme planı bitmap filtrelemenin olmaması nedeniyle daha az verimli olabilir:

Bitmap olmayan yürütme planı

Orijinal soru ilginçtir, çünkü gerekli null kontrol uygulamasıyla performansı etkileyen bir sorun ortaya çıkarır. Bu cevabın amacı, sorguyu doğru bir şekilde yazmanın da sorunu ortadan kaldırmasıdır.

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.