UPDATE-Leistung, bei der sich keine Daten ändern

31

Wenn ich eine UPDATEAnweisung habe, die tatsächlich keine Daten ändert (weil sich die Daten bereits im aktualisierten Zustand befinden). Gibt es einen Leistungsvorteil, wenn die WHEREKlausel überprüft wird , um das Update zu verhindern?

Zum Beispiel würde sich die Ausführungsgeschwindigkeit zwischen UPDATE 1 und UPDATE 2 wie folgt unterscheiden:

CREATE TABLE MyTable (ID int PRIMARY KEY, Value int);
INSERT INTO MyTable (ID, Value)
VALUES
    (1, 1),
    (2, 2),
    (3, 3);

-- UPDATE 1
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2
    AND Value <> 2;
SELECT @@ROWCOUNT;

-- UPDATE 2
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2;
SELECT @@ROWCOUNT;

DROP TABLE MyTable;

Der Grund, den ich frage, ist, dass die Zeilenanzahl die unveränderte Zeile enthalten muss, damit ich weiß, ob ich eine Einfügung durchführen soll, wenn die ID nicht vorhanden ist. Als solches habe ich das UPDATE 2 Formular verwendet. Wenn die Verwendung des Formulars UPDATE 1 einen Leistungsvorteil mit sich bringt, ist es dann möglich, die Anzahl der Zeilen zu ermitteln, die ich irgendwie benötige?

Martin Brown
quelle
Siehe sqlperformance.com/2012/10/t-sql-queries/conditional-updates (obwohl ich den Fall, in dem sich keine Werte ändern, nicht profiliert habe).
Aaron Bertrand

Antworten:

24

Wenn ich über eine UPDATE-Anweisung verfüge, die tatsächlich keine Daten ändert (da sich die Daten bereits im aktualisierten Zustand befinden), hat es einen Leistungsvorteil, die where-Klausel zu überprüfen, um die Aktualisierung zu verhindern?

Dies kann durchaus sein, da es aufgrund von UPDATE 1 einen geringfügigen Leistungsunterschied gibt :

  • keine Zeilen aktualisieren (daher nichts zum Schreiben auf die Festplatte, nicht einmal minimale Protokollaktivität) und
  • Entfernen weniger restriktiver Sperren als für das eigentliche Update erforderlich (daher besser für die gleichzeitige Verwendung) ( siehe Update-Abschnitt zum Ende )

Wie groß der Unterschied ist, müsste von Ihnen auf Ihrem System anhand Ihres Schemas, Ihrer Daten und der Systemlast gemessen werden. Es gibt verschiedene Faktoren, die bestimmen, wie stark sich ein nicht aktualisiertes UPDATE auswirkt:

  • Der Umfang der Konflikte in der Tabelle, die aktualisiert werden
  • Die Anzahl der zu aktualisierenden Zeilen
  • wenn die zu aktualisierende Tabelle UPDATE-Trigger enthält (wie von Mark in einem Kommentar zur Frage angegeben). Wenn Sie ausführen UPDATE TableName SET Field1 = Field1, wird ein Update-Trigger ausgelöst , der angibt, dass das Feld aktualisiert wurde (wenn Sie dies mit den Funktionen UPDATE () oder COLUMNS_UPDATED überprüfen ) und dass das Feld in beiden INSERTEDund den DELETEDTabellen den gleichen Wert hat.

Der folgende zusammenfassende Abschnitt ist auch in Paul Whites Artikel " Die Auswirkungen nicht aktualisierender Updates" zu finden (wie von @spaghettidba in einem Kommentar zu seiner Antwort vermerkt):

SQL Server enthält eine Reihe von Optimierungen, um unnötiges Protokollieren oder Leeren von Seiten bei der Verarbeitung eines UPDATE-Vorgangs zu vermeiden, der zu keiner Änderung der persistenten Datenbank führt.

  • Durch das Nicht-Aktualisieren von Aktualisierungen einer gruppierten Tabelle werden im Allgemeinen zusätzliche Protokollierungs- und Seitenlöschvorgänge vermieden, es sei denn, eine Spalte, die (einen Teil) des Cluster-Schlüssels bildet, ist vom Aktualisierungsvorgang betroffen.
  • Wenn ein Teil des Cluster-Schlüssels auf denselben Wert aktualisiert wird, wird der Vorgang protokolliert, als hätten sich Daten geändert, und die betroffenen Seiten werden im Pufferpool als verschmutzt markiert. Dies ist eine Folge der Konvertierung von UPDATE in eine Lösch-Einfüge-Operation.
  • Heap-Tabellen verhalten sich genauso wie gruppierte Tabellen, außer dass sie keinen Cluster-Schlüssel haben, um zusätzliche Protokollierung oder Seitenlöschung zu verursachen. Dies bleibt auch dann der Fall, wenn auf dem Heap ein nicht gruppierter Primärschlüssel vorhanden ist. Aktualisierungen auf einem Heap, die nicht aktualisiert werden, vermeiden daher im Allgemeinen das zusätzliche Protokollieren und Löschen (siehe jedoch unten).
  • Sowohl Heaps als auch gruppierte Tabellen werden für jede Zeile, in der eine LOB-Spalte mit mehr als 8000 Datenbytes mit einer anderen Syntax als 'SET Spaltenname = Spaltenname' auf denselben Wert aktualisiert wird, zusätzlich protokolliert und geleert.
  • Das Aktivieren einer der beiden Isolationsstufen für die Zeilenversionierung in einer Datenbank führt immer zu einer zusätzlichen Protokollierung und Löschung. Dies geschieht unabhängig von der für die Aktualisierungstransaktion geltenden Isolationsstufe.

Bitte beachten Sie (insbesondere, wenn Sie nicht dem Link folgen, um den vollständigen Artikel von Paul anzuzeigen) die folgenden beiden Punkte:

  1. Aktualisierungen, die nicht aktualisiert werden, weisen noch einige Protokollaktivitäten auf, die darauf hinweisen, dass eine Transaktion beginnt und endet. Es findet nur keine Datenänderung statt (was immer noch eine gute Einsparung ist).

  2. Wie oben erwähnt, müssen Sie auf Ihrem System testen. Verwenden Sie dieselben Suchanfragen, die Paul verwendet, und prüfen Sie, ob Sie dieselben Ergebnisse erhalten. Ich sehe auf meinem System etwas andere Ergebnisse als in dem Artikel gezeigt. Es müssen noch keine schmutzigen Seiten geschrieben werden, sondern etwas mehr Protokollaktivität.


... Die Zeilenanzahl muss die unveränderte Zeile enthalten, damit ich weiß, ob eine Einfügung erfolgen soll, wenn die ID nicht vorhanden ist. ... ist es möglich, die Anzahl der Zeilen zu ermitteln, die ich irgendwie brauche?

Vereinfacht gesagt, wenn Sie sich nur mit einer einzelnen Zeile befassen, können Sie Folgendes tun:

UPDATE MyTable
SET    Value = 2
WHERE  ID = 2
AND Value <> 2;

IF (@@ROWCOUNT = 0)
BEGIN
  IF (NOT EXISTS(
                 SELECT *
                 FROM   MyTable
                 WHERE  ID = 2 -- or Value = 2 depending on the scenario
                )
     )
  BEGIN
     INSERT INTO MyTable (ID, Value) -- or leave out ID if it is an IDENTITY
     VALUES (2, 2);
  END;
END;

Bei mehreren Zeilen können Sie mithilfe der OUTPUTKlausel die Informationen abrufen, die für diese Entscheidung erforderlich sind . Indem Sie genau erfassen, welche Zeilen aktualisiert wurden, können Sie die Elemente eingrenzen, um den Unterschied zwischen dem Nicht-Aktualisieren von nicht vorhandenen Zeilen und dem Nicht-Aktualisieren von vorhandenen Zeilen, die jedoch nicht aktualisiert werden müssen, zu ermitteln.

Ich zeige die grundlegende Implementierung in der folgenden Antwort:

Wie vermeide ich die Verwendung der Zusammenführungsabfrage, wenn mehrere Daten mit dem xml-Parameter aktualisiert werden?

Die in dieser Antwort gezeigte Methode filtert keine vorhandenen Zeilen heraus, die noch nicht aktualisiert werden müssen. Dieser Teil könnte hinzugefügt werden, aber Sie müssen zunächst genau angeben, woher Sie Ihr Dataset beziehen, in dem Sie zusammenführen MyTable. Kommen sie von einem temporären Tisch? Ein tabellenwertiger Parameter (TVP)?


UPDATE 1:

Ich konnte endlich einige Tests durchführen und hier ist, was ich in Bezug auf Transaktionsprotokoll und Sperren gefunden habe. Zuerst das Schema für die Tabelle:

CREATE TABLE [dbo].[Test]
(
  [ID] [int] NOT NULL CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED,
  [StringField] [varchar](500) NULL
);

Als nächstes aktualisiert der Test das Feld auf den Wert, den es bereits hat:

UPDATE rt
SET    rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM   dbo.Test rt
WHERE  rt.ID = 4082117

Ergebnisse:

-- Transaction Log (2 entries):
Operation
----------------------------
LOP_BEGIN_XACT
LOP_COMMIT_XACT


-- SQL Profiler (3 Lock:Acquired events):
Mode            Type
--------------------------------------
8 - IX          5 - OBJECT
8 - IX          6 - PAGE
5 - X           7 - KEY

Zum Schluss der Test, der das Update herausfiltert, weil sich der Wert nicht ändert:

UPDATE rt
SET    rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM   dbo.Test rt
WHERE  rt.ID = 4082117
AND    rt.StringField <> '04CF508B-B78E-4264-B9EE-E87DC4AD237A';

Ergebnisse:

-- Transaction Log (0 entries):
Operation
----------------------------


-- SQL Profiler (3 Lock:Acquired events):
Mode            Type
--------------------------------------
8 - IX          5 - OBJECT
7 - IU          6 - PAGE
4 - U           7 - KEY

Wie Sie sehen, wird beim Herausfiltern der Zeile nichts in das Transaktionsprotokoll geschrieben, im Gegensatz zu den beiden Einträgen, die den Anfang und das Ende der Transaktion markieren. Und obwohl es stimmt, dass diese beiden Einträge fast nichts sind, sind sie immer noch etwas.

Außerdem ist das Sperren der Ressourcen PAGE und KEY weniger restriktiv, wenn die nicht geänderten Zeilen herausgefiltert werden. Wenn keine anderen Prozesse mit dieser Tabelle interagieren, ist dies wahrscheinlich kein Problem (aber wie wahrscheinlich ist das wirklich?). Denken Sie daran, dass diese Tests, die in einem der verlinkten Blogs (und sogar in meinen Tests) gezeigt werden, implizit davon ausgehen, dass keine Konflikte auf dem Tisch liegen, da sie niemals Teil der Tests sind. Zu sagen, dass nicht aktualisierte Updates so leicht sind, dass sich das Filtern nicht lohnt, muss mit einem Salzkorn genommen werden, da die Tests mehr oder weniger im luftleeren Raum durchgeführt wurden. In der Produktion ist diese Tabelle jedoch höchstwahrscheinlich nicht isoliert. Natürlich könnte es durchaus sein, dass das bisschen Protokollieren und restriktivere Sperren nicht zu einer geringeren Effizienz führen. Die zuverlässigste Informationsquelle zur Beantwortung dieser Frage? SQL Server. Speziell:Ihren SQL Server. Es wird Ihnen zeigen, welche Methode für Ihr System besser ist :-).


UPDATE 2:

Wenn die Vorgänge, bei denen der neue Wert mit dem aktuellen Wert übereinstimmt (dh keine Aktualisierung), die Vorgänge übersteigen, bei denen der neue Wert abweicht und die Aktualisierung erforderlich ist, kann sich das folgende Muster als noch besser erweisen, insbesondere wenn Auf dem Tisch liegt viel Streit. Die Idee ist, zuerst einen einfachen SELECTSchritt zu machen, um den aktuellen Wert zu erhalten. Wenn Sie keinen Wert erhalten, haben Sie Ihre Antwort bezüglich der INSERT. Wenn Sie einen Wert haben, können Sie einen einfachen Wert nur dann IFausgeben, wenn er benötigt wird.UPDATE

DECLARE @CurrentValue VARCHAR(500) = NULL,
        @NewValue VARCHAR(500) = '04CF508B-B78E-4264-B9EE-E87DC4AD237A',
        @ID INT = 4082117;

SELECT @CurrentValue = rt.StringField
FROM   dbo.Test rt
WHERE  rt.ID = @ID;

IF (@CurrentValue IS NULL) -- if NULL is valid, use @@ROWCOUNT = 0
BEGIN
  -- row does not exist
  INSERT INTO dbo.Test (ID, StringField)
  VALUES (@ID, @NewValue);
END;
ELSE
BEGIN
  -- row exists, so check value to see if it is different
  IF (@CurrentValue <> @NewValue)
  BEGIN
    -- value is different, so do the update
    UPDATE rt
    SET    rt.StringField = @NewValue
    FROM   dbo.Test rt
    WHERE  rt.ID = @ID;
  END;
END;

Ergebnisse:

-- Transaction Log (0 entries):
Operation
----------------------------


-- SQL Profiler (2 Lock:Acquired events):
Mode            Type
--------------------------------------
6 - IS          5 - OBJECT
6 - IS          6 - PAGE

Es werden also nur 2 statt 3 Sperren erworben, und beide Sperren sind Absichtsfreigaben, nicht Absichtsexklusiv oder Absichtsaktualisierung ( Sperrenkompatibilität ). Berücksichtigt man, dass jede erworbene Sperre auch freigegeben wird, handelt es sich bei jeder Sperre tatsächlich um 2 Vorgänge. Bei dieser neuen Methode handelt es sich also um insgesamt 4 Vorgänge anstelle der 6 Vorgänge der ursprünglich vorgeschlagenen Methode. Berücksichtigt man, dass dieser Vorgang einmal alle 15 ms ausgeführt wird (ungefähr, wie vom OP angegeben), dh ungefähr 66 Mal pro Sekunde. Der ursprüngliche Vorschlag umfasst also 396 Sperr- / Entsperrvorgänge pro Sekunde, während diese neue Methode nur 264 Sperr- / Entsperrvorgänge pro Sekunde für noch leichtere Sperren umfasst. Dies ist keine Garantie für großartige Leistung, aber auf jeden Fall einen Test wert :-).

Solomon Rutzky
quelle
14

Zoomen Sie ein wenig heraus und denken Sie über das größere Bild nach. In der Realität wird Ihre Update-Anweisung folgendermaßen aussehen:

UPDATE MyTable
  SET Value = 2
WHERE
     ID = 2
     AND Value <> 2;

Oder wird es eher so aussehen:

UPDATE Customers
  SET AddressLine1 = '123 Main St',
      AddressLine2 = 'Apt 24',
      City = 'Chicago',
      State = 'IL',
      (and a couple dozen more fields)
WHERE
     ID = 2
     AND (AddressLine1 <> '123 Main St'
     OR AddressLine2 <> 'Apt 24'
     OR City <> 'Chicago'
     OR State <> 'IL'
      (and a couple dozen more fields))

Denn in der realen Welt haben Tabellen viele Spalten. Das bedeutet, dass Sie eine Menge komplexer dynamischer Anwendungslogik generieren müssen, um dynamische Zeichenfolgen zu erstellen, ODER Sie müssen jedes Mal den Vorher-Nachher-Inhalt jedes Felds angeben.

Wenn Sie diese Aktualisierungsanweisungen dynamisch für jede Tabelle erstellen und nur die zu aktualisierenden Felder übergeben, können Sie schnell auf ein Problem mit der Cache-Verschmutzung stoßen , das dem Problem mit den NHibernate-Parametergrößen seit einigen Jahren ähnelt . Schlimmer noch, wenn Sie die Aktualisierungsanweisungen in SQL Server erstellen (wie in gespeicherten Prozeduren), brennen Sie wertvolle CPU-Zyklen, da SQL Server die Verkettung von Zeichenfolgen in großem Maßstab nicht besonders effizient ausführt.

Aufgrund dieser Komplexität ist es normalerweise nicht sinnvoll, diese Art von zeilenweisem, feldweisem Vergleich durchzuführen, während Sie die Aktualisierungen durchführen. Denken Sie stattdessen an satzbasierte Operationen.

Brent Ozar
quelle
1
Mein Beispiel aus der realen Welt ist so einfach, wird aber oft genannt. Meine Schätzung ist einmal alle 15ms zu Spitzenzeiten. Ich habe mich gefragt, ob SQL Server ausreicht, um nicht auf die Festplatte zu schreiben, wenn dies nicht erforderlich ist.
Martin Brown
3

Das Überspringen von Zeilen, die nur dann nicht aktualisiert werden müssen, wenn die Anzahl der Zeilen groß ist (weniger Protokollierung, weniger schmutzige Seiten zum Schreiben auf die Festplatte), kann zu einer Leistungsverbesserung führen.

Bei einzeiligen Aktualisierungen wie in Ihrem Fall ist der Leistungsunterschied völlig vernachlässigbar. Wenn das Aktualisieren der Zeilen in jedem Fall für Sie einfacher ist, tun Sie es.

Weitere Informationen zum Thema finden Sie unter Keine Aktualisierung von Updates von Paul White

Spaghettidba
quelle
3

Sie können das Update kombinieren und in eine Anweisung einfügen. Unter SQL Server können Sie eine MERGE- Anweisung verwenden, um das Update durchzuführen und einzufügen, wenn es nicht gefunden wird. Für MySQL können Sie INSERT ON DUPLICATE KEY UPDATE verwenden .

Russell Harkins
quelle
1

Anstatt die Werte aller Felder zu überprüfen, können Sie anhand der gewünschten Spalten keinen Hash-Wert ermitteln und diesen dann mit dem in der Tabelle gespeicherten Hash-Wert in Bezug auf die Zeile vergleichen.

IF EXISTS (Select 1 from Table where ID =@ID AND HashValue=Sha256(column1+column2))
GOTO EXIT
ELSE
Ruchira Liyanagama
quelle