Falsche Schätzung für eine Abfrage in partitionierten Tabellen

7

Ich frage mich, warum SQL Server in einem so einfachen Fall falsche Schätzungen vornimmt. Es gibt ein Szenario.

CREATE PARTITION FUNCTION PF_Test (int) AS RANGE RIGHT 
FOR VALUES (20140801, 20140802, 20140803)

CREATE PARTITION SCHEME PS_Test AS PARTITION PF_Test ALL TO ([Primary])

CREATE TABLE A
(
  DateKey int not null,
  Type int not null,
  constraint PK_A primary key (DateKey, Type) on PS_Test(DateKey)
)

INSERT INTO A (DateKey, Type)
SELECT
  DateKey = N1.n  + 20140801,
  Type = N2.n + 1
FROM dbo.Numbers N1
  cross join dbo.Numbers N2
WHERE N1.n BETWEEN 0 AND 2
  and N2.n BETWEEN 0 AND 10000 - 1

UPDATE STATISTICS A (PK_A) WITH FULLSCAN, INCREMENTAL = ON

CREATE TABLE B
(
  DateKey int not null,
  SubType int not null,
  Type int not null,
  constraint PK_B primary key (DateKey, SubType) on PS_Test(DateKey)
)

INSERT INTO B (DateKey, SubType, Type)
SELECT
  DateKey,
  SubType = Type * 10000 + N.n,
  Type
FROM A
  cross join dbo.Numbers N
WHERE N.n BETWEEN 1 AND 10

UPDATE STATISTICS B (PK_B) WITH FULLSCAN, INCREMENTAL = ON

Die Einrichtung ist also ziemlich einfach, Statistiken sind vorhanden und SQL Server kann korrekte Schätzungen erstellen, wenn wir eine Tabelle abfragen.

select COUNT(*) from A where DateKey = 20140802
--10000
select COUNT(*) from B where DateKey = 20140802
--100000

Aber in dieser einfachen Auswahl sind Schätzungen weit entfernt, und ich sehe keine Erklärung dafür.

SELECT a.DateKey, a.Type
FROM A
  JOIN B
    ON b.DateKey = a.DateKey
    AND b.Type = a.Type
WHERE a.DateKey = 20140802

Ausführungsplan

Unmittelbar nach der Clustered Index-Suche liegt die Schätzung bei 57% vom tatsächlichen Wert! Die reale Abfrage ist noch schlimmer, die Schätzung liegt bei 2% vom tatsächlichen Wert.

PS-Nummerntabelle zur Reproduktion des Setups

DECLARE @UpperBound INT = 1000000;

;WITH cteN(Number) AS
(
  SELECT ROW_NUMBER() OVER (ORDER BY s1.[object_id]) - 1
  FROM sys.all_columns AS s1
  CROSS JOIN sys.all_columns AS s2
)
SELECT n = [Number] INTO dbo.Numbers
FROM cteN WHERE [Number] <= @UpperBound;

CREATE UNIQUE CLUSTERED INDEX CIX_Number ON dbo.Numbers(n)
WITH 
(
  FILLFACTOR = 100,      -- in the event server default has been changed
  DATA_COMPRESSION = ROW -- if Enterprise & table large enough to matter
);

PPS Das gleiche Szenario, jedoch nicht partitioniert, funktioniert einwandfrei.

Alsin
quelle
Obwohl es Statistiken pro Partition gibt, betrachtet der Optimierer immer noch nur das einzelne Histogramm in der gesamten Tabelle. Wenn die Partitionen also stark verzerrt sind, wird dies weitgehend geglättet. Siehe: sqlperformance.com/2015/05/sql-statistics/…
Aaron Bertrand
@ AaronBertrand Ja, aber ein einzelnes Histogramm ist in perfekter Form! Alle 3 Werte sind Schritte. Wenn Tabellen nicht partitioniert sind, liefert dieselbe Abfrage perfekte Schätzungen! SQL Server erzeugt diesen Fehler nur, wenn Bedingung und Verweis auf Partition kombiniert werden, und es ist nicht klar, warum.
Alsin

Antworten:

9

Die Schätzungen (mit dem neuen Kardinalitätsschätzer) sind für einen normalen Join in Ordnung, jedoch weniger genau, wenn der Optimierer die Option eines kolokalisierten Joins in Betracht zieht .

Ein Colocated Join (auch als Partitionsverknüpfung bezeichnet) ist verfügbar, wenn zwei Tabellen verknüpft werden, die auf dieselbe Weise partitioniert sind. Die Idee ist, jeweils eine Partition zu verbinden, wobei verschachtelte Schleifen verwendet werden, die von Partitions-IDs gesteuert werden, die durch einen konstanten Scan (speicherinterne Wertetabelle) bereitgestellt werden.

Regelmäßiger Beitritt

Da für den kolokalisierten Join verschachtelte Schleifen gelten, können Sie den Optimierer zwingen, dies zu vermeiden, indem Sie OPTION (HASH JOIN)beispielsweise Folgendes angeben :

Plan mit Hash Join gezwungen

Die beiden Ziele in diesem Plan sind:

Seek Keys[1]: Prefix:
    PtnId1000, [dbo].[A].DateKey = Scalar Operator((3)), Scalar Operator((20140802))
Seek Keys[1]: Prefix:
    PtnId1003, [dbo].[B].DateKey = Scalar Operator((3)), Scalar Operator((20140802))

Das Optimierungsprogramm hat in beiden Fällen die statische Partitionseliminierung angewendet und genaue Schätzungen für beide Suchvorgänge und den folgenden Join angegeben.

Colocated Join

Wenn der Optimierer einen kolokalisierten Join berücksichtigt (wie in der Frage gezeigt), lauten die Suchvorgänge:

Colocated Join Plan

Seek Keys[1]: Prefix:
    PtnId1000, [dbo].[A].DateKey = Scalar Operator([Expr1006]), Scalar Operator((20140802))
Seek Keys[1]: Prefix:
    PtnId1003, [dbo].[B].DateKey = Scalar Operator([Expr1006]), Scalar Operator((20140802))

... wo [Expr1006]ist der vom Operator Constant Scan zurückgegebene Wert.

Der Kardinalitätsschätzer kann jetzt nicht erkennen, dass der DateKeyWert und die Partitions-ID voneinander abhängig sind, wie dies bei Verwendung von Literalkonstanten der Fall sein könnte. Mit anderen Worten, es ist für den Schätzer nicht ersichtlich, dass der Wert darin [Expr1006]dieselbe Partition wie angibt DateKey = 20140802.

Infolgedessen wählt das CE (standardmäßig) die Schätzung der Selektivität der beiden (scheinbar unabhängigen) Prädikate unter Verwendung der normalen exponentiellen Backoff-Methode .

Dies erklärt die reduzierten Kardinalitätsschätzungen, die den Join speisen. Die geringeren offensichtlichen Kosten dieser Option (aufgrund der falschen Schätzung) bedeuten, dass der Optimierer einen kolokalisierten Join anstelle eines regulären Joins wählt, obwohl es (für Menschen) offensichtlich ist, dass er keinen Wert bietet.

Es gibt verschiedene Möglichkeiten, um diese Lücke in der Logik zu umgehen, einschließlich der Verwendung des Abfragehinweises USE HINT ('ASSUME_MIN_SELECTIVITY_FOR_FILTER_ESTIMATES'). Dies wirkt sich jedoch auf die gesamte Abfrage aus, nicht nur auf die problematische Colocated Join-Alternative. Wie Erik in seiner Antwort bemerkt, könnten Sie auch auf die Verwendung des Legacy-CE hinweisen.

Weitere Informationen zu Colocated Joins finden Sie in meinem Artikel Verbessern der Leistung partitionierter Tabellenverknüpfungen

Paul White 9
quelle
Vielen Dank für eine so ausführliche Antwort, @ paul-white! Sie haben meine Befürchtungen bestätigt, dass SQL Server diese Prädikate als unabhängige behandelt. Ein Hinweis dazu: Ich erhalte den gleichen Ausführungsplan für "Colocated Join", auch wenn diese Tabellen unterschiedliche Partitionsfunktionen verwenden. Die Funktionen sind unterschiedlich, aber identisch, sodass sie dieselbe Partitionsnummer zurückgeben.
Alsin
Wenn zwei Partitionsfunktionen nicht identisch sind, funktioniert dieses Szenario wie erwartet. Ich habe eine Funktion einen Tag zuvor als die andere gestartet, und der Ausführungsplan ist ein perfekter Hash-Join mit korrekten Schätzungen. Es ist eine Art dumme Problemumgehung, aber das ist eine Möglichkeit, dies für uns zu beheben.
Alsin
5

Dies scheint auf den neuen Kardinalitätsschätzer zurückzuführen zu sein, der in SQL Server 2014 eingeführt wurde.

Wenn Sie die Abfrage anweisen, die alte zu verwenden, erhalten Sie einen anderen Plan und korrekte Schätzungen.

SELECT a.DateKey, a.Type
FROM A AS a
  JOIN B AS b
    ON b.DateKey = a.DateKey
    AND b.Type = a.Type
WHERE a.DateKey = 20140802
OPTION(USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'));

NÜSSE

Weitere Informationen finden Sie unter diesen Links:

Erik Darling
quelle