SQL Server'da çoktan çoğa katılma ipucu nasıl?


9

Bir çift sütun (her ikisi de int) katılmak 3 "büyük" tablo var .

  • Tablo1'de ~ 200 milyon satır var
  • Tablo2'de ~ 1.5 milyon satır var
  • Tablo3'te ~ 6 milyon satır var

Her tablo kümelenmiş üzerinde dizin vardır Key1, Key2o zaman, ve bir daha sütunu. Key1düşük kardinaliteye sahiptir ve çok çarpıktır. Bu WHEREmaddeye her zaman başvurulur . maddesinde Key2asla bahsedilmez WHERE. Her birleştirme çoktan çoğadır.

Sorun kardinalite tahminidir. Her birleştirmenin çıktı tahmini daha büyük yerine küçülür . Bu, asıl sonuç milyonlara ulaştığında yüzlerce kişinin son tahminlerine yol açar.

CE'yi daha iyi tahminler yapmamın bir yolu var mı?

SELECT 1
FROM Table1 t1
     JOIN Table2 t2
       ON t1.Key1 = t2.Key1
          AND t1.Key2 = t2.Key2
     JOIN Table3 t3
       ON t1.Key1 = t3.Key1
          AND t1.Key2 = t3.Key2
WHERE t1.Key1 = 1;

Denediğim çözümler:

  • Tarihinde çok sütunlu istatistikler oluşturma Key1,Key2
  • Üzerinde tonlarca filtrelenmiş istatistik oluşturma Key1(Bu biraz yardımcı olur, ancak veritabanında binlerce kullanıcı tarafından oluşturulan istatistik ile sonuçlanır.)

Maskeli uygulama planı (kötü maskeleme için özür dilerim)

Baktığım durumda, sonuçta 9 milyon satır var. Yeni CE, 180 sıra tahmin ediyor; eski CE, 6100 satırı tahmin eder.

İşte tekrarlanabilir bir örnek:

DROP TABLE IF EXISTS #Table1, #Table2, #Table3;
CREATE TABLE #Table1 (Key1 INT NOT NULL, Key2 INT NOT NULL, T1Key3 INT NOT NULL, CONSTRAINT pk_t1 PRIMARY KEY CLUSTERED (Key1, Key2, T1Key3));
CREATE TABLE #Table2 (Key1 INT NOT NULL, Key2 INT NOT NULL, T2Key3 INT NOT NULL, CONSTRAINT pk_t2 PRIMARY KEY CLUSTERED (Key1, Key2, T2Key3));
CREATE TABLE #Table3 (Key1 INT NOT NULL, Key2 INT NOT NULL, T3Key3 INT NOT NULL, CONSTRAINT pk_t3 PRIMARY KEY CLUSTERED (Key1, Key2, T3Key3));

-- Table1 
WITH Numbers
     AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
         FROM master..spt_values t1
              CROSS JOIN master..spt_values t2),
     DataSize (Key1, NumberOfRows)
     AS (SELECT 1, 2000 UNION
         SELECT 2, 10000 UNION
         SELECT 3, 25000 UNION
         SELECT 4, 50000 UNION
         SELECT 5, 200000)
INSERT INTO #Table1
SELECT Key1
     , Key2 = ROW_NUMBER() OVER (PARTITION BY Key1, T1Key3 ORDER BY Number)
     , T1Key3
FROM DataSize
     CROSS APPLY (SELECT TOP(NumberOfRows) 
                         Number
                       , T1Key3 = Number%(Key1*Key1) + 1 
                  FROM Numbers
                  ORDER BY Number) size;

-- Table2 (same Key1, Key2 values; smaller number of distinct third Key)
WITH Numbers
     AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
         FROM master..spt_values t1
              CROSS JOIN master..spt_values t2)
INSERT INTO #Table2
SELECT DISTINCT 
       Key1
     , Key2
     , T2Key3
FROM #Table1
     CROSS APPLY (SELECT TOP (Key1*10) 
                         T2Key3 = Number
                  FROM Numbers
                  ORDER BY Number) size;

-- Table2 (same Key1, Key2 values; smallest number of distinct third Key)
WITH Numbers
     AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
         FROM master..spt_values t1
              CROSS JOIN master..spt_values t2)
INSERT INTO #Table3
SELECT DISTINCT 
       Key1
     , Key2
     , T3Key3
FROM #Table1
     CROSS APPLY (SELECT TOP (Key1) 
                         T3Key3 = Number
                  FROM Numbers
                  ORDER BY Number) size;


DROP TABLE IF EXISTS #a;
SELECT col = 1 
INTO #a
FROM #Table1 t1
     JOIN #Table2 t2
       ON t1.Key1 = t2.Key1
          AND t1.Key2 = t2.Key2
WHERE t1.Key1 = 1;

DROP TABLE IF EXISTS #b;
SELECT col = 1 
INTO #b
FROM #Table1 t1
     JOIN #Table2 t2
       ON t1.Key1 = t2.Key1
          AND t1.Key2 = t2.Key2
     JOIN #Table3 t3
       ON t1.Key1 = t3.Key1
          AND t1.Key2 = t3.Key2
WHERE t1.Key1 = 1;

Yanıtlar:


5

Açık olmak gerekirse, optimize edici bunun çoktan çoğa bir birleşim olduğunu zaten biliyor. Birleştirme birleştirmelerini zorlar ve tahmini bir plana bakarsanız, birleştirme işleci için birleştirme çoktan çoğa olabileceğini söyleyen bir özellik görebilirsiniz. Burada çözmeniz gereken problem, büyük olasılıkla kardinalite tahminlerini çarpıştırmaktır, böylece sorgunun bıraktığınız kısmı için daha verimli bir sorgu planı elde edersiniz.

Denediğim ilk şey, birleştirmenin sonuçlarını geçici tabloya Object3ve Object5geçici tabloya koymak. Gönderdiğiniz plan için 51393 satırındaki tek bir sütun olduğundan, tempdb'de neredeyse hiç yer kaplamamalıdır. Temp tablosunda tam istatistikler toplayabilirsiniz ve bu, yeterli bir kesin nihai kardinalite tahmini elde etmek için tek başına yeterli olabilir. Üzerinde tam istatistiklerin toplanması Object1da yardımcı olabilir. Kardinalite tahminleri genellikle bir plandan sağdan sola doğru ilerledikçe kötüleşir.

Bu işe yaramazsa ENABLE_QUERY_OPTIMIZER_HOTFIXES, veritabanı veya sunucu düzeyinde etkinleştirilmiş değilse , sorgu ipucunu deneyebilirsiniz . Microsoft, SQL Server 2016 için plana etki eden performans düzeltmelerini bu ayarın arkasında kilitler. Bazıları kardinalite tahminleri ile ilgilidir, bu yüzden belki şanslı olacaksınız ve düzeltmelerden biri sorgunuzda yardımcı olacaktır. Eski kardinalite tahmincisini bir FORCE_LEGACY_CARDINALITY_ESTIMATIONsorgu ipucuyla kullanmayı da deneyebilirsiniz . Bazı veri setleri eski CE ile daha iyi tahminler alabilir.

Son çare olarak Adam Machanic'in MANY()işlevini kullanarak kardinalite tahminini istediğiniz faktöre göre manuel olarak artırabilirsiniz . Başka bir cevapta konuşuyorum ama bağlantının öldüğü anlaşılıyor. Eğer ilgileniyorsanız bir şeyler kazmayı deneyebilirim.


Adam'ın make_parallelişlevi sorunu hafifletmeye yardımcı olur. Bir bakayım many. Oldukça iğrenç bir grup yardımı gibi görünüyor.
Steven Hibble

2

SQL Server istatistikleri, yalnızca istatistik nesnesinin önde gelen sütunu için bir histogram içerir. Bu nedenle, Key2yalnızca bir satır için değerleri içeren bir histogram sağlayan filtrelenmiş istatistikler oluşturabilirsiniz Key1 = 1. Bu filtrelenmiş istatistikleri her tabloda oluşturmak, tahminleri düzeltir ve test sorgusu için beklediğiniz davranışa yol açar: her yeni birleştirme son kardinalite tahminini etkilemez (hem SQL 2016 SP1 hem de SQL 2017'de onaylanır).

-- Note: Add "WITH FULLSCAN" to each if you want a perfect 20,000 row estimate
CREATE STATISTICS st_#Table1 ON #Table1 (Key2) WHERE Key1 = 1
CREATE STATISTICS st_#Table2 ON #Table2 (Key2) WHERE Key1 = 1
CREATE STATISTICS st_#Table3 ON #Table3 (Key2) WHERE Key1 = 1

Bu filtrelenmiş istatistikler olmadan SQL Server, birleştirmenizin önemini tahmin etmek için daha sezgisel tabanlı bir yaklaşım benimseyecektir. Aşağıdaki tanıtım belgesi, SQL Server'ın kullandığı bazı buluşsal yöntemlerin iyi düzeyde üst düzey açıklamalarını içerir: Sorgu Planlarınızı SQL Server 2014 Kardinalite Tahmincisi ile Optimize Etme .

Örneğin, USE HINT('ASSUME_JOIN_PREDICATE_DEPENDS_ON_FILTERS')ipucunu sorgunuza eklemek, Key1yüklem ile Key2birleştirme yüklemi arasında bir miktar korelasyon (bağımsızlık yerine) varsaymak üzere birleştirme sınırlaması sezgisel olarak değiştirir; bu, sorgunuz için faydalı olabilir. Son test sorgusu için, bu ipucu kardinalite tahminini ' 1,175e arttırır 7,551, ancak yine 20,000de filtrelenmiş istatistiklerle üretilen doğru satır tahmininden biraz utangaçtır .

Benzer durumlarda kullandığımız başka bir yaklaşım da verinin ilgili alt kümesini #temp tablolarına çıkarmaktır. Özellikle SQL Server'ın daha yeni sürümleri artık #temp tablolarını hevesle diske yazmadığına göre , bu yaklaşımla iyi sonuçlar aldık. Çoktan çoğa birleştirme açıklamanız, durumunuzdaki her bir #temp tablosunun nispeten küçük (veya son sonuç kümesinden en azından daha küçük) olacağı anlamına gelir, bu nedenle bu yaklaşım denemeye değer olabilir.

DROP TABLE IF EXISTS #Table1_extract, #Table2_extract, #Table3_extract, #c
-- Extract only the subset of rows that match the filter predicate
-- (Or better yet, extract only the subset of columns you need!)
SELECT * INTO #Table1_extract FROM #Table1 WHERE Key1 = 1
SELECT * INTO #Table2_extract FROM #Table2 WHERE Key1 = 1
SELECT * INTO #Table3_extract FROM #Table3 WHERE Key1 = 1
-- Now perform the join on those extracts, removing the filter predicate
SELECT col = 1
INTO #c 
FROM #Table1_extract t1
JOIN #Table2_extract t2
    ON t1.Key2 = t2.Key2
JOIN #Table3_extract t3
    ON t1.Key2 = t3.Key2

Filtrelenmiş istatistikleri yoğun olarak kullanıyoruz, ancak Key1her tablodaki değer başına bir tane yapıyoruz . Şimdi binlerce var.
Steven Hibble

2
@StevenHibble Filtrelenmiş binlerce istatistiğin yönetimi zorlaştırabileceği iyi bir nokta. (Planın derleme süresini de olumsuz etkilediğini gördük.) Kullanım durumunuza uymayabilir, ancak birkaç kez başarıyla kullandığımız başka bir #temp table yaklaşımı da ekledim.
Geoff Patterson

-1

Bir erişim. Denemek dışında gerçek bir temel yok.

SELECT 1
FROM Table1 t1
     JOIN Table2 t2
       ON t1.Key2 = t2.Key2
      AND t1.Key1 = 1
      AND t2.Key1 = 1
     JOIN Table3 t3
       ON t2.Key2 = t3.Key2
      AND t3.Key1 = 1;
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.