Come implementare correttamente il blocco ottimistico in MySQL


13

Come si implementa correttamente il blocco ottimistico in MySQL?

Il nostro team ha dedotto che dobbiamo fare il n. 4 di seguito, altrimenti esiste il rischio che un altro thread possa aggiornare la stessa versione del record, ma vorremmo confermare che questo è il modo migliore per farlo.

  1. Creare un campo versione sulla tabella che si desidera utilizzare il blocco ottimistico, ad esempio nome colonna = "versione"
  2. Su selezioni, assicurarsi di includere la colonna della versione e prendere nota della versione
  3. In un successivo aggiornamento del record, l'istruzione update dovrebbe emettere "dove versione = X" dove X è la versione che abbiamo ricevuto in # 2 e impostare il campo della versione durante tale istruzione di aggiornamento su X + 1
  4. Eseguire un SELECT FOR UPDATErecord che stiamo per aggiornare in modo da serializzare chi può apportare modifiche al record che stiamo tentando di aggiornare.

Per chiarire, stiamo cercando di impedire a due thread che selezionano lo stesso record nella stessa finestra di tempo in cui prendono la stessa versione del record di sovrascriversi a vicenda se provano ad aggiornare il record allo stesso tempo. Riteniamo che, a meno che non facciamo il n. 4, esiste la possibilità che se entrambi i thread immettono le rispettive transazioni contemporaneamente (ma non hanno ancora emesso i loro aggiornamenti), quando passano all'aggiornamento, il secondo thread che utilizzerà UPDATE ... dove version = X opererà su vecchi dati.

Siamo corretti nel pensare che dobbiamo fare questo blocco pessimistico durante l'aggiornamento anche se stiamo usando i campi versione / blocco ottimistico?


Qual è il problema? Aumenta il numero di versione con il tuo AGGIORNAMENTO, quindi il secondo AGGIORNAMENTO fallirà perché il numero della versione non è lo stesso di quando è stato letto - che è quello che vuoi.
AndreKR,

Sei sicuro? Non è chiaro che a meno che non si imposti il ​​livello di isolamento della transazione su un'impostazione specifica che si vedrebbero effettivamente gli altri thread di aggiornamento. Se entrambi immettete la transazione contemporaneamente, il secondo thread può benissimo vedere i VECCHI dati quando va a fare l'aggiornamento. MySQL non è così robusto nell'arena ACID come dice Oracle, quindi cerca il modo migliore per implementare il blocco ottimistico in MySQL che impedirà letture / aggiornamenti sporchi.
BestPractices,

Ma poi la transazione fallirà comunque durante il commit, giusto?
AndreKR,

Le indicazioni sono che si vorrebbe fare una selezione per l'aggiornamento per far fronte a questa situazione: dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html
BestPractices

@BestPractices È necessario uno SELECT ... FOR UPDATE o blocco ottimistico tramite il controllo delle versioni delle righe, non entrambi. Vedi i dettagli nella risposta.
Craig Ringer,

Risposte:


17

Il tuo sviluppatore si sbaglia. È necessario il controllo versione SELECT ... FOR UPDATE o riga, non entrambi.

Provalo e vedi. Aperte tre sessioni di MySQL (A), (B)e (C)allo stesso database.

In (C)questione:

CREATE TABLE test(
    id integer PRIMARY KEY,
    data varchar(255) not null,
    version integer not null
);
INSERT INTO test(id,data,version) VALUES (1,'fred',0);
BEGIN;
LOCK TABLES test WRITE;

In entrambi (A)e (B)pubblica un UPDATEtest che imposta e imposta la versione della riga, modificando il winnertesto in ciascuno in modo da poter vedere quale sessione è quale:

-- In (A):

BEGIN;
UPDATE test SET data = 'winnerA',
            version = version + 1
WHERE id = 1 AND version = 0;

-- in (B):

BEGIN;
UPDATE test SET data = 'winnerB',
            version = version + 1
WHERE id = 1 AND version = 0;

Ora dentro (C), UNLOCK TABLES;per rilasciare il blocco.

(A)e (B)correrà per il blocco delle file. Uno di loro vincerà e otterrà il lucchetto. L'altro si bloccherà sulla serratura. Il vincitore che ha ottenuto il blocco procederà a cambiare la riga. Supponendo che (A)sia il vincitore, ora puoi vedere la riga modificata (ancora non impegnata, quindi non visibile ad altre transazioni) con a SELECT * FROM test WHERE id = 1.

Ora COMMITnella sessione del vincitore, diciamo (A).

(B)otterrà il blocco e procederà con l'aggiornamento. Tuttavia, la versione non corrisponde più, quindi non cambierà righe, come riportato dal risultato del conteggio delle righe. Solo uno ha UPDATEavuto alcun effetto e l'applicazione client può vedere chiaramente quale è UPDATEriuscita e quale non è riuscita. Non sono necessari ulteriori blocchi.

Vedi i log delle sessioni su pastebin qui . Ho usato mysql --prompt="A> "ecc per rendere facile capire la differenza tra le sessioni. Ho copiato e incollato l'output interlacciato in sequenza temporale, quindi non è un output completamente grezzo ed è possibile che avrei potuto fare errori copiandolo e incollandolo. Provalo tu stesso per vedere.


Se tu avessi non aggiunto un campo versione di riga, allora si avrebbe bisogno di SELECT ... FOR UPDATEessere in grado di garantire in modo affidabile l'ordinazione.

Se ci pensate, a SELECT ... FOR UPDATEè completamente ridondante se lo state facendo immediatamente UPDATEsenza riutilizzare i dati dal SELECT, o se state utilizzando il controllo delle versioni delle righe. L' UPDATEavrà un blocco in ogni caso. Se qualcun altro aggiorna la riga tra la tua lettura e la successiva, la tua versione non corrisponderà più, quindi l'aggiornamento non riuscirà. Ecco come funziona il blocco ottimistico.

Lo scopo di SELECT ... FOR UPDATEè:

  • Gestire l'ordinamento dei blocchi per evitare deadlock; e
  • Per estendere l'intervallo di un blocco di riga per quando si desidera leggere i dati da una riga, modificarli nell'applicazione e scrivere una nuova riga basata su quella originale senza dover utilizzare l' SERIALIZABLEisolamento o il controllo delle versioni delle righe.

Non è necessario utilizzare sia il blocco ottimistico (versione delle righe) sia SELECT ... FOR UPDATE. Usa l'uno o l'altro.


Grazie Craig. Avevi ragione-- lo sviluppatore si era sbagliato. Grazie per aver eseguito questo test.
BestPractices,

Che dire di SQL Server? Esiste sempre un blocco acquisito nella riga aggiornata indipendentemente dal livello di isolamento della transazione?
Plalx,

@plalx Bene, cosa dice la documentazione? Cosa succede se si esegue un test interattivo proprio come questo?
Craig Ringer,

@CraigRinger, cosa succederà se B ottiene il blocco prima di un commit ma dopo un aggiornamento?
MengT

1
@MengT Non può, ecco perché è un lucchetto.
Craig Ringer,

0
UPDATE tbl SET owner = $me,
               id = LAST_INSERT_ID(id)
    WHERE owner = ''
    LIMIT 1;
$id = SELECT LAST_INSERT_ID();
Do some stuff (arbitrarily long time)...;
UPDATE  tbl SET owner = '' WHERE id = $id;

Nessun blocco (non tabella, non transazione) necessario o addirittura desiderato:

  • L'aggiornamento è atomico
  • LAST_INSERT_ID () è specifico della sessione, quindi sicuro per i thread.
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.