L'aggiornamento di una riga con lo stesso valore aggiorna effettivamente la riga?


28

Ho una domanda relativa alle prestazioni. Diciamo che ho un utente con il nome Michael. Prendi la seguente query:

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123

La query eseguirà effettivamente l'aggiornamento, anche se viene aggiornato allo stesso valore? In tal caso, come posso impedirlo?


1
Perché dovresti eseguire un'istruzione e contemporaneamente aspettarti che non venga eseguita?
Max Vernon,

@MaxVernon Ruby on Rails 'ORM non aggiorna il record, quindi ero curioso di sapere se PostgreSQL ha fatto la stessa cosa.
OneSneakyMofo,

1
Suggerirei che se Ruby on Rails lo sta facendo, probabilmente sta facendo prima una selezione per vedere se la riga necessita di un aggiornamento.
Max Vernon,

Risposte:


35

A causa del modello MVCC di Postgres e secondo le regole di SQL, UPDATEscrive una nuova versione di riga per ogni riga che non è esclusa nella WHEREclausola.

Questo ha avuto un impatto più o meno sostanziale sulle prestazioni, direttamente e indirettamente. Gli "aggiornamenti vuoti" hanno lo stesso costo per riga di qualsiasi altro aggiornamento. Attivano i trigger (se presenti) come qualsiasi altro aggiornamento, devono essere registrati WAL e producono righe morte che gonfiano la tabella e causano più lavoro in VACUUMseguito come qualsiasi altro aggiornamento.

Le voci degli indici e le colonne TOASTed in cui nessuna delle colonne interessate vengono modificate possono rimanere invariate, ma ciò vale per qualsiasi riga aggiornata. Relazionato:

È quasi sempre una buona idea escludere tali aggiornamenti vuoti (quando esiste un'effettiva possibilità che ciò accada). Non hai fornito una definizione di tabella nella tua domanda (che è sempre una buona idea). Dobbiamo presumere che first_namepossa essere NULL (che non sarebbe sorprendente per un "nome"), quindi la query deve usare il confronto NULL sicuro :

UPDATE users
SET    first_name = 'Michael'
WHERE  id = 123
AND   first_name IS DISTINCT FROM 'Michael';

Se first_name IS NULLprima dell'aggiornamento, un test con solo first_name <> 'Michael'valuterà NULL e come tale escluderebbe la riga dall'aggiornamento. Errore subdolo. Se la colonna è definitaNOT NULL , usa il semplice controllo di uguaglianza, perché è un po 'più economico.

Relazionato:


1
Indexes entries and TOASTed columns where none of the involved columns are changed can stay the sameMa non dovrebbero essere aggiornati per puntare alla nuova posizione della riga?
dvtan,

1
@dtgq: non con gli aggiornamenti HOT, in cui l'indice può continuare a puntare alla posizione precedente e i recuperi di heap devono attraversare la catena HOT per ottenere la tupla dal vivo. Ho aggiunto collegamenti a ulteriori spiegazioni sopra.
Erwin Brandstetter,

1
Che dire di MVCC richiede un aggiornamento noop per scrivere una nuova tupla?
jberryman,

@jberryman: non sono sicuro di aver capito. Ad ogni modo, si prega di porre la domanda come nuova domanda . Puoi sempre collegarti a questo per il contesto. E puoi lasciare un commento qui per ricollegarti (e attirare la mia attenzione).
Erwin Brandstetter,

2
@jberryman: In realtà non conosco i motivi per cui il progetto è andato in questo modo. Ciò è stato stabilito molto tempo fa. Ma suppongo che sarebbe inutilmente costoso controllare l'uguaglianza di ogni riga e avere un percorso di codice separato per le righe invariate. La gestione degli ID delle transazioni sarebbe più complicata - involucro speciale per rollback, gestione delle istantanee, gestione dei blocchi, WAL, e cosa no ...
Erwin Brandstetter

4

Gli ORM come Ruby on Rail offrono l'esecuzione differita che contrassegna un record come modificato (o meno) e quindi, quando necessario o chiamato, quindi invia la modifica al database.

PostgreSQL è un database e non un ORM. Le prestazioni sarebbero ridotte se ci volesse del tempo per verificare se un nuovo valore era uguale al valore aggiornato nella query.

Pertanto aggiornerà il valore indipendentemente dal fatto che sia uguale al nuovo valore o meno.

Se si desidera impedirlo, è possibile utilizzare il codice come suggerito da Max Vernon nella sua risposta.


2

Puoi semplicemente aggiungere alla whereclausola:

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123
    AND (first_name <> 'Michael' OR first_name IS NULL);

Se first_namedefinito come NOT NULL, la OR first_name IS NULLparte può essere rimossa.

La condizione:

(first_name <> 'Michael' OR first_name IS NULL)

può anche essere scritto in modo più elegante come (nella risposta di Erwin):

first_name IS DISTINCT FROM 'Michael'

Non sapendo se la colonna può essere NULL, ciò potrebbe introdurre un bug subdolo.
Erwin Brandstetter,

1
@ErwinBrandstetter stavo aggiornando la risposta - poi ho visto il commento e la tua risposta!
ypercubeᵀᴹ

grazie per la modifica, @ypercube - e per il commento su NULL@erwin
Max Vernon,

1

Dal punto di vista del database

La risposta alla tua domanda è sì. L'aggiornamento avrà luogo. Il database non controlla il valore precedente, imposta solo il nuovo valore.

Poiché ciò accade in memoria (e verrà scritto nei file di dati solo dopo l'emissione di un commit), le prestazioni non sarebbero un problema.

Dal punto di vista ORM

Normalmente avrai un oggetto che rappresenta una singola riga del database (può essere molto più complesso di così, ma manteniamolo semplice). Questo oggetto è gestito in memoria (a livello del server delle app) e solo l'ultima versione di cui è stato eseguito il commit lo farà effettivamente nel database a un certo punto.

Ciò può spiegare il diverso comportamento.

Ora, non confrontiamo una nave mercantile con una stampante 3D. Il fatto che sia possibile inviare stampanti 3D utilizzando navi mercantili non significa che ci possa essere alcun tipo di confronto tra di loro.

Godere!

Spero che questo abbia chiarito alcuni concetti.


4
Le prestazioni sono e problema. Ogni aggiornamento deve essere scritto su disco (il registro e la tabella).
ypercubeᵀᴹ

Dipenderà dall'RDBMS effettivo che usi. Ma molti di loro non eseguono il commit di ogni singolo aggiornamento, ma solo l'ultimo blocco sottoposto a commit che hanno in memoria. Non leggere o scrivere mai una singola riga in un database. Leggi / scrivi blocchi e tienili in memoria fino a quando non devi svuotare per mettere un nuovo blocco nello stesso posto. Durante la memoria, non tutte le modifiche di una riga verranno scritte sul disco, ma solo il contenuto del blocco quando viene segnalato il processo di "scrittura del database" per scaricare quel blocco di memoria in un file di dati. Quindi, no ... Non è un problema a meno che l'applicazione non mantenga il blocco non sottoposto a commit per troppo tempo.
Silvarion,

1
la domanda riguarda Postgres, non qualsiasi DBMS arbitrario. E mentre gli aggiornamenti non devono essere tutti scritti uno per uno, ogni scrittura sul database deve essere scritta nel registro. Se una modifica non viene scritta sull'archiviazione persistente, in che modo il DBMS sopravviverà a un arresto anomalo del sistema?
ypercubeᵀᴹ

Sì, scrive nei registri, anche dalla memoria durante i checkpoint. A meno che tu non abbia un numero enorme di utenti simultanei, non dovrebbe essere affatto un problema. Anche i log sono scritti in batch. Penso che stiamo parlando di server. Se stai parlando di un database Postgres in un laptop con un HDD da 5400 RPM, sì ... avrai sempre problemi di prestazioni. Quindi, la risposta finale sarebbe la prima ... Dipende da troppe cose.
Silvarion,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.