Daten von Natur aus so geordnet, als wäre es ein Clustered-Index

8

Ich habe die folgende Tabelle mit 7,5 Millionen Datensätzen:

CREATE TABLE [dbo].[TestTable](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [TestCol] [nvarchar](50) NOT NULL,
    [TestCol2] [nvarchar](50) NOT NULL,
    [TestCol3] [nvarchar](50) NOT NULL,
    [Anonymised] [tinyint] NOT NULL,
    [Date] [datetime] NOT NULL,
CONSTRAINT [PK_TestTable] 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]

Ich stelle fest, dass, wenn das Datumsfeld einen nicht gruppierten Index enthält:

CREATE NONCLUSTERED INDEX IX_TestTable_Date ON [dbo].[TestTable] ([Date])

-und ich führe die folgende Abfrage aus:

UPDATE TestTable 
SET TestCol='*GDPR*', TestCol2='*GDPR*', TestCol3='*GDPR*', Anonymised=1
WHERE [Date] <= '25 August 2016'

-Die von der Indexzugriffsoperation zurückgegebenen Daten werden so sortiert, dass sie der Schlüsselreihenfolge des PK / CX entsprechen, wodurch die Leistung verringert wird.

Abfrageplan

Ich war überrascht, dass das Entfernen des Index aus dem Datumsfeld die Leistung der Abfrage tatsächlich um etwa 30% verbessert, da die Sortierung nicht mehr ausgeführt wird:

Abfrageplan

Meine Theorie, und dies mag für die erfahreneren unter Ihnen offensichtlich sein, ist, dass sie herausgefunden hat, dass die Datumsspalte implizit genau so geordnet ist wie der Primärschlüssel / Clustered-Index.

Meine Frage lautet also: Ist es möglich, diese Tatsache zu nutzen, um die Leistung meiner Abfrage zu verbessern?

AproposArmadillo
quelle
1
Ich habe mir die Pläne nicht angesehen, aber ich würde vermuten, dass sich die Leistung (nun, Dauer, keine dieser nutzlosen geschätzten Kosten in%) verbessert hat, da der gelöschte Index nicht mehr aktualisiert werden musste, nicht aufgrund einer Sortieroperation.
Aaron Bertrand
@AaronBertrand Ich lese diese möglicherweise falsch. Bitte korrigieren Sie mich, wenn ich falsch liege. In beiden Abfrageplänen scheint es jedoch eine Indexaktualisierung zu geben. Beziehen Sie sich auf etwas anderes?
AproposArmadillo
1
Wieder sagte ich, ich habe mir die Pläne nicht angesehen. Sie sagten, "Das Entfernen des Index aus dem Datumsfeld verbessert die Leistung der Abfrage" ... Wenn Sie den Index entfernt haben, sollte er nicht im Plan angezeigt werden. Vielleicht haben Sie den falschen Plan gesammelt oder den nicht entfernt Index, von dem Sie dachten, dass Sie es getan haben. Auch hier ist ein geschätzter Prozentsatz für einen Plan ein Indikator, der jedoch in keiner Weise die tatsächliche Leistungsmessung widerspiegelt. Diese Schätzung wird berechnet, bevor die Abfrage überhaupt ausgeführt wird.
Aaron Bertrand
@ Aaron Bertrand, der Index musste sowieso nicht aktualisiert werden, da [Datum] nicht zu den aktualisierten Feldern gehörte.
Denis Rubashkin
1
@Shaffanhoon Haben Sie versucht, den Index neu zu erstellen, [Date]aber in der richtigen DESCReihenfolge? Nur neugierig, da das Prädikat ist <=. Wenn der Index on Date(in der Standardreihenfolge ACS) anderen Abfragen hilft, können Sie möglicherweise versuchen, dem UPDATE einen Tabellenhinweis hinzuzufügen, um die Verwendung der PK zu erzwingen. Oder teilen Sie dies in zwei Teile auf: Erstellen Sie eine temporäre Tabelle, füllen Sie sie mit [Id]basierend auf [Date] <= '25 August 2016'und entfernen Sie sie dann WHEREaus dem UPDATE und fügen Sie sie hinzu FROM dbo.TestTable tt INNER JOIN #tmp ids ON ids.[Id] = tt.[Id]. Es ist schließlich ein UPDATE und muss die tatsächlichen Zeilen, den Index oder die Nr. Finden.
Solomon Rutzky

Antworten:

7

Ich habe Testdaten verspottet, die Ihr Problem hauptsächlich reproduzieren:

INSERT INTO [dbo].[TestTable] WITH (TABLOCK)
SELECT TOP (7000000) N'*NOT GDPR*', N'*NOT GDPR*', N'*NOT GDPR*', 0, DATEADD(DAY, q.RN  / 16965, '20160801')
FROM
(
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) q
ORDER BY q.RN
OPTION (MAXDOP 1);


DROP INDEX IF EXISTS [dbo].[TestTable].IX_TestTable_Date;
CREATE NONCLUSTERED INDEX IX_TestTable_Date ON [dbo].[TestTable] ([Date]);

Statistiken für die Abfrage, die den nicht gruppierten Index verwendet:

Tabelle 'TestTable'. Scananzahl 1, logische Lesevorgänge 1299838, physische Lesevorgänge 0, Vorlesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Vorlesevorgänge 0.

SQL Server-Ausführungszeiten: CPU-Zeit = 984 ms, verstrichene Zeit = 988 ms.

Statistiken für die Abfrage, die den Clustered-Index verwendet:

Tabelle 'TestTable'. Scananzahl 1, logische Lesevorgänge 72609, physische Lesevorgänge 0, Vorlesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Vorlesevorgänge 0.

SQL Server-Ausführungszeiten: CPU-Zeit = 781 ms, verstrichene Zeit = 772 ms.

Kommen wir zu Ihrer Frage:

Ist es möglich, diese Tatsache zu nutzen, um die Leistung meiner Abfrage zu verbessern?

Ja. Sie können den nicht gruppierten Index verwenden, den Sie bereits idbenötigen, um den Maximalwert , der aktualisiert werden muss, effizient zu ermitteln . Wenn Sie dies in einer Variablen speichern und danach filtern, erhalten Sie einen Abfrageplan für das Update, der den Clustered-Index-Scan (ohne Sortierung) durchführt, der vorzeitig beendet wird und daher weniger E / A ausführt. Hier ist eine Implementierung:

DECLARE @Id INT;

SELECT TOP (1) @Id = Id
FROM dbo.TestTable 
WHERE [Date] <= '25 August 2016'
ORDER BY [Date] DESC, Id DESC;

UPDATE TestTable 
SET TestCol='*GDPR*', TestCol2='*GDPR*', TestCol3='*GDPR*', Anonymised=1
WHERE [Id] < @Id AND [Date] <= '25 August 2016'
AND [Anonymised] <> 1 -- optional
OPTION (MAXDOP 1);

Führen Sie Statistiken für die neue Abfrage aus:

Tabelle 'TestTable'. Scananzahl 1, logische Lesevorgänge 3, physische Lesevorgänge 0, Vorlesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Lobvorlesevorgänge 0.

Tabelle 'TestTable'. Scananzahl 1, logische Lesevorgänge 4776, physische Lesevorgänge 0, Vorlesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Vorlesevorgänge 0.

SQL Server-Ausführungszeiten: CPU-Zeit = 515 ms, verstrichene Zeit = 510 ms.

Sowie der Abfrageplan:

ok Abfrageplan

Nach alledem lässt Ihr Wunsch, die Abfrage schneller zu machen, darauf schließen, dass Sie die Abfrage mehrmals ausführen möchten. Im Moment hat Ihre Abfrage einen offenen Filter für die dateSpalte. Ist es wirklich notwendig, die Zeilen mehr als einmal zu anonymisieren? Können Sie vermeiden, bereits anonymisierte Zeilen zu aktualisieren oder zu scannen? Es sollte auf jeden Fall schneller sein, eine Reihe von Daten mit Daten auf beiden Seiten zu aktualisieren. Sie können die AnonymisedSpalte auch zu Ihrem Index hinzufügen , dieser Index muss jedoch während Ihrer UPDATEAbfrage aktualisiert werden. Zusammenfassend lässt sich sagen, dass Sie es vermeiden sollten, dieselben Daten immer wieder zu verarbeiten, wenn Sie können.

Die ursprüngliche Abfrage, die Sie mit der Sortierung haben, ist aufgrund der im Clustered Index UpdateOperator ausgeführten Arbeit langsamer . Die für die Indexsuche und -sortierung aufgewendete Zeit beträgt nur 407 ms. Sie können dies im aktuellen Plan sehen. Der Plan wird im Zeilenmodus ausgeführt, sodass die für die Sortierung aufgewendete Zeit die Zeit dieses Operators zusammen mit jedem untergeordneten Operator ist:

Geben Sie hier die Bildbeschreibung ein

Damit bleibt dem Sortieroperator etwa 1600 ms Zeit. SQL Server muss Seiten aus dem Clustered-Index lesen, um die Aktualisierung durchzuführen. Sie können sehen, dass der Clustered Index UpdateOperator 1205921 logische Lesevorgänge ausführt. Weitere Informationen zum Sortieren von Optimierungen für DML und zum optimierten Prefetch finden Sie in diesem Blogbeitrag von Paul White .

Der andere Abfrageplan, den Sie (ohne Sortierung) haben, benötigt 683 ms für den Clustered-Index-Scan und etwa 550 ms für den Clustered Index UpdateOperator. Der Aktualisierungsoperator führt für diese Abfrage keine E / A durch.

Die einfache Antwort darauf, warum der Plan mit der Sortierung langsamer ist, besteht darin, dass SQL Server den Clustered-Index für diesen Plan logischer liest als der Clustered-Index-Scan-Plan. Selbst wenn sich alle benötigten Daten im Speicher befinden, ist der Aufwand und die Kosten für diese logischen Lesevorgänge immer noch hoch. Eine bessere Antwort ist viel schwieriger zu bekommen, da die Pläne meines Wissens keine weiteren Details enthalten. Es ist möglich, PerfView oder ein anderes auf ETW-Tracing basierendes Tool zu verwenden, um Anrufstapel zwischen den Abfragen zu vergleichen:

Geben Sie hier die Bildbeschreibung ein

Links befindet sich die Abfrage, die den Clustered-Index-Scan durchführt, und rechts die Abfrage, die die Sortierung durchführt. Ich habe Anrufstapel blau oder rot markiert, die nur in einer Abfrage angezeigt werden. Es überrascht nicht, dass die verschiedenen Aufrufstapel mit einer hohen Anzahl von abgetasteten CPU-Zyklen für die Sortierabfrage mit den logischen Lesevorgängen zu tun zu haben scheinen, die zum Durchführen der Aktualisierung des Clustered-Index erforderlich sind. Darüber hinaus gibt es Unterschiede in der Anzahl der abgetasteten Zyklen zwischen den Abfragen für dieselbe Operation. Zum Beispiel benötigt die Abfrage mit der Sortierung 31 Zyklen zum Erfassen von Latches, während die Abfrage mit dem Scan nur 9 Zyklen zum Erfassen von Latches benötigt.

Ich vermute, dass SQL Server den langsameren Plan aufgrund einer Kostenbeschränkung für den Abfrageplan-Operator auswählt. Möglicherweise ist ein Teil des Unterschieds in der Laufzeit auf die Hardware oder Ihre Edition von SQL Server zurückzuführen. In jedem Fall kann SQL Server nicht herausfinden, dass die Datumsspalte implizit genau so geordnet ist wie der Clustered-Index. Die Daten werden vom Clustered-Index-Scan in der Reihenfolge der Clustered-Schlüssel zurückgegeben, sodass beim Sortieren der Clustered-Index-Aktualisierung keine Sortierung durchgeführt werden muss, um die E / A zu optimieren.

Joe Obbish
quelle