MySQL: transazioni vs tabelle di blocco


110

Sono un po 'confuso con le transazioni rispetto al blocco delle tabelle per garantire l'integrità del database e assicurarmi che SELECT e UPDATE rimangano sincronizzati e nessun'altra connessione interferisca con esso. Ho bisogno di:

SELECT * FROM table WHERE (...) LIMIT 1

if (condition passes) {
   // Update row I got from the select 
   UPDATE table SET column = "value" WHERE (...)

   ... other logic (including INSERT some data) ...
}

Devo assicurarmi che nessun'altra query interferisca e funzioni allo stesso modo SELECT (leggendo il "vecchio valore" prima che la connessione termini l'aggiornamento della riga.

So di poter impostare per impostazione predefinita LOCK TABLES tablesolo per assicurarmi che solo 1 connessione lo stia facendo e sbloccarlo quando ho finito, ma sembra eccessivo. Il wrapping di questo in una transazione farebbe la stessa cosa (assicurandosi che nessun'altra connessione tenti lo stesso processo mentre un'altra è ancora in fase di elaborazione)? O sarebbe SELECT ... FOR UPDATEo SELECT ... LOCK IN SHARE MODEsarebbe meglio?

Risposte:


173

Il blocco delle tabelle impedisce ad altri utenti di DB di influenzare le righe / tabelle che hai bloccato. Ma i blocchi, di per sé, NON assicureranno che la tua logica esca in uno stato coerente.

Pensa a un sistema bancario. Quando paghi una fattura online, ci sono almeno due account interessati dalla transazione: il tuo account, da cui viene prelevato il denaro. E il conto del destinatario, in cui viene trasferito il denaro. E il conto della banca, sul quale depositeranno felicemente tutte le commissioni di servizio addebitate sulla transazione. Dato (come tutti sanno oggigiorno) che le banche sono straordinariamente stupide, diciamo che il loro sistema funziona in questo modo:

$balance = "GET BALANCE FROM your ACCOUNT";
if ($balance < $amount_being_paid) {
    charge_huge_overdraft_fees();
}
$balance = $balance - $amount_being paid;
UPDATE your ACCOUNT SET BALANCE = $balance;

$balance = "GET BALANCE FROM receiver ACCOUNT"
charge_insane_transaction_fee();
$balance = $balance + $amount_being_paid
UPDATE receiver ACCOUNT SET BALANCE = $balance

Ora, senza blocchi e transazioni, questo sistema è vulnerabile a varie condizioni di gara, la più grande delle quali è l'esecuzione di più pagamenti sul tuo account o sull'account del destinatario in parallelo. Mentre il tuo codice ha recuperato il tuo saldo e sta facendo il huge_overdraft_fees () e quant'altro, è del tutto possibile che qualche altro pagamento esegua lo stesso tipo di codice in parallelo. Recupereranno il tuo saldo (diciamo $ 100), faranno le loro transazioni (prendi i $ 20 che stai pagando e i $ 30 con cui ti stanno fregando), e ora entrambi i percorsi del codice hanno due saldi diversi: $ 80 e $ 70. A seconda di quale finisce per ultimo, ti ritroverai con uno di quei due saldi nel tuo account, invece dei $ 50 con cui avresti dovuto finire ($ 100 - $ 20 - $ 30). In questo caso, "errore bancario a tuo favore"

Ora, diciamo che usi i lucchetti. Il pagamento della bolletta ($ 20) colpisce per primo il tubo, quindi vince e blocca il record del tuo account. Ora hai un uso esclusivo e puoi detrarre i $ 20 dal saldo e riscrivere il nuovo saldo in pace ... e il tuo account finisce con $ 80 come previsto. Ma ... uhoh ... provi ad aggiornare l'account del destinatario, ed è bloccato, e bloccato più a lungo di quanto il codice permetta, facendo scadere la tua transazione ... Abbiamo a che fare con banche stupide, quindi invece di avere un errore corretto maneggiando, il codice tira semplicemente un exit()e i tuoi $ 20 svaniscono in uno sbuffo di elettroni. Ora sei fuori $ 20, e devi ancora $ 20 al ricevitore, e il tuo telefono viene recuperato.

Quindi ... inserisci le transazioni. Inizi una transazione, addebiti $ 20 sul tuo conto, provi ad accreditare $ 20 al destinatario ... e qualcosa esplode di nuovo. Ma questa volta, invece di exit(), il codice può semplicemente fare rollback, e puff, i tuoi $ 20 vengono aggiunti magicamente al tuo account.

Alla fine, si riduce a questo:

I blocchi impediscono a chiunque altro di interferire con i record del database con cui hai a che fare. Le transazioni impediscono a qualsiasi errore "successivo" di interferire con le cose "precedenti" che hai fatto. Nessuno dei due da solo può garantire che le cose vadano bene alla fine. Ma insieme lo fanno.

nella lezione di domani: The Joy of Deadlocks.


4
Sono anche / ancora confuso. Supponiamo che l'account del destinatario contenga $ 100 per iniziare e che stiamo aggiungendo il pagamento della fattura di $ 20 dal nostro account. La mia comprensione delle transazioni è che quando iniziano, qualsiasi operazione in-transazione vede il database nello stato in cui si trovava all'inizio della transazione. cioè: finché non lo cambiamo, l'account del destinatario ha $ 100. Quindi ... quando aggiungiamo $ 20, stabiliamo un saldo di $ 120. Ma cosa succede se, durante la nostra transazione, qualcuno ha prosciugato l'account del destinatario a $ 0? Questo è impedito in qualche modo? Riusciranno magicamente a ottenere di nuovo $ 120? È per questo che sono necessarie anche le serrature?
Russ

Sì, è qui che entrano in gioco le serrature. Un sistema appropriato bloccherebbe il record in scrittura in modo che nessun altro possa aggiornare il record mentre la transazione è in corso. Un sistema paranoico metterebbe un blocco incondizionato sul record in modo che nessuno possa leggere nemmeno il saldo "stantio".
Marc B

1
Fondamentalmente guarda le transazioni come proteggere le cose all'interno del tuo percorso di codice. Blocca le cose protette attraverso percorsi di codice "paralleli". Fino a quando i deadlock non hanno raggiunto ...
Marc B

1
@ MarcB, quindi perché dobbiamo eseguire il blocco in modo esplicito se l'utilizzo delle sole transazioni garantisce già che i blocchi sono a posto? Ci sarà anche un caso in cui dovremo eseguire un blocco esplicito perché le transazioni da sole non sono sufficienti?
Pacerier

2
Questa risposta non è corretta e può portare a conclusioni errate. Questa affermazione: "I blocchi impediscono a chiunque altro di interferire con i record di database con cui hai a che fare. Le transazioni impediscono a qualsiasi errore" successivo "di interferire con le cose" precedenti "che hai fatto. Nessuno dei due da solo può garantire che le cose funzionino correttamente nel fine. Ma insieme, lo fanno. " - ti farebbe licenziare, è estremamente sbagliato e stupido Vedi articoli: en.wikipedia.org/wiki/ACID , en.wikipedia.org/wiki/Isolation_(database_systems) e dev.mysql.com/doc/refman/5.1/ it /…
Nikola Svitlica

14

Vuoi una transazione SELECT ... FOR UPDATEo SELECT ... LOCK IN SHARE MODEall'interno di una transazione, come hai detto, poiché normalmente gli SELECT, indipendentemente dal fatto che siano in una transazione o meno, non bloccheranno una tabella. Quale sceglierai dipenderà dal fatto che tu voglia che altre transazioni siano in grado di leggere quella riga mentre la tua transazione è in corso.

http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

START TRANSACTION WITH CONSISTENT SNAPSHOTnon farà il trucco per te, poiché altre transazioni possono ancora arrivare e modificare quella riga. Questo è menzionato proprio nella parte superiore del collegamento sottostante.

Se altre sessioni aggiornano simultaneamente la stessa tabella, [...] potresti vedere la tabella in uno stato che non è mai esistito nel database.

http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html


7

I concetti di transazione e i blocchi sono diversi. Tuttavia, la transazione utilizzava dei blocchi per aiutarla a seguire i principi ACID. Se vuoi che la tabella impedisca ad altri di leggere / scrivere nello stesso momento mentre sei in lettura / scrittura, hai bisogno di un blocco per farlo. Se vuoi assicurarti l'integrità e la coerenza dei dati, è meglio usare le transazioni. Penso concetti misti di livelli di isolamento nelle transazioni con blocchi. Cerca i livelli di isolamento delle transazioni, SERIALIZE dovrebbe essere il livello desiderato.


Questa dovrebbe essere la risposta corretta. Il blocco serve per prevenire le race condition e le transazioni servono per aggiornare più tabelle con dati dipendenti. Due concetti completamente diversi, nonostante le transazioni utilizzino i blocchi.
Blue Water il

6

Ho avuto un problema simile durante il tentativo IF NOT EXISTS ...e l'esecuzione di uno INSERTche ha causato una condizione di competizione quando più thread stavano aggiornando la stessa tabella.

Ho trovato la soluzione al problema qui: Come scrivere le query INSERT IF NOT EXISTS in SQL standard

Mi rendo conto che questo non risponde direttamente alla tua domanda, ma lo stesso principio di eseguire un controllo e inserire come una singola dichiarazione è molto utile; dovresti essere in grado di modificarlo per eseguire l'aggiornamento.


2

Sei confuso con il blocco e la transazione. Sono due cose diverse in RMDB. Il blocco impedisce operazioni simultanee mentre la transazione si concentra sull'isolamento dei dati. Dai un'occhiata a questo fantastico articolo per i chiarimenti e qualche graziosa soluzione.


1
I blocchi impediscono ad altri di interferire con i record su cui stai lavorando descrive ciò che fa in modo succinto e le transazioni impediscono che errori successivi (quelli di altri che apportano modifiche in parallelo) interferiscano con le cose precedenti che hai fatto (consentendo il rollback nel caso in cui qualcuno abbia fatto qualcosa in parallelo) riassume più o meno le transazioni ... cosa c'è di confuso nella sua comprensione di questi argomenti?
steviesama

1

Userei un file

START TRANSACTION WITH CONSISTENT SNAPSHOT;

per cominciare, e a

COMMIT;

per finire con.

Tutto ciò che fai nel mezzo è isolato dagli altri utenti del tuo database se il tuo motore di archiviazione supporta le transazioni (che è InnoDB).


1
Tranne che la tabella da cui sta selezionando non sarà bloccata per altre sessioni a meno che non la blocchi specificamente (o fino a quando non si verifica il suo UPDATE), il che significa che altre sessioni potrebbero venire e modificarlo tra SELECT e UPDATE.
Alison R.

Dopo aver letto su START TRANSACTION WITH CONSISTENT SNAPSHOT nella documentazione di MySQL, non vedo dove effettivamente blocca un'altra connessione dall'aggiornamento della stessa riga. La mia comprensione è che vedrebbe comunque che la tabella è iniziata all'inizio della transazione. Quindi, se un'altra transazione è in corso, ha già ricevuto una riga e sta per aggiornarla, la seconda transazione vedrebbe ancora la riga prima che sia stata aggiornata. Potrebbe quindi potenzialmente provare ad aggiornare la stessa riga che sta per l'altra transazione. È corretto o mi sto perdendo qualcosa in corso?
Ryan

1
@ Ryan Non esegue alcun blocco; hai ragione. Il blocco (o meno) è determinato dal tipo di operazioni eseguite (SELEZIONA / AGGIORNA / ELIMINA).
Alison R.

4
Vedo. Fornisce coerenza di lettura della transazione, ma non impedisce ad altri utenti di modificare una riga appena prima di te.
Martin Schapendonk
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.