ROW_NUMBER () OVER (PARTITION VON B, A ORDER VON C) verwendet keinen Index für (A, B, C)

12

Betrachten Sie diese beiden Funktionen:

ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C)

ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C)

Soweit ich weiß, liefern sie genau das gleiche Ergebnis. Mit anderen Worten, die Reihenfolge, in der Sie die Spalten in der PARTITION BYKlausel auflisten, spielt keine Rolle.

Wenn es einen Index für gibt, habe (A,B,C)ich erwartet, dass der Optimierer diesen Index in beiden Varianten verwendet.

Überraschenderweise entschied sich das Optimierungsprogramm für eine zusätzliche explizite Sortierung in der zweiten Variante.

Ich habe es auf SQL Server 2008 Standard und SQL Server 2014 Express gesehen.

Hier ist ein vollständiges Skript, mit dem ich es reproduziert habe.

Versucht auf Microsoft SQL Server 2014 - 12.0.2000.8 (X64) 20.02.2014 20:04:26 Copyright (c) Microsoft Corporation Express Edition (64-Bit) unter Windows NT 6.1 (Build 7601: Service Pack 1)

und Microsoft SQL Server 2014 (SP1-CU7) (KB3162659) - 12.0.4459.0 (X64) 27. Mai 2016, 15:33:17 Uhr Copyright (c) Microsoft Corporation Express Edition (64-Bit) unter Windows NT 6.1 (Build 7601: Service Pack 1)

mit altem und neuem Cardinality Estimator unter Verwendung von OPTION (QUERYTRACEON 9481)und OPTION (QUERYTRACEON 2312).

Richten Sie Tabelle, Index und Beispieldaten ein

CREATE TABLE [dbo].[T](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [A] [int] NOT NULL,
    [B] [int] NOT NULL,
    [C] [int] NOT NULL,
    CONSTRAINT [PK_T] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, 
STATISTICS_NORECOMPUTE = OFF, 
IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, 
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_ABC] ON [dbo].[T]
(
    [A] ASC,
    [B] ASC,
    [C] ASC
)WITH (PAD_INDEX = OFF, 
STATISTICS_NORECOMPUTE = OFF, 
SORT_IN_TEMPDB = OFF, 
DROP_EXISTING = OFF, 
ONLINE = OFF, 
ALLOW_ROW_LOCKS = ON, 
ALLOW_PAGE_LOCKS = ON)
GO

INSERT INTO [dbo].[T] ([A],[B],[C]) VALUES
(10, 20, 30),
(10, 21, 31),
(10, 21, 32),
(10, 21, 33),
(11, 20, 34),
(11, 21, 35),
(11, 21, 36),
(12, 20, 37),
(12, 21, 38),
(13, 21, 39);

Abfragen

SELECT -- AB
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);

SELECT -- BA
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);

SELECT -- both
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
FROM T
ORDER BY C
OPTION(RECOMPILE);

Ausführungspläne

TEILUNG DURCH A, B

AB

TEILUNG DURCH B, A

BA

Beide

beide

Wie Sie sehen, hat der zweite Plan eine zusätzliche Sortierung. Es ordnet nach B, A, C. Das Optimierungsprogramm ist anscheinend nicht schlau genug, um zu erkennen, dass PARTITION BY B,Aes mit PARTITION BY A,Bden Daten identisch ist, und sortiert sie neu.

Interessanterweise enthält die dritte Abfrage beide Varianten von ROW_NUMBERund es gibt keine zusätzliche Sortierung! Der Plan ist derselbe wie für die erste Abfrage. (Das Sequenzprojekt enthält in der Ausgabeliste einen zusätzlichen Ausdruck für die zusätzliche Spalte, jedoch keine zusätzliche Sortierung.) In diesem komplizierteren Fall schien das Optimierungsprogramm also klug genug zu sein, um zu erkennen, dass PARTITION BY B,Aes dasselbe ist wie PARTITION BY A,B.

In der ersten und dritten Abfrage hat der Index-Scan-Operator die Eigenschaft Sortiert: Wahr, in der zweiten Abfrage ist sie Falsch.

Noch interessanter, wenn ich die dritte Abfrage so umschreibe (zwei Spalten vertauschen):

SELECT -- both
    ID,A,B,C
    ,ROW_NUMBER() OVER (PARTITION BY B,A ORDER BY C) AS rnBA
    ,ROW_NUMBER() OVER (PARTITION BY A,B ORDER BY C) AS rnAB
FROM T
ORDER BY C
OPTION(RECOMPILE);

dann erscheint die extra Sortierung wieder!

Könnte jemand Licht ins Dunkel bringen? Was ist hier im Optimierer los?

Vladimir Baranov
quelle
Kommentare archiviert .
Paul White 9

Antworten:

2

Es scheint, dass es keine gute endgültige Antwort auf die Frage gibt, was im Optimierer vor sich geht, es sei denn, Sie sind dessen Entwickler und kennen dessen Interna.

Ich werde die Kommentare hier zusammenstellen.

Insgesamt scheint es zu hart zu sein, ihn als Fehler zu bezeichnen, da das Endergebnis der Abfrage korrekt ist. In einigen Fällen ist der Ausführungsplan einfach nicht optimal. ypercube Aaron , Martin Smith und Aaron Bertrand nennen es "verpasste Optimierung".

  • Scheint GROUP BY a,bund GROUP BY b,aergibt identische Pläne, PARTITION BYkann aber nicht dieselbe Transformation verwenden

  • Es fehlen auch andere Optimierungen, bei denen Fensterfunktionen mit derselben Fensterspezifikation eine zusätzliche Sortieroperation haben können, wenn sie in der Auswahlliste durch eine mit einer anderen Spezifikation getrennt sind.

  • Ja, das scheint eine weitere verpasste Optimierung zu sein, und davon gibt es viele. Der Optimierer wurde von Menschen geschrieben und ist nicht perfekt


Es gibt einen etwas verwandten Artikel Absteigende Indizes. Indexreihenfolge, Parallelität und Rangfolgenberechnungen von Itzik Ben-Gan. Dort bespricht Itzik absteigende Indizes und gibt auch ein Beispiel, wie sich die Richtung der Indexdefinition auf Fensterfunktionen mit Partitionen auswirkt. Er zeigt Beispiele für Abfragen und generierte Pläne mit ROW_NUMBEReinem zusätzlichen Sortieroperator, den das Optimierungsprogramm hätte vermeiden können.


Für mich wäre das praktische Ergebnis, diese Besonderheit des Optimierers im Auge zu behalten. PARTITION BYVersuchen Sie bei der Verwendung von In-Window-Funktionen immer, die Reihenfolge, in der Sie die Spalten PARTITION BYauflisten, mit der Reihenfolge abzugleichen, in der sie im Index aufgeführt sind. Auch wenn es keine Rolle spielen sollte.

Eine andere Seite dieser Vorsichtsmaßnahme ist, wenn Sie Ihre Indizes überprüfen und einige Spalten in der Indexdefinition vertauschen. Beachten Sie, dass Sie möglicherweise versehentlich einige vorhandene Abfragen betreffen, die anscheinend nicht betroffen sein sollten. So ist mir diese Besonderheit des Optimierers aufgefallen.

Andernfalls kann das Optimierungsprogramm den Index möglicherweise nicht vollständig nutzen. Selbst wenn das Optimierungsprogramm einen optimalen Plan auswählt, kann sich dieser Plan mit einer geringfügigen unschuldigen Änderung der Abfrage in einen weniger optimalen Plan ändern, z. B. durch Ändern der Spaltenreihenfolge in der SELECTAnweisung.

Vladimir Baranov
quelle