sql >> Databasteknik >  >> RDS >> Database

Kontrollera om en icke-LOB-kolumn behöver uppdateras

Ibland ser jag människor försöka "optimera" sina uppdateringssatser för att undvika att skriva samma värde till en viss kolumn. Min uppfattning har alltid varit att om du ska uppdatera en rad, förutsatt att alla värden är i rad, är kostnaderna för att låsa raden mycket högre än den inkrementella kostnaden för att uppdatera en, två eller alla kolumner i den raden .

Så jag skapade en enkel tabell för att testa detta:

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
);

Sedan skapade jag en lagrad procedur för att fylla tabellen med 50 000 rader med en mängd olika små strängar:

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

Sedan skrev jag uppdateringssatser formulerade på två sätt som du kan "undvika" att skriva till en specifik kolumn, givet denna variabeltilldelning:

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';

Först genom att använda ett CASE-uttryck för att kontrollera om värdet i kolumnen är detsamma som värdet i variabeln:

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
);

Och för det andra genom att utfärda en oberoende UPPDATERING för varje kolumn (var och en riktar sig bara till de rader där det värdet faktiskt har ändrats):

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;

Då skulle jag jämföra detta med hur de flesta av oss skulle göra detta idag:UPPDATERA bara alla kolumner utan att bry mig om det var det redan existerande värdet för just den kolumnen:

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
);

(Alla dessa förutsätter att kolumnerna och parametrarna/variablerna inte är NULL-bara – de skulle behöva använda COALESCE för att redogöra för jämförelse av NULLs på båda sidor om så är fallet. De antar också att du skulle ha en ytterligare WHERE-sats för att rikta in sig på specifika rader – i det här exemplet kan du köra den första och tredje frågan utan den allomfattande WHERE-satsen och se nästan identiska resultat. Jag höll det här enkelt för korthets skull.)

Sedan ville jag se vad som händer i dessa tre fall när vilket värde som helst kan ändras, när vissa värden kan ändras, när inga värden kommer att ändras och när alla värden kommer att ändras. Jag skulle kunna påverka detta genom att ändra den lagrade proceduren för att infoga konstanter i särskilda kolumner, eller genom att ändra hur variabler tilldelades.

-- 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';

Resultat

Efter att ha kört dessa tester vann den "blinda uppdateringen" i varje enskilt scenario. Nu tänker du, vad är ett par hundra millisekunder? Extrapolera. Om du utför många uppdateringar i ditt system kan detta verkligen börja ta hårt.

Detaljerade resultat i Plan Explorer:Alla ändringar | 2 värden undantagna | 4 värden undantagna | Alla värden undantagna | Allt förändras

Baserat på feedback från Roji bestämde jag mig för att testa detta med några index också:

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);

Durationerna ökade avsevärt med dessa index:

Detaljerade resultat i Plan Explorer:Alla ändringar | 2 värden undantagna | 4 värden undantagna | Alla värden undantagna | Allt förändras

Slutsats

Av detta test tycks det för mig att det vanligtvis inte är värt att kolla om ett värde ska uppdateras. Om din UPDATE-sats påverkar flera kolumner är det nästan alltid billigare för dig att skanna alla kolumner där ett värde kan ha ändrats i stället för att kontrollera varje kolumn individuellt. I ett framtida inlägg kommer jag att undersöka om detta scenario är parallellt för LOB-kolumner.


  1. PostgreSQL FEL:avbryter uttalande på grund av konflikt med återställning

  2. Hur man använder "Gilla" i SQL

  3. MySQL Tutorial:MySQL IN-uttalande (avancerat)

  4. Konvertera en Unix-tidsstämpel till ett datumvärde i Oracle