Se ho un'istruzione UPDATE che in realtà non modifica alcun dato (perché i dati sono già nello stato aggiornato), c'è qualche vantaggio in termini di prestazioni nel mettere un segno di spunta nella clausola where per impedire l'aggiornamento?
Potrebbe esserci sicuramente una leggera differenza di prestazioni dovuta all'aggiornamento 1 :
- non aggiornando effettivamente alcuna riga (quindi nulla da scrivere sul disco, nemmeno un'attività di registro minima), e
- eliminando blocchi meno restrittivi di quelli necessari per eseguire l'aggiornamento effettivo (quindi meglio per la concorrenza) ( consultare la sezione Aggiornamento verso la fine )
Tuttavia, quanta differenza c'è dovrebbe essere misurata da te sul tuo sistema con il tuo schema, i dati e il carico del sistema. Esistono diversi fattori che influiscono sull'impatto di un AGGIORNAMENTO senza aggiornamento:
- la quantità di contesa sulla tabella in fase di aggiornamento
- il numero di righe da aggiornare
- se ci sono trigger UPDATE sulla tabella in fase di aggiornamento (come notato da Mark in un commento sulla domanda). Se si esegue
UPDATE TableName SET Field1 = Field1
, verrà attivato un trigger di aggiornamento che indicherà che il campo è stato aggiornato (se si controlla utilizzando le funzioni UPDATE () o COLUMNS_UPDATED ) e che il campo in entrambe INSERTED
e nelle DELETED
tabelle ha lo stesso valore.
Inoltre, la seguente sezione di riepilogo si trova nell'articolo di Paul White, L'impatto degli aggiornamenti non di aggiornamento (come notato da @spaghettidba in un commento sulla sua risposta):
SQL Server contiene una serie di ottimizzazioni per evitare la registrazione non necessaria o lo svuotamento della pagina durante l'elaborazione di un'operazione di AGGIORNAMENTO che non comporterà alcuna modifica al database permanente.
- Gli aggiornamenti non aggiornati a una tabella cluster generalmente evitano ulteriori registrazioni e svuotamento delle pagine, a meno che l'operazione di aggiornamento non influisca su una colonna che forma (parte) della chiave del cluster.
- Se una parte della chiave del cluster viene "aggiornata" con lo stesso valore, l'operazione viene registrata come se i dati fossero stati modificati e le pagine interessate fossero contrassegnate come sporche nel pool di buffer. Questa è una conseguenza della conversione di UPDATE in un'operazione di cancellazione-inserimento-inserimento.
- Le tabelle di heap si comportano allo stesso modo delle tabelle di cluster, tranne per il fatto che non dispongono di una chiave di cluster per causare ulteriori registrazioni o svuotamento della pagina. Questo rimane il caso anche quando esiste una chiave primaria non cluster sull'heap. Pertanto, gli aggiornamenti non aggiornati a un heap generalmente evitano il log e lo svuotamento extra (ma vedi sotto).
- Sia le heap sia le tabelle cluster subiranno il log e lo svuotamento extra per qualsiasi riga in cui una colonna LOB contenente più di 8000 byte di dati viene aggiornata allo stesso valore utilizzando qualsiasi sintassi diversa da "SET nome_colonna = nome_colonna".
- La semplice abilitazione di entrambi i tipi di livello di isolamento del controllo delle versioni di riga su un database causa sempre la registrazione e lo svuotamento aggiuntivi. Ciò si verifica indipendentemente dal livello di isolamento in vigore per la transazione di aggiornamento.
Tieni presente (soprattutto se non segui il link per vedere l'articolo completo di Paul), i seguenti due elementi:
Gli aggiornamenti non aggiornati presentano ancora alcune attività di registro, a dimostrazione del fatto che una transazione sta iniziando e terminando. È solo che non si verifica alcuna modifica dei dati (che è ancora un buon risparmio).
Come ho detto sopra, devi testare sul tuo sistema. Usa le stesse query di ricerca utilizzate da Paul e vedi se ottieni gli stessi risultati. Sto vedendo risultati leggermente diversi sul mio sistema rispetto a quelli mostrati nell'articolo. Ancora nessuna pagina sporca da scrivere, ma un po 'più attività di registro.
... Ho bisogno che il conteggio delle righe includa la riga invariata, quindi so se effettuare un inserimento se l'ID non esiste. ... è possibile ottenere il conteggio delle righe di cui ho bisogno in qualche modo?
Semplicisticamente, se hai a che fare con una sola riga, puoi fare quanto segue:
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;
Per più righe, è possibile ottenere le informazioni necessarie per prendere tale decisione utilizzando la OUTPUT
clausola. Catturando esattamente quali righe sono state aggiornate, è possibile restringere gli elementi per cercare la differenza tra non aggiornare le righe che non esistono invece di non aggiornare le righe esistenti ma non è necessario l'aggiornamento.
Mostro l'implementazione di base nella seguente risposta:
Come evitare l'utilizzo della query Unisci quando si esegue l'uperting di più dati utilizzando il parametro xml?
Il metodo mostrato in quella risposta non filtra le righe esistenti ma non è necessario aggiornarle. Quella parte potrebbe essere aggiunta, ma prima devi mostrare esattamente dove stai ottenendo il tuo set di dati in cui ti stai unendo MyTable
. Vengono da un tavolo temporaneo? Un parametro con valori di tabella (TVP)?
AGGIORNAMENTO 1:
Finalmente sono stato in grado di fare alcuni test ed ecco cosa ho trovato per quanto riguarda il registro delle transazioni e il blocco. Innanzitutto, lo schema per la tabella:
CREATE TABLE [dbo].[Test]
(
[ID] [int] NOT NULL CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED,
[StringField] [varchar](500) NULL
);
Successivamente, il test aggiorna il campo al valore che ha già:
UPDATE rt
SET rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM dbo.Test rt
WHERE rt.ID = 4082117
risultati:
-- 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
Infine, il test che filtra l'aggiornamento a causa del valore che non cambia:
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';
risultati:
-- 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
Come puoi vedere, nulla viene scritto nel registro delle transazioni quando si filtra la riga, al contrario delle due voci che segnano l'inizio e la fine della transazione. E mentre è vero che quelle due voci non sono quasi nulla, sono comunque qualcosa.
Inoltre, il blocco delle risorse PAGE e KEY è meno restrittivo quando si filtrano le righe che non sono state modificate. Se nessun altro processo interagisce con questa tabella, probabilmente si tratta di un problema (ma quanto è probabile, davvero?). Tieni presente che i test mostrati in uno qualsiasi dei blog collegati (e persino i miei test) presuppongono implicitamente che non ci siano contese sul tavolo poiché non fanno mai parte dei test. Dire che gli aggiornamenti senza aggiornamento sono così leggeri che non paga per fare il filtraggio deve essere preso con un granello di sale poiché i test sono stati eseguiti, più o meno, nel vuoto. Ma in produzione, molto probabilmente questa tabella non è isolata. Certo, potrebbe benissimo essere che un po 'di registrazione e blocchi più restrittivi non si traducano in meno efficienza. Quindi la fonte di informazioni più affidabile per rispondere a questa domanda? Server SQL. In particolare:il tuo SQL Server. Ti mostrerà quale metodo è migliore per il tuo sistema :-).
AGGIORNAMENTO 2:
Se le operazioni in cui il nuovo valore è uguale al valore corrente (ovvero nessun aggiornamento) numera le operazioni in cui il nuovo valore è diverso e l'aggiornamento è necessario, il modello seguente potrebbe rivelarsi ancora migliore, specialmente se c'è molta contesa sul tavolo. L'idea è di fare un semplice SELECT
primo per ottenere il valore corrente. Se non ottieni un valore, hai la tua risposta per quanto riguarda il INSERT
. Se hai un valore, puoi fare un semplice IF
ed emettere l' UPDATE
unico se è necessario.
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;
risultati:
-- Transaction Log (0 entries):
Operation
----------------------------
-- SQL Profiler (2 Lock:Acquired events):
Mode Type
--------------------------------------
6 - IS 5 - OBJECT
6 - IS 6 - PAGE
Pertanto, vengono acquisiti solo 2 blocchi anziché 3, ed entrambi questi blocchi sono Intent Shared, non Intent eXclusive o Intent Update ( Compatibilità dei blocchi ). Tenendo presente che anche ogni blocco acquisito verrà rilasciato, ogni blocco è in realtà 2 operazioni, quindi questo nuovo metodo è un totale di 4 operazioni anziché le 6 operazioni nel metodo proposto originariamente. Considerando che questa operazione viene eseguita una volta ogni 15 ms (approssimativamente, come indicato dall'OP), ovvero circa 66 volte al secondo. Quindi la proposta originale ammonta a 396 operazioni di blocco / sblocco al secondo, mentre questo nuovo metodo ammonta a solo 264 operazioni di blocco / sblocco al secondo di blocchi anche più leggeri. Questa non è una garanzia di prestazioni eccezionali, ma sicuramente vale la pena testarla :-).