Database
 sql >> Datenbank >  >> RDS >> Database

Prüfen, ob eine Nicht-LOB-Spalte aktualisiert werden muss

Gelegentlich sehe ich Leute, die versuchen, ihre Update-Anweisungen zu "optimieren", um zu vermeiden, dass derselbe Wert in eine bestimmte Spalte geschrieben wird. Mein Verständnis war immer, dass, wenn Sie eine Zeile aktualisieren, vorausgesetzt, dass alle Werte in der Zeile sind, die Kosten für das Sperren der Zeile viel höher sind als die inkrementellen Kosten für das Aktualisieren einer, zwei oder aller Spalten in dieser Zeile .

Also habe ich eine einfache Tabelle erstellt, um dies zu testen:

CREATE TABLE dbo.whatever
(
  ID INT IDENTITY(1,1) PRIMARY KEY,
  v1 NVARCHAR(50) NOT NULL,
  v2 NVARCHAR(50) NOT NULL,
  v3 NVARCHAR(50) NOT NULL,
  v4 NVARCHAR(50) NOT NULL,
  v5 NVARCHAR(50) NOT NULL,
  v6 NVARCHAR(50) NOT NULL
);

Dann habe ich eine gespeicherte Prozedur erstellt, um die Tabelle mit 50.000 Zeilen mit einer Vielzahl kleiner Zeichenfolgen zu füllen:

CREATE PROCEDURE dbo.clean
AS
BEGIN
  SET NOCOUNT ON;
 
  TRUNCATE TABLE dbo.whatever;
 
  ;WITH x(d) AS
  (
    SELECT d FROM
    (
      VALUES (N'abc'),(N'def'),(N'ghi'),
             (N'jkl'),(N'mno'),(N'pqr')
    ) AS y(d)
  )
  INSERT dbo.whatever(v1, v2, v3, v4, v5, v6)
  SELECT TOP (50000) x1.d, x2.d, x3.d, x4.d, x5.d, x6.d
   FROM x AS x1, x AS x2, x AS x3, x AS x4,
        x AS x5, x AS x6, x AS x7;
END
GO

Dann habe ich Update-Anweisungen geschrieben, die auf zwei Arten formuliert sind, damit Sie das Schreiben in eine bestimmte Spalte "vermeiden" können, wenn diese Variablenzuweisung gegeben ist:

DECLARE
  @v1 NVARCHAR(50) = N'abc',
  @v2 NVARCHAR(50) = N'def',
  @v3 NVARCHAR(50) = N'ghi',
  @v4 NVARCHAR(50) = N'jkl',
  @v5 NVARCHAR(50) = N'mno',
  @v6 NVARCHAR(50) = N'pqr';

Prüfen Sie zunächst mit einem CASE-Ausdruck, ob der Wert in der Spalte mit dem Wert in der Variablen übereinstimmt:

UPDATE dbo.whatever SET
  v1 = CASE WHEN v1 <> @v1 THEN @v1 ELSE v1 END,
  v2 = CASE WHEN v2 <> @v2 THEN @v2 ELSE v2 END,
  v3 = CASE WHEN v3 <> @v3 THEN @v3 ELSE v3 END,
  v4 = CASE WHEN v4 <> @v4 THEN @v4 ELSE v4 END,
  v5 = CASE WHEN v5 <> @v5 THEN @v5 ELSE v5 END,
  v6 = CASE WHEN v6 <> @v6 THEN @v6 ELSE v6 END
WHERE
(
     v1 <> @v1 OR v2 <> @v2 OR v3 <> @v3 
  OR v4 <> @v4 OR v5 <> @v5 OR v6 <> @v6
);

Und zweitens, indem für jede Spalte ein unabhängiges UPDATE ausgegeben wird (wobei jedes nur auf die Zeilen abzielt, in denen sich dieser Wert tatsächlich geändert hat):

UPDATE dbo.whatever SET v1 = @v1 WHERE v1 <> @v1;
UPDATE dbo.whatever SET v2 = @v2 WHERE v2 <> @v2;
UPDATE dbo.whatever SET v3 = @v3 WHERE v3 <> @v3;
UPDATE dbo.whatever SET v4 = @v4 WHERE v4 <> @v4;
UPDATE dbo.whatever SET v5 = @v5 WHERE v5 <> @v5;
UPDATE dbo.whatever SET v6 = @v6 WHERE v6 <> @v6;

Dann würde ich dies mit der Art und Weise vergleichen, wie die meisten von uns dies heute tun würden:AKTUALISIEREN Sie einfach alle Spalten, ohne sich darum zu kümmern, ob dies der bereits vorhandene Wert für diese bestimmte Spalte war:

UPDATE dbo.whatever SET
  v1 = @v1, v2 = @v2, v3 = @v3,
  v4 = @v4, v5 = @v5, v6 = @v6
WHERE
(
     v1 <> @v1 OR v2 <> @v2 OR v3 <> @v3 
  OR v4 <> @v4 OR v5 <> @v5 OR v6 <> @v6
);

(Diese gehen alle davon aus, dass die Spalten und die Parameter/Variablen nicht NULL-fähig sind – sie müssten COALESCE verwenden, um den Vergleich von NULL-Werten auf beiden Seiten zu berücksichtigen, wenn dies der Fall ist. Sie gehen auch davon aus, dass Sie eine zusätzliche WHERE-Klausel dazu haben würden auf bestimmte Zeilen abzielen – in diesem Beispiel könnten Sie die erste und dritte Abfrage ohne die allumfassende WHERE-Klausel ausführen und nahezu identische Ergebnisse sehen. Ich habe dies der Kürze halber einfach gehalten.)

Dann wollte ich sehen, was in diesen drei Fällen passiert, wenn irgendein Wert geändert werden könnte, wenn bestimmte Werte geändert werden könnten, wenn keine Werte geändert würden und wenn alle Werte geändert würden. Ich könnte dies beeinflussen, indem ich die gespeicherte Prozedur so ändere, dass Konstanten in bestimmte Spalten eingefügt werden, oder indem ich die Art und Weise ändere, wie Variablen zugewiesen werden.

-- to show when any value might change in a row, the procedure uses the full cross join:
 
  SELECT TOP (50000) x1.d, x2.d, x3.d, x4.d, x5.d, x6.d
 
-- to show when particular values will change on many rows, we can hard-code constants:
 
  -- two values exempt:
  SELECT TOP (50000) N'abc', N'def', x3.d, x4.d, x5.d, x6.d
 
  -- four values exempt:
  SELECT TOP (50000) N'abc', N'def', N'ghi', N'jkl', x5.d, x6.d
 
-- to show when no values will change, we hard-code all six values:
 
  SELECT TOP (50000) N'abc', N'def', N'ghi', N'jkl', N'mno', N'pqr'
 
-- and to show when all values will change, a different variable assignment would take place:
 
DECLARE
  @v1 NVARCHAR(50) = N'zzz',
  @v2 NVARCHAR(50) = N'zzz',
  @v3 NVARCHAR(50) = N'zzz',
  @v4 NVARCHAR(50) = N'zzz',
  @v5 NVARCHAR(50) = N'zzz',
  @v6 NVARCHAR(50) = N'zzz';

Ergebnisse

Nach diesen Tests hat das „blinde Update“ in jedem einzelnen Szenario gewonnen. Jetzt denken Sie, was sind ein paar hundert Millisekunden? Extrapolieren. Wenn Sie viele Aktualisierungen in Ihrem System durchführen, kann dies wirklich zu einer Belastung werden.

Detaillierte Ergebnisse im Plan-Explorer:Jede Änderung | 2 Werte ausgenommen | 4 Werte ausgenommen | Alle Werte ausgenommen | Alle Änderungen

Aufgrund des Feedbacks von Roji habe ich beschlossen, dies auch mit ein paar Indizes zu testen:

CREATE INDEX x1 ON dbo.whatever(v1);
CREATE INDEX x2 ON dbo.whatever(v2);
CREATE INDEX x3 ON dbo.whatever(v3) INCLUDE(v4,v5,v6);

Die Duration wurde mit diesen Indizes erheblich verlängert:

Detaillierte Ergebnisse im Plan-Explorer:Jede Änderung | 2 Werte ausgenommen | 4 Werte ausgenommen | Alle Werte ausgenommen | Alle Änderungen

Schlussfolgerung

Aus diesem Test scheint es mir, dass es sich normalerweise nicht lohnt, zu prüfen, ob ein Wert aktualisiert werden soll. Wenn sich Ihre UPDATE-Anweisung auf mehrere Spalten auswirkt, ist es fast immer günstiger, alle Spalten zu scannen, in denen sich Werte geändert haben könnten, anstatt jede Spalte einzeln zu prüfen. In einem zukünftigen Beitrag werde ich untersuchen, ob dieses Szenario für LOB-Spalten parallel ist.