sql >> Database teknologi >  >> RDS >> Database

Kontrollerer, om en ikke-LOB-kolonne skal opdateres

Af og til ser jeg folk forsøge at "optimere" deres opdateringsudsagn for at undgå at skrive den samme værdi til en bestemt kolonne. Min forståelse har altid været, at hvis du vil opdatere en række, forudsat at alle værdierne er i rækken, er omkostningerne ved at låse rækken meget højere end de trinvise omkostninger ved at opdatere en, to eller alle kolonner i den række. .

Så jeg lavede en simpel tabel for at teste dette:

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

Derefter oprettede jeg en lagret procedure til at udfylde tabellen med 50.000 rækker med en række små strenge:

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

Så skrev jeg opdateringsudsagn formuleret på to måder, som du kunne "undgå" at skrive til en specifik kolonne, givet denne variabeltildeling:

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 ved at bruge et CASE-udtryk til at kontrollere, om værdien i kolonnen er den samme som værdien i variablen:

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

Og for det andet ved at udsende en uafhængig OPDATERING for hver kolonne (hver målrettede kun mod de rækker, hvor denne værdi faktisk var ændret):

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;

Så ville jeg sammenligne dette med den måde, de fleste af os ville gøre dette på i dag:bare OPDATERE alle kolonner uden at være ligeglade med, om det var den allerede eksisterende værdi for den pågældende kolonne:

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

(Disse antager alle, at kolonnerne og parametrene/variablerne ikke er NULL-bare – de skal bruge COALESCE til at tage højde for sammenligning af NULL-værdier på begge sider, hvis det er tilfældet. De antager også, at du vil have en yderligere WHERE-sætning til målrette mod specifikke rækker – i dette eksempel kunne du køre den første og tredje forespørgsel uden den altomfattende WHERE-klausul og se næsten identiske resultater. Jeg holdt dette simpelt for kortheds skyld.)

Så ville jeg se, hvad der sker i disse tre tilfælde, når en værdi kan ændres, når bestemte værdier kan ændres, når ingen værdier vil blive ændret, og når alle værdier vil blive ændret. Jeg kunne påvirke dette ved at ændre den lagrede procedure for at indsætte konstanter i bestemte kolonner eller ved at ændre den måde, variabler blev tildelt på.

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

Resultater

Efter at have kørt disse test, vandt den "blinde opdatering" i hvert enkelt scenarie. Nu tænker du, hvad er et par hundrede millisekunder? Ekstrapoler. Hvis du udfører mange opdateringer i dit system, kan dette virkelig begynde at tage en vejafgift.

Detaljerede resultater i Plan Explorer:Enhver ændring | 2 værdier fritaget | 4 værdier fritaget | Alle værdier undtaget | Alt ændres

Baseret på feedback fra Roji besluttede jeg også at teste dette med et par indekser:

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

Varigheden blev væsentligt forøget med disse indekser:

Detaljerede resultater i Plan Explorer:Enhver ændring | 2 værdier fritaget | 4 værdier fritaget | Alle værdier undtaget | Alt ændres

Konklusion

Ud fra denne test forekommer det mig, at det normalt ikke er værd at tjekke, om en værdi skal opdateres. Hvis din UPDATE-sætning påvirker flere kolonner, er det næsten altid billigere for dig at scanne alle de kolonner, hvor en værdi kan have ændret sig, i stedet for at kontrollere hver kolonne individuelt. I et fremtidigt indlæg vil jeg undersøge, om dette scenarie er parallelt for LOB-kolonner.


  1. Sådan opsætter du databasemail i SQL Server (SSMS)

  2. SQL Server Lock Escalation

  3. DECODE( ) funktion i SQL Server

  4. Beregn MD5-hash af en UTF8-streng