Neden bir alt sorgu satır tahminini 1 olarak azaltır?


26

Aşağıdaki basit fakat basit sorguyu düşünün:

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2) 
  END AS ID2
FROM X_HEAP;

Bu sorgu için son satır tahmininin X_HEAPtablodaki satır sayısına eşit olmasını beklerdim . Alt sorguda yaptığım şey, satır tahmini için önemli olmamalıdır, çünkü herhangi bir satırı filtreleyemez. Ancak, SQL Server 2016'da, alt sorgu nedeniyle satır tahmininin 1'e düşürüldüğünü görüyorum:

hatalı sorgu

Bu neden oluyor? Bu konuda ne yapabilirim?

Bu konuyu doğru sözdizimi ile yeniden oluşturmak çok kolaydır. İşte bunu yapacak bir tablo tanımları kümesi:

CREATE TABLE dbo.X_HEAP (ID INT NOT NULL)
CREATE TABLE dbo.X_OTHER_TABLE (ID INT NOT NULL);
CREATE TABLE dbo.X_OTHER_TABLE_2 (ID INT NOT NULL);

INSERT INTO dbo.X_HEAP WITH (TABLOCK)
SELECT TOP (1000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values;

CREATE STATISTICS X_HEAP__ID ON X_HEAP (ID) WITH FULLSCAN;

db keman bağlantısı .

Yanıtlar:


22

Bu kardinalite tahmini (CE) aşağıdaki durumlarda ortaya çıkar:

  1. Birleştirme, doğrudan bir yüklem belirten bir dış birleşimdir
  2. Seçicilik geçiş yüklem olduğu tahmin edilmektedir tam olarak 1 .

Not: Seçiciliği belirlemek için kullanılan belirli hesap makinesi önemli değildir.


ayrıntılar

CE dış seçiciliği olarak katılmak hesaplar toplamı arasında:

  • İç birleştirme aynı yüklemi seçiciliği
  • Anti katılmak aynı yüklemi seçiciliği

Bir dış ve iç birleşim arasındaki tek fark, bir dış birleşimin aynı zamanda birleşim belirleyicisi ile eşleşmeyen satırları döndürmesidir. Anti birleştirme tam olarak bu farkı sağlar. İç ve anti birleşimi için kardinalite tahmini doğrudan dış birleşmeden daha kolaydır.

Birleştirme seçiciliği tahmin süreci çok basittir:

  • İlk olarak, doğrudan geçiş yüklemesinin seçiciliği değerlendirilir. SPT
    • Bu, koşullara uygun olan hesap makinesini kullanarak yapılır.
    • Belirti, olumsuzlayıcı bir IsFalseOrNullbileşen de dahil olmak üzere her şeydir .
  • İç birleşim seçiciliği: = 1 - SPT
  • Anti-katılım seçiciliği: = SPT

Anti birleştirme, birleşimden 'geçecek' satırları temsil eder. İç birleşim, 'geçmeyecek' sıraları temsil eder. 'Geçmenin', iç tarafı hiç çalıştırmadan bağlantıdan akan satırlar anlamına geldiğine dikkat edin. Vurgulamak için: tüm satırlar birleştirme tarafından döndürülecek, ayrım ortaya çıkmadan önce birleşmenin iç tarafını geçen ve aralarında olmayan sıralar arasında olacaktır.

Açıkçası, ekleme için her zaman beklendiği gibi tüm satırları katılmak tarafından döndürülen yani 1 olmak üzere toplam seçiciliği vermelidir.1 - SPTSPT

Aslında, yukarıdaki hesaplama tam olarak 1 dışındaki tüm değerler için açıklandığı gibi çalışır .SPT

Zaman = 1, her iki iç birleştirme ve anti birleştirme seçicilikler bir sıranın (bir bütün olarak birleştirme için) önem düzeyi tahmini sonuçlanan sıfır olduğu tahmin edilmektedir. Söyleyebileceğim kadarıyla, bu istemsiz ve bir hata olarak rapor edilmelidir.SPT


İlgili bir sorun

Bu hatanın, ayrı bir CE sınırlaması nedeniyle, düşünülenden daha fazla tezahür etmesi olasıdır. Bu CASE, bir EXISTScümle (ortak olduğu gibi) bir cümle kullandığında ortaya çıkar . Örneğin, sorudan aşağıdaki değiştirilmiş sorgu , beklenmeyen önemlilik tahminiyle karşı karşıya değil :

-- This is fine
SELECT 
    CASE
        WHEN XH.ID = 1
        THEN (SELECT TOP (1) XOT.ID FROM dbo.X_OTHER_TABLE AS XOT) 
    END
FROM dbo.X_HEAP AS XH;

Önemsiz bir şekilde tanıtmak EXISTS, sorunun yüzleşmesine neden olur:

-- This is not fine
SELECT 
    CASE
        WHEN EXISTS (SELECT 1 WHERE XH.ID = 1)
        THEN (SELECT TOP (1) XOT.ID FROM dbo.X_OTHER_TABLE AS XOT) 
    END
FROM dbo.X_HEAP AS XH;

Kullanılması EXISTS, yürütme planına yarı birleştirme (vurgulanır) getirir:

Yarı birleşme planı

Yarı birleşme için tahmin iyi. Sorun, CE'nin ilgili prob sütununu 1 olan sabit bir seçiciliği olan basit bir projeksiyon olarak görmesidir:

Semijoin with probe column treated as a Project.

Selectivity of probe column = 1

Bu, EXISTSMaddenin içeriğinden bağımsız olarak, bu CE sorununun ortaya çıkarması için gerekli koşullardan birini otomatik olarak karşılamaktadır .


Önemli geçmiş bilgileri için bkz . CASEİfadelerde Craig Freedman'ın Alt Sorguları.


22

Bu kesinlikle istenmeyen davranış gibi görünüyor. Kardinalite tahminlerinin bir planın her aşamasında tutarlı olması gerekmediği doğrudur, ancak bu nispeten basit bir sorgu planıdır ve nihai kardinalite tahmininin sorgunun yaptığı ile tutarsız olduğu doğrudur. Bu kadar düşük bir kardinalite tahmini, birleşme tipleri için kötü tercihler ve daha karmaşık bir plandaki akış aşağı akışındaki diğer tablolar için erişim yöntemleri ile sonuçlanabilir.

Deneme ve yanılma yoluyla, sorunun görünmediği benzer birkaç soruyla karşılaşabiliriz:

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    ELSE (SELECT -1) 
  END AS ID2
FROM dbo.X_HEAP;

SELECT 
  ID
, CASE
    WHEN ID < 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    WHEN ID >= 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
  END AS ID2
FROM dbo.X_HEAP;

Ayrıca, sorunun göründüğü daha fazla sorgu bulabiliriz:

SELECT 
  ID
, CASE
    WHEN ID < 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    WHEN ID >= 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
    ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE) 
  END AS ID2
FROM dbo.X_HEAP;

SELECT 
  ID
, CASE
    WHEN ID = 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    ELSE (SELECT -1) 
  END AS ID2
FROM dbo.X_HEAP;

SELECT 
  ID
, CASE
    WHEN ID = 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
  END AS ID2
FROM dbo.X_HEAP;

Bir kalıp var gibi görünüyor: içinde CASEolması beklenmeyen bir ifade varsa ve sonuç ifadesi bir tabloya karşı bir alt sorgu ise, o zaman satır tahmini bu ifadeden sonra 1'e düşer.

Sorguyu kümelenmiş bir dizine sahip bir tabloya yazarsam, kurallar biraz değişir. Aynı verileri kullanabiliriz:

CREATE TABLE dbo.X_CI (ID INT NOT NULL, PRIMARY KEY (ID))

INSERT INTO dbo.X_CI WITH (TABLOCK)
SELECT * FROM dbo.X_HEAP;

UPDATE STATISTICS X_CI WITH FULLSCAN;

Bu sorgu 1000 satırlık son tahmine sahiptir:

SELECT 
  ID
, CASE
    WHEN ID = 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
    ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
  END
FROM dbo.X_CI;

Ancak bu sorguda 1 satırlık son bir tahmin var

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
  END
FROM dbo.X_CI;

Buna daha fazla eklenmek için, sorgu iyileştiricinin seçicilik hesaplamalarını nasıl gerçekleştirdiği hakkında bilgi almak için belgesiz izleme bayrağını 2363 kullanabiliriz. Bu izleme bayrağını belgesiz izleme bayrağı 8606 ile eşleştirmeyi faydalı buldum . TF 2363, hem sadeleştirilmiş ağaç hem de proje normalleştirmeden sonra ağaç için seçicilik hesaplamaları yapıyor gibi görünmektedir. Her iki izleme bayrağı etkin olması, hangi hesapların hangi ağaç için geçerli olduğunu açıkça belirtir.

Soruya gönderilen orijinal sorgu için deneyelim:

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2) 
  END AS ID2
FROM X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);

İşte bazı yorumların yanı sıra ilgili olduğunu düşündüğüm çıktının bir kısmı:

Plan for computation:

  CSelCalcColumnInInterval -- this is the type of calculator used

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID -- this is the column used for the calculation

Pass-through selectivity: 0 -- all rows are expected to have a true value for the case expression

Stats collection generated: 

  CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter) -- the row estimate after the join will still be 1000

      CStCollBaseTable(ID=1, CARD=1000 TBL: X_HEAP)

      CStCollBaseTable(ID=2, CARD=1 TBL: X_OTHER_TABLE)

...

Plan for computation:

  CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID

Pass-through selectivity: 1 -- no rows are expected to have a true value for the case expression

Stats collection generated: 

  CStCollOuterJoin(ID=9, CARD=1 x_jtLeftOuter) -- the row estimate after the join will still be 1

      CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter) -- here is the row estimate after the previous join

          CStCollBaseTable(ID=1, CARD=1000 TBL: X_HEAP)

          CStCollBaseTable(ID=2, CARD=1 TBL: X_OTHER_TABLE)

      CStCollBaseTable(ID=3, CARD=1 TBL: X_OTHER_TABLE_2)

Şimdi, sorunu olmayan benzer bir sorgu için deneyelim. Bunu kullanacağım:

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    ELSE (SELECT -1) 
  END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);

En sonda hata ayıklama çıktısı:

Plan for computation:

  CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID

Pass-through selectivity: 1

Stats collection generated: 

  CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)

      CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)

          CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)

          CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)

      CStCollConstTable(ID=4, CARD=1) -- this is different than before because we select a constant instead of from a table

Kötü satır tahmininin bulunduğu başka bir sorgu deneyelim:

SELECT 
  ID
, CASE
    WHEN ID < 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    WHEN ID >= 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
    ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE) 
  END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);

Sonunda, kardinalite tahmini, Pass-through seçiciliği = 1'den sonra tekrar 1 satıra düşer. Kardinalite tahmini, 0.501 ve 0.499 seçiciliğinden sonra korunur.

Plan for computation:

 CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID

Pass-through selectivity: 0.501

...

Plan for computation:

  CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID

Pass-through selectivity: 0.499

...

Plan for computation:

  CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID

Pass-through selectivity: 1

Stats collection generated: 

  CStCollOuterJoin(ID=12, CARD=1 x_jtLeftOuter) -- this is associated with the ELSE expression

      CStCollOuterJoin(ID=11, CARD=1000 x_jtLeftOuter)

          CStCollOuterJoin(ID=10, CARD=1000 x_jtLeftOuter)

              CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)

              CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)

          CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)

      CStCollBaseTable(ID=4, CARD=1 TBL: X_OTHER_TABLE)

Sorunu olmayan başka bir benzer sorguyu tekrar değiştirelim. Bunu kullanacağım:

SELECT 
  ID
, CASE
    WHEN ID < 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    WHEN ID >= 500 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
  END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);

Hata ayıklama çıktısında, hiçbir zaman geçme seçiciliği 1 olan bir adım yoktur. Kardinalite tahmini, 1000 satırda kalır.

Plan for computation:

  CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID

Pass-through selectivity: 0.499

Stats collection generated: 

  CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)

      CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)

          CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)

          CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)

      CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)

End selectivity computation

Kümelenmiş bir dizini olan bir tablo içerdiğinde sorguya ne dersiniz? Sıra tahmini sorunu ile aşağıdaki sorguyu göz önünde bulundurun:

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
  END
FROM dbo.X_CI
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);

Hata ayıklama çıktısının sonu gördüklerimize benzer:

Plan for computation:

  CSelCalcColumnInInterval

      Column: QCOL: [SE_DB].[dbo].[X_CI].ID

Pass-through selectivity: 1

Stats collection generated: 

  CStCollOuterJoin(ID=9, CARD=1 x_jtLeftOuter)

      CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)

          CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)

          CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)

      CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)

Ancak, sorun olmadan CI'ye karşı sorgu farklı çıktıya sahiptir. Bu sorguyu kullanarak:

SELECT 
  ID
, CASE
    WHEN ID = 0 
    THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2) 
    ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE) 
  END
FROM dbo.X_CI
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);

Farklı hesap makinelerinde kullanılan sonuçlar. CSelCalcColumnInIntervalartık görünmüyor:

Plan for computation:

  CSelCalcFixedFilter (0.559)

Pass-through selectivity: 0.559

Stats collection generated: 

  CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)

      CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)

      CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE_2)

...

Plan for computation:

  CSelCalcUniqueKeyFilter

Pass-through selectivity: 0.001

Stats collection generated: 

  CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)

      CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)

          CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)

          CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE_2)

      CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE)

Sonuç olarak, aşağıdaki koşullarda alt sorgudan sonra kötü bir satır tahmini elde ettiğimiz görülüyor:

  1. CSelCalcColumnInIntervalSeçicilik hesap kullanılır. Ne zaman kullanıldığını tam olarak bilmiyorum ama temel tablo bir yığın olduğunda çok daha sık ortaya çıkıyor gibi görünüyor.

  2. Geçiş seçiciliği = 1. Başka bir deyişle, CASEifadelerden birinin tüm satırlar için yanlış olarak değerlendirilmesi bekleniyor. İlk CASEifadenin tüm satırlar için true olarak değerlendirilmesi önemli değildir .

  3. Dış bir birleşimi var CStCollBaseTable. Başka bir deyişle, CASEsonuç ifadesi bir tabloya karşı bir alt sorgudur. Sabit bir değer işe yaramaz.

Belki de bu koşullar altında sorgu iyileştirici, iç içe geçen döngünün iç kısmında yapılan iş yerine, geçiş seçiciliğini dış tablonun sıra tahminine istemeden uygular. Bu, satır tahminini 1'e düşürür.

İki geçici çözüm bulabildim. APPLYBir alt sorgu yerine kullanırken sorunu yeniden oluşturamadım . İz bayrağı 2363'ün çıktısı ile çok farklıydı APPLY. Soruda orijinal sorguyu yeniden yazmanın bir yolu:

SELECT 
  h.ID
, a.ID2
FROM X_HEAP h
OUTER APPLY
(
SELECT CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2) 
  END
) a(ID2);

iyi sorgu 1

Eski CE sorunu da önlüyor gibi görünüyor.

SELECT 
  ID
, CASE
    WHEN ID <> 0 
    THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE) 
    ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2) 
  END AS ID2
FROM X_HEAP
OPTION (USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'));

iyi sorgu 2

Bu sorun için bir bağlantı öğesi gönderildi (Paul White'ın cevabında verdiği bazı ayrıntılarla).

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.