SELECT DISTINCT TOP N sorgum neden bütün tabloyu tarıyor?


27

SELECT DISTINCT TOP NSql Server sorgu en iyi duruma getiricisi tarafından kötü optimize edilmiş gibi görünen birkaç sorguları çalıştırın . Önemsiz bir örnek düşünerek başlayalım: iki alternatif değeri olan bir milyon satırlık tablo. Ben kullanacağız GetNums verilerini oluşturmak için fonksiyonu:

DROP TABLE IF EXISTS X_2_DISTINCT_VALUES;

CREATE TABLE X_2_DISTINCT_VALUES (PK INT IDENTITY (1, 1), VAL INT NOT NULL);

INSERT INTO X_2_DISTINCT_VALUES WITH (TABLOCK) (VAL)
SELECT N % 2
FROM dbo.GetNums(1000000);

UPDATE STATISTICS X_2_DISTINCT_VALUES WITH FULLSCAN;

Aşağıdaki sorgu için:

SELECT DISTINCT TOP 2 VAL
FROM X_2_DISTINCT_VALUES
OPTION (MAXDOP 1);

SQL Server, tablonun ilk veri sayfasını tarayarak iki farklı değer bulabilir ancak bunun yerine tüm verileri tarar . SQL Server neden istenen sayıda farklı değer bulana kadar tarama yapmıyor?

Bu soru için lütfen bloklarda oluşturulan 10 farklı değere sahip 10 milyon satır içeren aşağıdaki test verilerini kullanın:

DROP TABLE IF EXISTS X_10_DISTINCT_HEAP;

CREATE TABLE X_10_DISTINCT_HEAP (VAL VARCHAR(10) NOT NULL);

INSERT INTO X_10_DISTINCT_HEAP WITH (TABLOCK)
SELECT REPLICATE(CHAR(65 + (N / 100000 ) % 10 ), 10)
FROM dbo.GetNums(10000000);

UPDATE STATISTICS X_10_DISTINCT_HEAP WITH FULLSCAN;

Kümelenmiş bir dizini olan bir tablonun cevapları da kabul edilebilir:

DROP TABLE IF EXISTS X_10_DISTINCT_CI;

CREATE TABLE X_10_DISTINCT_CI (PK INT IDENTITY (1, 1), VAL VARCHAR(10) NOT NULL, PRIMARY KEY (PK));

INSERT INTO X_10_DISTINCT_CI WITH (TABLOCK) (VAL)
SELECT REPLICATE(CHAR(65 + (N / 100000 ) % 10 ), 10)
FROM dbo.GetNums(10000000);

UPDATE STATISTICS X_10_DISTINCT_CI WITH FULLSCAN;

Aşağıdaki sorgu, tüm 10 milyon satırı tablodan tarar . Tüm masayı taramayacak bir şeyi nasıl alabilirim? SQL Server 2016 SP1 kullanıyorum.

SELECT DISTINCT TOP 10 VAL
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1);

Bir imleç 10 için işe yarayabilir
paparazzo

Yanıtlar:


29

DISTINCTYukarıdaki sorguda işlemi gerçekleştirebilecek üç farklı iyileştirici kural var gibi görünüyor . Aşağıdaki sorgu, listenin ayrıntılı olduğunu gösteren bir hata atar:

SELECT DISTINCT TOP 10 ID
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1, QUERYRULEOFF GbAggToSort, QUERYRULEOFF GbAggToHS, QUERYRULEOFF GbAggToStrm);

Mesaj 8622, Seviye 16, Durum 1, Sıra 1

Sorgu işlemcisi, bu sorguda tanımlanan ipuçları nedeniyle bir sorgu planı oluşturamadı. Herhangi bir ipucu belirtmeden ve SET FORCEPLAN kullanmadan sorguyu tekrar gönderin.

GbAggToSortGrup bazında kümeyi (farklı) farklı bir sıralama olarak uygular. Bu, herhangi bir satır üretmeden önce girdideki tüm verileri okuyan bir engelleme operatörüdür. GbAggToStrmGrup-by toplamasını bir akış toplayıcısı olarak uygular (bu, aynı zamanda bir giriş sıralaması gerektirir). Bu aynı zamanda bir engelleme operatörüdür. GbAggToHSSorundan kötü planda gördüğümüz şey olan karma eşleşmeyi uygular, ancak karma eşleşmesi (toplam) veya karma eşleşmesi (farklı akış) olarak uygulanabilir.

Karma eşleşmesi ( farklı akış ) operatörü bu sorunu çözmenin bir yoludur, çünkü engellemediğinden. SQL Server, yeterli farklı değerler bulduğunda taramayı durdurabilmelidir.

Flow Distinct mantıksal operatörü girişi tarar, çiftleri siler. Distinct operatörü herhangi bir çıktı üretmeden önce tüm girdiyi tüketirken, Flow Distinct operatörü girdiden elde edilen her satırı geri döndürür (bu satır bir kopya olmadığı sürece atılır).

Neden sorudaki sorgu, karma eşleşmesi (akış farklı) yerine karma eşleştirmesini (toplu) kullanıyor? Tablodaki farklı değerlerin sayısı değiştikçe, karma eşleşme (akış farklı) sorgusunun maliyetinin düşmesini beklerim, çünkü tabloya taraması gereken satır sayısının tahmini düşmelidir. Karma eşleşmenin (toplamın) maliyetinin artmasını beklerim çünkü inşa etmesi gereken karma tablo büyür. Bunu araştırmanın bir yolu, bir plan rehberi oluşturmaktır . Verilerin iki kopyasını oluşturursam ancak bunlardan birine bir plan kılavuzu uygularsam, eşleşme eşlemesini (toplam), eşleşme eşlemesine (farklı) aynı verilere karşı yan yana karşılaştırabilirim. Aynı kural her iki plana da uygulandığından, sorgu iyileştirici kurallarını devre dışı bırakarak bunu yapamayacağımı unutmayın GbAggToHS.

Peşinde olduğum plan rehberini almanın bir yolu:

DROP TABLE IF EXISTS X_PLAN_GUIDE_TARGET;

CREATE TABLE X_PLAN_GUIDE_TARGET (VAL VARCHAR(10) NOT NULL);

INSERT INTO X_PLAN_GUIDE_TARGET WITH (TABLOCK)
SELECT CAST(N % 10000 AS VARCHAR(10))
FROM dbo.GetNums(10000000);

UPDATE STATISTICS X_PLAN_GUIDE_TARGET WITH FULLSCAN;

-- run this query
SELECT DISTINCT TOP 10 VAL  FROM X_PLAN_GUIDE_TARGET  OPTION (MAXDOP 1)

Plan tanıtıcısını alın ve bir plan kılavuzu oluşturmak için kullanın:

-- plan handle is 0x060007009014BC025097E88F6C01000001000000000000000000000000000000000000000000000000000000
SELECT qs.plan_handle, st.text FROM 
sys.dm_exec_query_stats AS qs   
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st  
WHERE st.text LIKE '%X[_]PLAN[_]GUIDE[_]TARGET%'
ORDER BY last_execution_time DESC;

EXEC sp_create_plan_guide_from_handle 
'EVIL_PLAN_GUIDE', 
0x060007009014BC025097E88F6C01000001000000000000000000000000000000000000000000000000000000;

Plan rehberleri yalnızca tam olarak sorgu metni üzerinde çalışır, bu yüzden tekrar plan kılavuzundan kopyalayalım:

SELECT query_text
FROM sys.plan_guides
WHERE name = 'EVIL_PLAN_GUIDE';

Verileri sıfırla:

TRUNCATE TABLE X_PLAN_GUIDE_TARGET;

INSERT INTO X_PLAN_GUIDE_TARGET WITH (TABLOCK)
SELECT REPLICATE(CHAR(65 + (N / 100000 ) % 10 ), 10)
FROM dbo.GetNums(10000000);

Plan rehberi uygulanmış olarak, sorgu için bir sorgu planı edinin :

SELECT DISTINCT TOP 10 VAL  FROM X_PLAN_GUIDE_TARGET  OPTION (MAXDOP 1)

Bu, test verilerimizle istediğimiz karma eşleme (farklı akış) operatörüne sahiptir. SQL Server'ın tablodaki tüm satırları okumayı beklediğini ve tahmini maliyetin karma eşlemeli (toplam) planla aynı olduğunu unutmayın. Yaptığım test, iki planın maliyetinin, planın satır hedefi, SQL Server'ın tablodan beklediği farklı değerlerin sayısından büyük veya ona eşit olması durumunda aynı olduğunu gösterdi. istatistikleri. Maalesef (sorgumuz için), optimizer maliyetler aynı olduğunda karma eşleşmesini (toplam) karma eşleşmesi (farklı akış) üzerinden alır. Yani 0,0000001 sihirli optimizer birimi istediğimiz plandan uzaktayız.

Bu soruna saldırmanın bir yolu kürek hedefini azaltmaktır. Görünüm noktasındaki satır hedefi, optimize edici ise, satır sıralarının sayısından daha az ise, büyük olasılıkla karma eşleşmesi elde ederiz (farklı akış). Bu OPTIMIZE FORsorgu ipucu ile gerçekleştirilebilir :

DECLARE @j INT = 10;

SELECT DISTINCT TOP (@j) VAL
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1, OPTIMIZE FOR (@j = 1));

Bu sorgu için optimizer, sorgu yalnızca ilk satıra ihtiyaç duyuyormuş gibi bir plan oluşturur, ancak sorgu çalıştırıldığında ilk 10 satırı geri alır. Makinemde bu sorgu 892800 satırdan tarar X_10_DISTINCT_HEAPve 299 msn'de 250 ms CPU zamanı ve 2537 mantıksal okuma ile tamamlar.

İstatistikler, çarpık verilere karşı örneklenmiş istatistikler için olabilecek yalnızca tek bir değeri rapor ederse bu tekniğin işe yaramayacağını unutmayın. Bununla birlikte, bu durumda, verilerinizin böyle teknikleri kullanarak haklı kılmak için yeterince yoğun bir şekilde doldurulması pek olası değildir. Tablodaki tüm verileri tarayarak, özellikle de paralel olarak yapılabiliyorsa, çok fazla kaybedemezsiniz.

Bu soruna saldırmanın bir başka yolu, SQL Server'ın temel tablodan almasını beklediği tahmin edilen farklı değerlerin sayısını şişirmektir. Bu beklenenden daha zordu. Deterministik bir işlev uygulamak, muhtemelen belirgin sonuç sayısını artıramaz. Sorgu en iyi duruma getiricisi bu matematiksel gerçeğin farkındaysa (bazı testler en azından bizim amaçlarımız için olduğunu düşünür) o zaman deterministik işlevler uygulamak ( tüm dize işlevlerini içeren ), tahmini farklı satır sayısını artırmaz.

Nondeterministic işlevlerinin çoğu bariz seçimler dahil da işe yaramadı NEWID()ve RAND(). Ancak, LAG()bu sorgu için hile yapar. Sorgu en iyi duruma getiricisi LAG, karma eşleşmesi (farklı akış) planını teşvik edecek ifadeye karşı 10 milyon farklı değer bekler :

SELECT DISTINCT TOP 10 LAG(VAL, 0) OVER (ORDER BY (SELECT NULL)) AS ID
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1);

Makinemde bu sorgu 892800 satırdan tarar X_10_DISTINCT_HEAPve 1165 ms'de 1109 ms CPU zamanı ve 2537 mantıksal okuma ile tamamlanır, bu yüzden LAG()oldukça fazla bağıl ek yükü ekler. @ Paul White bu sorgu için toplu işlem modunu denemeyi önerdi. SQL Server 2016'da, toplu mod işleme ile bile çalışabiliriz MAXDOP 1. Bir satır deposu için toplu işlem kipini almanın bir yolu boş bir CCI'ye aşağıdaki gibi katılmaktır:

CREATE TABLE #X_DUMMY_CCI (ID INT NOT NULL);

CREATE CLUSTERED COLUMNSTORE INDEX X_DUMMY_CCI ON #X_DUMMY_CCI;

SELECT DISTINCT TOP 10 VAL
FROM
(
    SELECT LAG(VAL, 1) OVER (ORDER BY (SELECT NULL)) AS VAL
    FROM X_10_DISTINCT_HEAP
    LEFT OUTER JOIN #X_DUMMY_CCI ON 1 = 0
) t
WHERE t.VAL IS NOT NULL
OPTION (MAXDOP 1);

Bu kod, bu sorgu planında sonuçlanır .

Paul kullanmak için sorguyu değiştirmem gerektiğine işaret etti LAG(..., 1)çünkü LAG(..., 0)Pencere Toplamı optimizasyonu için uygun görünmüyordu. Bu değişiklik geçen süreyi 520 ms'ye, CPU süresini 454 ms'ye düşürdü.

LAG()Yaklaşımın en istikrarlı olmadığını unutmayın . Microsoft, işleve karşı benzersizlik varsayımını değiştirirse, artık çalışmayabilir. Eski CE ile farklı bir tahmine sahiptir. Ayrıca bir yığına karşı bu tür bir optimizasyon yapılması iyi bir fikir değildir. Eğer masa yeniden kurulursa, neredeyse tüm satırların tablodan okunması gereken en kötü durum senaryosunda sonuçlanabilir.

Benzersiz bir sütunu olan bir masaya (örneğin, sorudaki kümelenmiş indeks örneği gibi) daha iyi seçeneklere sahibiz. Örneğin SUBSTRING, her zaman boş bir dize döndüren bir ifade kullanarak en iyileştiriciyi kandırabiliriz . SQL Server, SUBSTRINGfarklı değerlerin sayısını değiştireceğini düşünmüyor, bu yüzden eğer PK gibi benzersiz bir sütuna uygularsak, o zaman tahmin edilen farklı satır sayısı 10 milyondur. Bu aşağıdaki sorgu , karma eşleşmesi (farklı akış) işlecini alır:

SELECT DISTINCT TOP 10 VAL + SUBSTRING(CAST(PK AS VARCHAR(10)), 11, 1)
FROM X_10_DISTINCT_CI
OPTION (MAXDOP 1);

Makinemde bu sorgu 90000 satır tarar X_10_DISTINCT_CIve 293 ms CPU zamanı ve 3011 mantıksal okuma ile 333 msn'de tamamlar.

Özetle, sorgu iyileştirici SELECT DISTINCT TOP N, N> = tablodan tahmini farklı satır sayısı olduğunda , tüm satırların sorgular için tablodan okunacağını varsayıyor gibi görünmektedir . Karma eşleştirme (toplama) işleci karma eşleştirme (akış farklı) işleci ile aynı maliyete sahip olabilir, ancak en iyileştirici her zaman toplama işlecini seçer. Bu, tablo taramasının başlangıcına yakın yeterli miktarda değer bulunduğunda gereksiz mantıksal okumalara neden olabilir. Optimize ediciyi karma eşleşme (farklı akış) operatörünü kullanmaya aldatmanın iki yolu OPTIMIZE FORipucunu kullanarak satır hedefini düşürmek LAG()veya SUBSTRINGbenzersiz bir sütunu kullanarak veya bu satırdaki tahmini farklı satır sayısını artırmaktır .


12

Zaten kendi sorularınızı doğru cevapladınız.

Sadece en verimli yolun aslında tüm tabloyu taramak olduğuna dair bir gözlem eklemek istiyorum - bir sütun deposu 'yığın' olarak düzenlenebilirse :

CREATE CLUSTERED COLUMNSTORE INDEX CCSI 
ON dbo.X_10_DISTINCT_HEAP;

Basit sorgu:

SELECT DISTINCT TOP (10)
    XDH.VAL 
FROM dbo.X_10_DISTINCT_HEAP AS XDH
OPTION (MAXDOP 1);

sonra verir:

Yürütme planı

Tablo 'X_10_DISTINCT_HEAP'. Tarama sayısı 1,
 mantıksal 0 okur, fiziksel okur, ileri okur 0 
 lob mantıksal değeri 66 , lob fiziksel değeri 0, lob okuma değeri 0.
Tablo 'X_10_DISTINCT_HEAP'. Bölüm 13 okur, bölüm 0 atlanır.

 SQL Server Yürütme Süreleri:
   CPU zamanı = 0 ms, geçen süre = 11 ms.

Karma Eşleşmesi (Akış Ayrımı) şu anda toplu modda yürütülemiyor. Bunu kullanan yöntemler, toplu işlemden sıra işlemeye (görünmez) pahalı geçiş nedeniyle, daha yavaştır. Örneğin:

SET ROWCOUNT 10;

SELECT DISTINCT 
    XDH.VAL
FROM dbo.X_10_DISTINCT_HEAP AS XDH
OPTION (FAST 1);

SET ROWCOUNT 0;

verir:

Akış Ayrı Yürütme Planı

Tablo 'X_10_DISTINCT_HEAP'. Tarama sayısı 1,
 mantıksal 0 okur, fiziksel okur, ileri okur 0 
 lob mantıksal 20 okur , lob fiziksel değeri 0, lob okuma değeri 0.
Tablo 'X_10_DISTINCT_HEAP'. Segment 4 okur , segment 0 atlanır.

 SQL Server Yürütme Süreleri:
   CPU süresi = 640 ms, geçen süre = 680 ms.

Bu, tablonun bir satır deposu yığını olarak organize edildiğinden daha yavaş.


4

Özyinelemeli bir CTE kullanarak tekrarlanan bir kısmi taramayı (atlama taramasına benzeyen ancak aynı olmayan) taklit etmeye çalışıyorum. Amaç - endekse sahip olmadığımız için (id)- masada çeşit ve çoklu taramalardan kaçınmaktır.

Bazı özyinelemeli CTE kısıtlamalarını atlamak için birkaç ipucu veriyor:

  • TOPÖzyinelemeli bölümünde izin yok . Bir alt sorgu kullanıyoruz ve ROW_NUMBER()bunun yerine.
  • Sabit parça için birden fazla referansımız olamaz LEFT JOINveya NOT IN (SELECT id FROM cte)özyinelemeli kısımdan kullanamaz veya kullanamazız . Atlamak için, birVARCHAR hiyerarşi kimliğine idbenzer STRING_AGGveya hierarchyID değerlerine benzer tüm değerleri toplayan dize oluştururuz ve sonra karşılaştırırız LIKE.

Bir Yığın İçin (sütunun adlandırıldığı varsayılarak id ) rextester.com'daki test-1 .

Bu - testlerin gösterdiği gibi - birden fazla taramadan kaçınmaz, ancak ilk birkaç sayfada farklı değerler bulunduğunda Tamam işlemini gerçekleştirir. Bununla birlikte, değerler eşit şekilde dağılmamışsa, tablonun büyük bölümlerinde birden fazla tarama yapabilir - bu durum pf'nin performansının düşmesine neden olur.

WITH ct (id, found, list) AS
  ( SELECT TOP (1) id, 1, CAST('/' + id + '/' AS VARCHAR(MAX))
    FROM x_large_table_2
  UNION ALL
    SELECT y.ID, ct.found + 1, CAST(ct.list + y.id + '/' AS VARCHAR(MAX))
    FROM ct
      CROSS APPLY 
      ( SELECT x.id, 
               rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
        FROM x_large_table_2 AS x
        WHERE ct.list NOT LIKE '%/' + id + '/%'
      ) AS y
    WHERE ct.found < 3         -- the TOP (n) parameter here
      AND y.rn = 1
  )
SELECT id FROM ct ;

ve tablo kümelendiğinde (CI açık unique_key ), rextester.com'da test-2 .

Bu, WHERE x.unique_key > ct.unique_keyçoklu taramaları önlemek için kümelenmiş indeksi ( ) kullanır :

WITH ct (unique_key, id, found, list) AS
  ( SELECT TOP (1) unique_key, id, 1, CAST(CONCAT('/',id, '/') AS VARCHAR(MAX))
    FROM x_large_table_2
  UNION ALL
    SELECT y.unique_key, y.ID, ct.found + 1, 
        CAST(CONCAT(ct.list, y.id, '/') AS VARCHAR(MAX))
    FROM ct
      CROSS APPLY 
      ( SELECT x.unique_key, x.id, 
               rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
        FROM x_large_table_2 AS x
        WHERE x.unique_key > ct.unique_key
          AND ct.list NOT LIKE '%/' + id + '/%'
      ) AS y
    WHERE ct.found < 5       -- the TOP (n) parameter here
      AND y.rn = 1
  )
-- SELECT * FROM ct ;        -- for debugging
SELECT id FROM ct ;

Bu çözümle ilgili oldukça ince bir performans sorunu var. Nth değerini bulduktan sonra masaya ekstra bir arama yaparak sona erer. Eğer ilk 10 için 10 ayrı değer varsa, orada olmayan bir 11. değeri arayacaktır. Ek bir tam tarama ile bitirdiniz ve 10 milyon ROW_NUMBER () hesaplaması gerçekten toplanıyor. Makinemde 20X sorgusunu hızlandıran bir geçici çözümüm var. Ne düşünüyorsun? brentozar.com/pastetheplan/?id=SkDhAmFKe
Joe Obbish

2

Bütünlük için, bu soruna yaklaşmanın başka bir yolu da OUTER APPLY kullanmaktır . OUTER APPLYBulmamız gereken her değer için bir işleç ekleyebiliriz . Bu, kavram olarak ypercube'un özyinelemeli yaklaşımına benzer, ancak özyinelemeyi el ile yazılan şekilde etkin bir şekilde kullanır. Bunun bir avantajı, geçici çözüm TOPyerine türetilmiş tablolarda kullanabilmemizdir ROW_NUMBER(). Büyük bir dezavantaj, sorgu metninin uzunluğu kadar uzadığıdır.N artmasıyla sürmesidir.

İşte yığına karşı sorgu için bir uygulama:

SELECT VAL
FROM (
    SELECT t1.VAL VAL1, t2.VAL VAL2, t3.VAL VAL3, t4.VAL VAL4, t5.VAL VAL5, t6.VAL VAL6, t7.VAL VAL7, t8.VAL VAL8, t9.VAL VAL9, t10.VAL VAL10
    FROM 
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP 
    ) t1
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t2 WHERE t2.VAL NOT IN (t1.VAL)
    ) t2
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t3 WHERE t3.VAL NOT IN (t1.VAL, t2.VAL)
    ) t3
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t4 WHERE t4.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL)
    ) t4
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t5 WHERE t5.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL)
    ) t5
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t6 WHERE t6.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL)
    ) t6
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t7 WHERE t7.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL)
    ) t7
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t8 WHERE t8.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL)
    ) t8
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t9 WHERE t9.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL)
    ) t9
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t10 WHERE t10.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL, t9.VAL)
    ) t10
) t
UNPIVOT 
(
  VAL FOR VALS IN (VAL1, VAL2, VAL3, VAL4, VAL5, VAL6, VAL7, VAL8, VAL9, VAL10)
) AS upvt;

İşte yukarıdaki sorgu için gerçek sorgu planı. Makinemde bu sorgu 613 ms CPU zamanı ve 12605 mantıksal okuma ile 713 ms'de tamamlanıyor. Her 100k satırında yeni bir değer elde ediyoruz, bu yüzden bu sorgunun 900000 * 10 * 0,5 = 4500000 satır civarında taranmasını beklerdim. Teoride, bu sorgu, diğer cevabın bu sorgunun mantıksal okumalarını beş kez yapmalıdır:

DECLARE @j INT = 10;

SELECT DISTINCT TOP (@j) VAL
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1, OPTIMIZE FOR (@j = 1));

Bu sorgu 2537 mantıksal okuma yaptı. 2537 * 5 = 12685, 12605'e oldukça yakın.

Kümelenmiş indeksi olan tablo için daha iyisini yapabiliriz. Bunun nedeni, aynı satırları iki kez taramaktan kaçınmak için, son kümelenmiş anahtar değerini türetilmiş tabloya geçirebilmemizdir. Bir uygulama:

SELECT VAL
FROM (
    SELECT t1.VAL VAL1, t2.VAL VAL2, t3.VAL VAL3, t4.VAL VAL4, t5.VAL VAL5, t6.VAL VAL6, t7.VAL VAL7, t8.VAL VAL8, t9.VAL VAL9, t10.VAL VAL10
    FROM 
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI 
    ) t1
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t2 WHERE PK > t1.PK AND t2.VAL NOT IN (t1.VAL)
    ) t2
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t3 WHERE PK > t2.PK AND t3.VAL NOT IN (t1.VAL, t2.VAL)
    ) t3
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t4 WHERE PK > t3.PK AND t4.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL)
    ) t4
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t5 WHERE PK > t4.PK AND t5.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL)
    ) t5
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t6 WHERE PK > t5.PK AND t6.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL)
    ) t6
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t7 WHERE PK > t6.PK AND t7.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL)
    ) t7
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t8 WHERE PK > t7.PK AND t8.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL)
    ) t8
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t9 WHERE PK > t8.PK AND t9.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL)
    ) t9
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t10 WHERE PK > t9.PK AND t10.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL, t9.VAL)
    ) t10
) t
UNPIVOT 
(
  VAL FOR VALS IN (VAL1, VAL2, VAL3, VAL4, VAL5, VAL6, VAL7, VAL8, VAL9, VAL10)
) AS upvt;

İşte yukarıdaki sorgu için gerçek sorgu planıdır. Makinemde bu sorgu 154 ms'de 140 ms CPU zamanı ve 3203 mantıksal okuma ile tamamlanıyor. Bu OPTIMIZE FORkümelenmiş dizin tablosu karşı sorgudan biraz daha hızlı çalışıyor gibiydi . Beklemiyordum, bu yüzden performansı daha dikkatli ölçmeye çalıştım. Benim metodoloji sonuç kümesi olmadan her sorgu on kez çalıştırmak ve alınan toplu numaraları bakmak için oldu sys.dm_exec_sessionsve sys.dm_exec_session_wait_stats. Oturum 56, APPLYsorgu ve oturum 63, OPTIMIZE FORsorgu oldu.

Çıktı sys.dm_exec_sessions:

╔════════════╦══════════╦════════════════════╦═══════════════╗
 session_id  cpu_time  total_elapsed_time  logical_reads 
╠════════════╬══════════╬════════════════════╬═══════════════╣
         56      1360                1373          32030 
         63      2094                2091          30400 
╚════════════╩══════════╩════════════════════╩═══════════════╝

APPLYSorgu için cpu_time ve elapsed_time içinde açık bir avantaj var gibi görünüyor .

Çıktı sys.dm_exec_session_wait_stats:

╔════════════╦════════════════════════════════╦═════════════════════╦══════════════╦══════════════════╦═════════════════════╗
 session_id            wait_type             waiting_tasks_count  wait_time_ms  max_wait_time_ms  signal_wait_time_ms 
╠════════════╬════════════════════════════════╬═════════════════════╬══════════════╬══════════════════╬═════════════════════╣
         56  SOS_SCHEDULER_YIELD                             340             0                 0                    0 
         56  MEMORY_ALLOCATION_EXT                            38             0                 0                    0 
         63  SOS_SCHEDULER_YIELD                             518             0                 0                    0 
         63  MEMORY_ALLOCATION_EXT                            98             0                 0                    0 
         63  RESERVED_MEMORY_ALLOCATION_EXT                  400             0                 0                    0 
╚════════════╩════════════════════════════════╩═════════════════════╩══════════════╩══════════════════╩═════════════════════╝

OPTIMIZE FORSorgu, ek bir bekleme türüne sahip RESERVED_MEMORY_ALLOCATION_EXT . Bunun ne anlama geldiğini tam olarak bilmiyorum. Bu sadece karma eşleşme (farklı akış) operatöründeki ek yükün bir ölçümü olabilir. Her durumda, belki de CPU zamanındaki 70 ms'lik farktan endişe etmeye değmez.


1

Sanırım neden
bu konuyu ele almanın bir yolu olabilir,
bunun dağınık göründüğünü biliyorum ama uygulama planı ilk 2'nin maliyetin% 84'ü olduğunu söyledi

SELECT distinct top (2)  [enumID]
FROM [ENRONbbb].[dbo].[docSVenum1]

declare @table table (enumID tinyint);
declare @enumID tinyint;
set @enumID = (select top (1) [enumID] from [docSVenum1]);
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
select enumID from @table;

Bu kod makinemde 5 saniye sürdü. Tablo değişkenine bağlanan birleşimler biraz ek yük ekledi gibi gözüküyor. Son sorguda tablo değişkeni 892800 kez tarandı. Bu sorgu 1359 ms CPU zamanı ve 1374 ms geçen zaman aldı. Kesinlikle beklediğimden daha fazla. Tablo değişkenine birincil anahtar eklemek, nedenini bilmeme rağmen, yardımcı görünüyor. Başka olası optimizasyonlar olabilir.
Joe Obbish

-4

Gördüğünüzü anlamak için geri durmanız ve sorunuza nesnel olarak bakmanız gerektiğini düşünüyorum.

Sorgu en iyi duruma getiricinin, ilk önce belirli değerlerin tam listesini tanımlamaksızın en iyi 10 farklı değeri seçmesi nasıl mümkün olabilir?

Farklı'yı seçmek, sonuç kümesini tanımlamak için tam tablo (veya dizin indeksi) taraması gerektirir. Bir düşünün - tablodaki son satır daha önce görmediği bir değer içerebilir.

Seçme Seçimi çok kör bir silahtır.


2
Pek sayılmaz. Bir tablo tararsam ve ilk 20 satırın 10 farklı değeri varsa, neden tablonun geri kalanını taramaya devam etmem gerekiyor?
ypercubeᵀᴹ

2
Sadece 10 istediğimde neden aramaya devam etsin? Zaten 10 farklı değer bulmuş, durmalı. Sorunun konusu bu.
ypercubeᵀᴹ

3
Neden bir üst N aramasının önce sonuç setinin tamamını görmesi gerekiyor? Eğer 10 farklı değere sahipse ve onunla ilgilendiğiniz tek şey, diğer değerleri aramayı durdurabilir. Sonuç kümesinin, hangisinin diğer 10 olduğunu "hangisinin" olduğunu bilmek için sıralamak zorunda kaldıysanız, ancak hangisinin umrunda olmadan 10 farklı değer istiyorsanız, tüm sonuç kümesini elde etmek için mantıklı bir gereklilik yoktur.
Tom V - Team Monica

2
Sadece istenen seti geri gönderme görevini kendiniz hayal edin. Sizden on milyonlarca en iyi on değer vermeniz istendi ve herhangi bir sıralama düzenini izlemeniz istenmedi. İlk 100’e baktıktan sonra sonuca varırsanız, tüm değerler kümesinden geçmek zorunda olduğunuzu hisseder misiniz? Bu sadece anlamsız olurdu. Şimdi bir veritabanı ürününde bu mantığı uygulamak başka bir konudur, ancak bu problem için masanın tamamını taramak için mantıklı olmamanız gerektiğini iddia ediyor gibi görünüyorsunuz .
Andriy M,

4
@Marco: Ben katılmıyorum, bu ise bir cevap. Sadece cevap veren soru sorunun öncülüne katılmıyor ve OP'nin yanlış algısı olarak algıladığı şeye cevap veriyor.
Andriy M,
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.