Ich habe 3 "große" Tabellen, die auf einem Spaltenpaar (beide int
s) verbunden sind.
- Tabelle 1 enthält ~ 200 Millionen Zeilen
- Tabelle 2 enthält ~ 1,5 Millionen Zeilen
- Tabelle 3 enthält ~ 6 Millionen Zeilen
Jeder Tisch hat einen Clustered - Index auf Key1
, Key2
und dann eine weitere Spalte. Key1
hat eine geringe Kardinalität und ist sehr schief. In der WHERE
Klausel wird immer darauf verwiesen . Key2
wird in der WHERE
Klausel nie erwähnt . Jeder Join ist viele zu viele.
Das Problem liegt in der Kardinalitätsschätzung. Die Ausgabeschätzung jedes Joins wird kleiner statt größer . Dies führt zu endgültigen Schätzungen von niedrigen Hunderten, wenn das tatsächliche Ergebnis weit in die Millionen geht.
Gibt es eine Möglichkeit für mich, das CE zu besseren Schätzungen zu bewegen?
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;
Lösungen, die ich ausprobiert habe:
- Erstellen mehrspaltiger Statistiken für
Key1
,Key2
- Erstellen von Tonnen von gefilterten Statistiken auf
Key1
(Dies hilft ziemlich viel, aber ich habe am Ende Tausende von benutzerdefinierten Statistiken in der Datenbank.)
Maskierter Ausführungsplan (Entschuldigung für die schlechte Maskierung)
In dem Fall, den ich betrachte, hat das Ergebnis 9 Millionen Zeilen. Das neue CE schätzt 180 Zeilen; Das alte CE schätzt 6100 Zeilen.
Hier ist ein reproduzierbares Beispiel:
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;
quelle
make_parallel
Funktion wird verwendet, um das Problem zu lösen. Ich werde einen Blick darauf werfenmany
. Scheint ein ziemlich grobes Pflaster zu sein.SQL Server-Statistiken enthalten nur ein Histogramm für die führende Spalte des Statistikobjekts. Daher können Sie gefilterte Statistiken erstellen, die ein Histogramm von Werten für
Key2
, jedoch nur zwischen Zeilen mit bereitstellenKey1 = 1
. Das Erstellen dieser gefilterten Statistiken für jede Tabelle korrigiert die Schätzungen und führt zu dem Verhalten, das Sie für die Testabfrage erwarten: Jeder neue Join hat keinen Einfluss auf die endgültige Kardinalitätsschätzung (bestätigt in SQL 2016 SP1 und SQL 2017).Ohne diese gefilterten Statistiken verfolgt SQL Server einen heuristischeren Ansatz zur Schätzung der Kardinalität Ihres Joins. Das folgende Whitepaper enthält allgemeine Beschreibungen einiger der von SQL Server verwendeten Heuristiken: Optimieren Ihrer Abfragepläne mit dem SQL Server 2014 Cardinality Estimator .
Wenn Sie beispielsweise den
USE HINT('ASSUME_JOIN_PREDICATE_DEPENDS_ON_FILTERS')
Hinweis zu Ihrer Abfrage hinzufügen , wird die Heuristik des Join-Containments so geändert, dass eine gewisse Korrelation (und keine Unabhängigkeit) zwischen demKey1
Prädikat und demKey2
Join-Prädikat angenommen wird, was für Ihre Abfrage von Vorteil sein kann. Bei der abschließenden Testabfrage erhöht dieser Hinweis die Kardinalitätsschätzung von1,175
bis7,551
, ist jedoch immer noch ziemlich schüchtern gegenüber der korrekten20,000
Zeilenschätzung, die mit den gefilterten Statistiken erstellt wurde.Ein anderer Ansatz, den wir in ähnlichen Situationen verwendet haben, besteht darin, die relevante Teilmenge der Daten in # temp-Tabellen zu extrahieren. Insbesondere jetzt, da neuere Versionen von SQL Server nicht mehr eifrig # temp-Tabellen auf die Festplatte schreiben , haben wir mit diesem Ansatz gute Ergebnisse erzielt. Ihre Beschreibung Ihres Viele-zu-Viele-Joins impliziert, dass jede einzelne # temp-Tabelle in Ihrem Fall relativ klein (oder zumindest kleiner als die endgültige Ergebnismenge) ist. Daher ist dieser Ansatz möglicherweise einen Versuch wert.
quelle
Key1
für jede Tabelle zu einer pro Wert. Wir haben jetzt Tausende von ihnen.Eine Reichweite. Keine andere wirkliche Basis als zu versuchen.
quelle