Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Aktualisieren von SQL Server-Tabellen mit weniger Unterbrechungen durch Partitionswechsel

Eine häufige Anforderung in ETL und verschiedenen Berichtsszenarien ist das stille Laden einer SQL Server-Staging-Tabelle im Hintergrund, damit Benutzer, die die Daten abfragen, nicht von den Schreibvorgängen betroffen sind und umgekehrt. Der Trick besteht darin, wie und wann Sie Benutzer auf die neue, aktualisierte Version der Daten verweisen.

Vereinfachtes Beispiel einer Staging-Tabelle:Eine Bauernmarkt-Analogie

Was ist also eine Staging-Tabelle in SQL? Ein Bereitstellungstisch lässt sich anhand eines Beispiels aus der Praxis besser verstehen:Angenommen, Sie haben einen Tisch voller Gemüse, das Sie auf dem örtlichen Bauernmarkt verkaufen. Wenn sich Ihr Gemüse verkauft und Sie neuen Bestand einführen:

  • Wenn Sie eine Ladung neues Gemüse mitbringen, brauchen Sie 20 Minuten, um den Tisch abzuräumen und den Restbestand durch das neuere Produkt zu ersetzen.
  • Sie möchten nicht, dass Kunden dasitzen und 20 Minuten warten, bis die Umstellung erfolgt, da die meisten ihr Gemüse woanders kaufen.

Was wäre nun, wenn Sie einen zweiten leeren Tisch hätten, an dem Sie das neue Gemüse laden, und während Sie dies tun, können die Kunden immer noch das ältere Gemüse vom ersten Tisch kaufen? (Nehmen wir an, es liegt nicht daran, dass das ältere Gemüse schlecht geworden ist oder anderweitig weniger wünschenswert ist.)

Aktualisieren von Tabellen in SQL Server

Es gibt mehrere Methoden, um ganze Tabellen neu zu laden, während sie aktiv abgefragt werden; Vor zwei Jahrzehnten habe ich ungezügelt Vorteile aus sp_rename gezogen — Ich würde ein Hütchenspiel mit einer leeren Schattenkopie der Tabelle spielen, die Schattenkopie fröhlich neu laden und dann nur die Umbenennung innerhalb einer Transaktion durchführen.

In SQL Server 2005 fing ich an, Schemata zu verwenden, um Schattenkopien von Tabellen zu speichern, die ich einfach mit der gleichen Shell-Game-Technik hin und her transferiert hatte, über die ich in diesen beiden Posts geschrieben habe:

  • Trickschüsse:Schema Switch-a-Roo
  • Schema Switch-a-Roo, Teil 2

Der einzige Vorteil des Übertragens von Objekten zwischen Schemas gegenüber dem Umbenennen besteht darin, dass es keine Warnmeldungen zum Umbenennen eines Objekts gibt – was an sich nicht einmal ein Problem darstellt, außer dass die Warnmeldungen die Verlaufsprotokolle der Agenten viel schneller füllen.

Beide Ansätze erfordern weiterhin eine Schemaänderungssperre (Sch-M), sodass sie warten müssen, bis alle vorhandenen Transaktionen ihre eigenen Sperren freigeben. Sobald sie ihre Sch-M-Sperre erworben haben, blockieren sie alle nachfolgenden Abfragen, die Schemastabilitätssperren (Sch-S) erfordern … was fast jede Abfrage ist. Es kann schnell zu einem Blocking-Chain-Albtraum werden, da alle neuen Abfragen, die Sch-S benötigen, in eine Warteschlange hinter dem Sch-M gelangen müssen. (Und nein, Sie können dies nicht umgehen, indem Sie RCSI oder NOLOCK verwenden überall, da selbst diese Abfragen immer noch Sch-S erfordern. Sie können Sch-S nicht mit einem vorhandenen Sch-M erwerben, da sie nicht kompatibel sind – Michael J. Swart spricht hier darüber.)

Kendra Little hat mir in ihrem Beitrag „Staging Data:Locking Danger with ALTER SCHEMA TRANSFER“ wirklich die Augen über die Gefahren der Schemaübertragung geöffnet. Dort zeigt sie, warum Schematransfer schlimmer sein kann als Umbenennen. Sie hat später eine dritte und viel weniger wirkungsvolle Methode zum Auslagern von Tabellen beschrieben, die ich jetzt ausschließlich verwende:Partitionswechsel. Diese Methode ermöglicht es dem Switch, mit einer niedrigeren Priorität zu warten, was bei den Umbenennungs- oder Schemaübertragungstechniken nicht einmal möglich ist. Joe Sack ging detailliert auf diese Erweiterung ein, die in SQL Server 2014 hinzugefügt wurde:„Exploring Low Priority Lock Wait Options in SQL Server 2014 CTP1.“

Beispiel für den Wechsel der SQL Server-Partition

Schauen wir uns ein einfaches Beispiel an und folgen Kendras gründlichem Kern hier. Zuerst erstellen wir zwei neue Datenbanken:

CREATE DATABASE NewWay;
CREATE DATABASE OldWay;
GO

In der neuen Datenbank erstellen wir eine Tabelle für unser Gemüseinventar und zwei Kopien der Tabelle für unser Hütchenspiel:

USE NewWay;
GO
 
CREATE TABLE dbo.Vegetables_NewWay
(
  VegetableID int,
  Name        sysname,
  WhenPicked  datetime,
  BackStory   nvarchar(max)
);
GO
 
-- we need to create two extra copies of the table.
 
CREATE TABLE dbo.Vegetables_NewWay_prev
(
  VegetableID int,
  Name        sysname,
  WhenPicked  datetime,
  BackStory   nvarchar(max)
);
GO
 
CREATE TABLE dbo.Vegetables_NewWay_hold
(
  VegetableID int,
  Name        sysname,
  WhenPicked  datetime,
  BackStory   nvarchar(max)
);
GO

Wir erstellen eine Prozedur, die die Staging-Kopie der Tabelle lädt und dann eine Transaktion verwendet, um die aktuelle Kopie auszuschalten.

CREATE PROCEDURE dbo.DoTheVeggieSwap_NewWay
AS
BEGIN
  SET NOCOUNT ON;
 
  TRUNCATE TABLE dbo.Vegetables_NewWay_prev;
 
  INSERT dbo.Vegetables_NewWay_prev
    SELECT TOP (1000000) s.session_id, o.name, s.last_successful_logon, 
      LEFT(m.definition, 500)
    FROM sys.dm_exec_sessions AS s
    CROSS JOIN model.sys.all_objects AS o
    INNER JOIN model.sys.all_sql_modules AS m
    ON o.[object_id] = m.[object_id];
 
  -- need to take Sch-M locks here:
 
  BEGIN TRANSACTION;
    ALTER TABLE dbo.Vegetables_NewWay 
      SWITCH TO dbo.Vegetables_NewWay_hold
      WITH (WAIT_AT_LOW_PRIORITY 
            (MAX_DURATION = 1 MINUTES,
             ABORT_AFTER_WAIT = BLOCKERS));
 
    ALTER TABLE dbo.Vegetables_NewWay_prev
      SWITCH TO dbo.Vegetables_NewWay;
  COMMIT TRANSACTION;
 
  -- and now users will query the new data in dbo
  -- can switch the old copy back and truncate it 
  -- without interfering with other queries
 
  ALTER TABLE dbo.Vegetables_NewWay_hold
	SWITCH TO dbo.Vegetables_NewWay_prev;
 
  TRUNCATE TABLE dbo.Vegetables_NewWay_prev;
END
GO

Die Schönheit von WAIT_AT_LOW_PRIORITY Mit dem ABORT_AFTER_WAIT können Sie das Verhalten vollständig steuern Möglichkeit:

ABORT_AFTER_WAIT
Einstellung
Beschreibung / Symptome
SELBST Das bedeutet, dass der Schalter aufgibt nach n Protokoll.

Für die Sitzung, die versucht, den Wechsel durchzuführen, erscheint dies als Fehlermeldung:

Zeitüberschreitung für Sperranforderung überschritten.
BLOCKER Dies diktiert, dass der Schalter bis zu n wartet Minuten, dann zwingt er sich an die Spitze der Linie, indem er alle Blocker vor ihm tötet .

Sitzungen, die versuchen, mit der Tabelle zu interagieren, die durch die Switch-Operation gestoßen wird, sehen eine Kombination dieser Fehlermeldungen:

Ihre Sitzung wurde aufgrund eines DDL-Vorgangs mit hoher Priorität getrennt.

Die Ausführung kann nicht fortgesetzt werden, da sich die Sitzung im Kill-Status befindet.

Beim aktuellen Befehl ist ein schwerwiegender Fehler aufgetreten. Die Ergebnisse sollten, falls vorhanden, verworfen werden.

KEINE Dies besagt, dass der Schalter fröhlich warten wird, bis er an der Reihe ist, unabhängig von MAX_DURATION .

Dies ist dasselbe Verhalten, das Sie beim Umbenennen, Schematransfer oder Partitionswechsel ohne WAIT_AT_LOW_PRIORITY erhalten würden .

Die BLOCKERS Option ist nicht die freundlichste Art, mit Dingen umzugehen, da Sie bereits sagen, dass es durch diese Staging-/Switch-Operation in Ordnung ist, dass Benutzer Daten sehen, die etwas veraltet sind. Ich würde wahrscheinlich lieber SELF verwenden und lassen Sie die Operation in Fällen erneut versuchen, in denen sie die erforderlichen Sperren nicht in der zugewiesenen Zeit erhalten konnte. Ich würde jedoch verfolgen, wie oft es fehlschlägt, insbesondere aufeinanderfolgende Fehler, weil Sie sicherstellen möchten, dass die Daten niemals zu veraltet werden.

Im Vergleich zur alten Art des Wechselns zwischen Schemas

So hätte ich den Wechsel vorher gehandhabt:

USE OldWay;
GO
 
-- create two schemas and two copies of the table
 
CREATE SCHEMA prev AUTHORIZATION dbo;
GO
 
CREATE SCHEMA hold AUTHORIZATION dbo;
GO
 
CREATE TABLE dbo.Vegetables_OldWay
(
  VegetableID int,
  Name sysname,
  WhenPicked datetime,
  BackStory nvarchar(max)
);
GO
 
CREATE TABLE prev.Vegetables_OldWay
(
  VegetableID int,
  Name sysname,
  WhenPicked datetime,
  BackStory nvarchar(max)
);
GO
 
CREATE PROCEDURE dbo.DoTheVeggieSwap_OldWay
AS
BEGIN
  SET NOCOUNT ON;
 
  TRUNCATE TABLE prev.Vegetables_OldWay;
 
  INSERT prev.Vegetables_OldWay
    SELECT TOP (1000000) s.session_id, o.name, s.last_successful_logon, 
      LEFT(m.definition, 500)
    FROM sys.dm_exec_sessions AS s
    CROSS JOIN model.sys.all_objects AS o
    INNER JOIN model.sys.all_sql_modules AS m
    ON o.[object_id] = m.[object_id];
 
  -- need to take Sch-M locks here:
  BEGIN TRANSACTION;
    ALTER SCHEMA hold TRANSFER dbo.Vegetables_OldWay;
    ALTER SCHEMA dbo  TRANSFER prev.Vegetables_OldWay;
  COMMIT TRANSACTION;
 
  -- and now users will query the new data in dbo
  -- can transfer the old copy back and truncate it without 
  -- interfering with other queries:
 
  ALTER SCHEMA prev TRANSFER hold.Vegetables_OldWay;
  TRUNCATE TABLE prev.Vegetables_OldWay;
END
GO

Ich habe Parallelitätstests durchgeführt, indem ich zwei Fenster von Erik Ejlskov Jensens SQLQueryStress verwendet habe:eines, um einen Aufruf der Prozedur jede Minute zu wiederholen, und das andere, um 16 Threads wie diesen tausende Male auszuführen:

BEGIN TRANSACTION;
 
UPDATE TOP (1) dbo.<table> SET name += 'x';
SELECT TOP (10) name FROM dbo.<table> ORDER BY NEWID();
WAITFOR DELAY '00:00:02';
 
COMMIT TRANSACTION;

Sie können sich die Ausgabe von SQLQueryStress, oder sys.dm_exec_query_stats oder Query Store ansehen, und Sie werden etwas in der Art der folgenden Ergebnisse sehen (aber ich empfehle dringend, ein hochwertiges SQL Server-Leistungsüberwachungstool zu verwenden, wenn Sie es ernst meinen Datenbankumgebungen proaktiv optimieren):

Dauer und Fehlerraten Schemaübertragung ABORT_AFTER_WAIT:
SELBST
ABORT_AFTER_WAIT:
BLOCKER
Durchschn. Dauer – Übertragung/Wechsel 96,4 Sekunden 68,4 Sekunden 20,8 Sekunden
Durchschn. Dauer – DML 18,7 Sekunden 2,7 Sekunden 2,9 Sekunden
Ausnahmen – Transfer/Switch 0 0,5/Minute 0
Ausnahmen – DML 0 0 25,5/Minute

Beachten Sie, dass die Dauer und die Anzahl der Ausnahmen stark von Ihren Serverspezifikationen und anderen Vorgängen in Ihrer Umgebung abhängen. Beachten Sie auch, dass es zwar keine Ausnahmen für die Schemaübertragungstests bei der Verwendung von SQLQueryStress gab, Sie jedoch je nach konsumierender Anwendung möglicherweise strengere Timeouts treffen. Und es war im Durchschnitt so viel langsamer, weil sich das Blocken viel aggressiver türmte. Niemand möchte jemals Ausnahmen, aber wenn es einen Kompromiss wie diesen gibt, ziehen Sie vielleicht hier und da ein paar Ausnahmen vor (abhängig von der Häufigkeit des Aktualisierungsvorgangs), anstatt dass alle die ganze Zeit länger warten.

Partitionsumschaltung vs. Umbenennung/Schemaübertragung zum Aktualisieren von SQL Server-Tabellen

Durch Partitionswechsel können Sie auswählen, welcher Teil Ihres Prozesses die Kosten der Parallelität trägt. Sie können dem Umschaltvorgang den Vorzug geben, damit die Daten zuverlässiger aktuell sind, aber das bedeutet, dass einige Ihrer Abfragen fehlschlagen. Umgekehrt können Sie die Abfragen priorisieren, auf Kosten eines langsameren Aktualisierungsprozesses (und des gelegentlichen Fehlers dort). Der Hauptaspekt ist, dass der Partitionswechsel von SQL Server eine überlegene Methode zum Aktualisieren von SQL Server-Tabellen im Vergleich zu den vorherigen Umbenennungs-/Schemaübertragungstechniken in fast allen Punkten ist, und Sie können eine robustere Wiederholungslogik verwenden oder mit Dauertoleranzen experimentieren, um am optimalen Punkt zu landen für Ihre Arbeitsbelastung.