Mam 3 „duże” tabele, które łączą się w parę kolumn (obie int
).
- Tabela 1 ma ~ 200 milionów wierszy
- Tabela 2 ma około 1,5 miliona wierszy
- Tabela 3 ma około 6 milionów wierszy
Każda tabela ma indeks klastrowany Key1
, Key2
, a następnie jeszcze jedną kolumnę. Key1
ma niską liczność i jest bardzo wypaczony. Jest to zawsze przywoływane w WHERE
klauzuli. Key2
nigdy nie jest wspomniany w WHERE
klauzuli. Każde dołączenie jest wiele do wielu.
Problem polega na oszacowaniu liczności. Szacunkowe dane wyjściowe dla każdego złączenia stają się mniejsze niż większe . Powoduje to ostateczne oszacowanie niskich setek, gdy faktyczny wynik należy do milionów.
Czy jest jakiś sposób, aby wskazać CE, aby dokonać lepszych szacunków?
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;
Rozwiązania, które wypróbowałem:
- Tworzenie statystyk wielokolumnowych
Key1
,Key2
- Tworzenie ton filtrowanych statystyk na
Key1
(pomaga to trochę, ale w końcu mam tysiące statystyk stworzonych przez użytkowników w bazie danych).
Zamaskowany plan wykonania (przepraszam za złe maskowanie)
W przypadku, na który patrzę, wynik ma 9 milionów wierszy. Nowy CE szacuje 180 rzędów; starsze CE szacuje 6100 wierszy.
Oto powtarzalny przykład:
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;
źródło
make_parallel
Funkcja Adama przyzwyczaja się do łagodzenia problemu. Rzucę okiemmany
. Wydaje się, że to dość obrzydliwa pomoc.Statystyka programu SQL Server zawiera tylko histogram dla wiodącej kolumny obiektu statystyki. Dlatego możesz tworzyć filtrowane statystyki, które zapewniają histogram wartości dla
Key2
, ale tylko między wierszami zKey1 = 1
. Utworzenie tych filtrowanych statystyk w każdej tabeli naprawia szacunki i prowadzi do oczekiwanego zachowania dla zapytania testowego: każde nowe sprzężenie nie wpływa na ostateczną ocenę liczności (potwierdzoną zarówno w SQL 2016 SP1, jak i SQL 2017).Bez tych filtrowanych statystyk SQL Server podejmie bardziej heurystyczne podejście do szacowania liczności twojego łączenia. Poniższy oficjalny dokument zawiera dobre opisy niektórych heurystyk używanych przez SQL Server na wysokim poziomie: Optymalizacja planów zapytań za pomocą programu SQL Server 2014 Cardinality Estimator .
Na przykład dodanie
USE HINT('ASSUME_JOIN_PREDICATE_DEPENDS_ON_FILTERS')
podpowiedzi do zapytania spowoduje zmianę heurystyki zawierania złączeń, aby przyjąć pewną korelację (a nie niezależność) międzyKey1
predykatem aKey2
predykatem łączenia, co może być korzystne dla zapytania. W przypadku ostatniego zapytania testowego ta podpowiedź zwiększa szacunkową liczność od1,175
do7,551
, ale wciąż jest nieco nieśmiała w stosunku do poprawnego20,000
oszacowania wiersza uzyskanego z filtrowanymi statystykami.Innym podejściem zastosowanym w podobnych sytuacjach jest wyodrębnienie odpowiedniego podzbioru danych do tabel #temp. Zwłaszcza teraz, gdy nowsze wersje SQL Servera nie chętnie zapisują na dysku tabele #temp , osiągnęliśmy dobre wyniki dzięki temu podejściu. Twój opis łączenia wielu do wielu sugeruje, że każda indywidualna tabela #temp w twoim przypadku byłaby względnie mała (lub przynajmniej mniejsza niż końcowy zestaw wyników), więc to podejście może być warte wypróbowania.
źródło
Key1
wartości w każdej tabeli. Teraz mamy ich tysiące.Zasięg Nie ma prawdziwej podstawy poza próbą.
źródło