2012-06-07 11 views
77

Per favore aiutami a capire il caso d'uso dietro SELECT ... FOR UPDATE.Quando utilizzare SELECT ... FOR UPDATE?

Domanda 1: Il seguente è un buon esempio di quando è necessario utilizzare SELECT ... FOR UPDATE?

Attribuite:

  • camere [id]
  • tag [id, nome]
  • room_tags [room_id, tag_id]
    • room_id e tag_id sono le chiavi esterne

L'applicazione desidera elencare tutte le stanze e i relativi tag, ma deve distinguere tra stanze senza tag e stanze rimosse. Se SELECT ... FOR UPDATE non viene utilizzato, ciò che potrebbe accadere è:

  • Inizialmente:
    • camere contiene [id = 1]
    • tag contiene [id = 1, name = 'cats']
    • room_tags contiene [room_id = 1, tag_id = 1]
  • Thread 1: SELECT id FROM rooms;
    • returns [id = 1]
  • filettatura 2: DELETE FROM room_tags WHERE room_id = 1;
  • filettatura 2: DELETE FROM rooms WHERE id = 1;
  • filettatura 2: [commit della transazione]
  • filettatura 1: SELECT tags.name FROM room_tags, tags WHERE room_tags.tag_id = 1 AND tags.id = room_tags.tag_id;
    • restituisce una lista vuota

Ora Thread 1 ritiene che la stanza 1 non abbia tag, ma in realtà la stanza è stata rimossa. Per risolvere questo problema, Thread 1 dovrebbe SELECT id FROM rooms FOR UPDATE, impedendo in tal modo Thread 2 dall'eliminazione da rooms fino all'applicazione Thread 1. È corretto?

Domanda 2: Quando si dovrebbe usare SERIALIZABLE isolamento delle transazioni rispetto READ_COMMITTED con SELECT ... FOR UPDATE?

Le risposte dovrebbero essere portatili (non specifiche del database). Se ciò non è possibile, ti preghiamo di spiegarne il motivo.

+2

Quali RDBMS stai utilizzando? – Quassnoi

+0

@Quassnoi, come menzionato in fondo alla domanda, sto cercando una soluzione portatile (non specifica per database). – Gili

+1

Le opzioni 'REPEATABLE_READ' e' READ_COMMITTED' sono anche opzioni portatili? Gli unici risultati che ottengo per quelli sono per il server MSSQL –

risposta

57

L'unico modo portatile per ottenere coerenza tra stanze e tag e assicurarsi che le stanze non vengano mai restituite dopo essere state cancellate è bloccarle con SELECT FOR UPDATE.

Tuttavia in alcuni sistemi il blocco è un effetto collaterale del controllo della concorrenza e si ottengono gli stessi risultati senza specificare esplicitamente FOR UPDATE.


Per risolvere questo problema, Thread 1 dovrebbe SELECT id FROM rooms FOR UPDATE, impedendo così filettatura 2 elimini da rooms fino filettatura 1 è fatto. È corretto?

Questo dipende dal controllo della concorrenza utilizzato dal sistema di database.

  • MyISAM in MySQL (e diversi altri sistemi vecchi) fa bloccare l'intera tabella per la durata di una query.

  • In SQL Server, SELECT query nel luogo blocchi condivisi sui record/pagine/tavoli hanno esaminato, mentre le serrature DML query luogo di aggiornamento (che in seguito vengono promossi ad esclusivo o retrocesso a blocchi condivisi). I blocchi esclusivi non sono compatibili con i blocchi condivisi, pertanto la query SELECT o DELETE si bloccherà fino a quando non si commetterà un'altra sessione.

  • Nei database che utilizzano MVCC (come Oracle, PostgreSQL, MySQL con InnoDB), una query DML crea una copia del record (in un modo o nell'altro) e in generale i lettori non bloccano gli scrittori e viceversa. Per questi database, un SELECT FOR UPDATE sarebbe utile: bloccherebbe o la query DELETE fino a quando non si commetterà un'altra sessione, proprio come fa SQL Server.

Quando si dovrebbe usare REPEATABLE_READ isolamento delle transazioni rispetto READ_COMMITTED con SELECT ... FOR UPDATE?

In generale, REPEATABLE READ non proibisce le righe fantasma (righe che apparivano o scomparivano in un'altra transazione, piuttosto che essere modificato)

  • In Oracle e precedenti PostgreSQL versioni, REPEATABLE READ è in realtà sinonimo di SERIALIZABLE . Fondamentalmente, questo significa che la transazione non vede le modifiche apportate dopo che è stata avviata. Quindi, in questa configurazione, l'ultima query Thread 1 restituirà la stanza come se non fosse mai stata eliminata (che potrebbe essere o non essere ciò che volevi). Se non si desidera visualizzare le camere dopo che sono stati eliminati, si dovrebbe bloccare le righe con SELECT FOR UPDATE

  • In InnoDB, REPEATABLE READ e SERIALIZABLE sono cose diverse: lettori in modalità SERIALIZABLE set serrature di nuova chiave sui record valutano, prevenendo efficacemente il contemporaneo DML su di essi. Quindi non hai bisogno di un SELECT FOR UPDATE in modalità serializzabile, ma ne hai bisogno in REPEATABLE READ o READ COMMITED.

Si noti che la norma sulla modalità di isolamento non prescrive che non si vede certi capricci nelle query, ma non definisce come (con bloccaggio o con MVCC o altro).

Quando dico "non è necessario SELECT FOR UPDATE" avrei dovuto aggiungere "a causa degli effetti collaterali di alcune implementazioni del motore di database".

+1

L'ultimo punto è il nocciolo della questione, penso: "non hai bisogno di SELECT FOR UPDATE in modalità serializzabile, ma ne hai bisogno in REPEATABLE READ o READ COMMITED". –

+0

Hai ragione. La seconda domanda dovrebbe essere posta quando si deve usare 'SERIALIZABLE' contro' READ_COMMITTED' con 'SELECT ... FOR UPDATE'. Puoi aggiornare la tua risposta per riflettere questa domanda aggiornata? – Gili

+0

@Gili: "non è necessario un 'SELECT FOR UPDATE' in modalità serializzabile, con' InnoDB'. Con gli altri sistemi 'MVCC', i due sono sinonimi e devi' SELECT FOR UPDATE'. – Quassnoi

14

risposte brevi:

Q1: Sì.

Q2: Non importa quale usi.

Risposta lunga:

A select ... for update sarà (in quanto implica) selezionare alcune righe, ma anche bloccare loro come se sono già stati aggiornati dalla transazione corrente (o come se l'aggiornamento di identità era stata eseguita). Ciò consente di aggiornarli nuovamente nella transazione corrente e quindi eseguire il commit, senza che un'altra transazione possa modificare queste righe in alcun modo.

Un altro modo di vedere le cose, è come se le seguenti due istruzioni vengono eseguite atomicamente:

select * from my_table where my_condition; 

update my_table set my_column = my_column where my_condition; 

Dal momento che le righe interessate da my_condition sono bloccati, nessun altra operazione li possono modificare in alcun modo, e quindi, il livello di isolamento della transazione non fa differenza qui.

Si noti inoltre che il livello di isolamento della transazione è indipendente dal blocco: l'impostazione di un diverso livello di isolamento non consente di aggirare e bloccare le righe in una transazione diversa bloccata dalla transazione.

I livelli di isolamento delle transazioni garantiscono (a diversi livelli) la coerenza dei dati durante le transazioni in corso.

+1

Penso che i livelli di isolamento delle transazioni garantiscano [...] la coerenza dei dati una volta completate le transazioni. In modo non corretto implica che i livelli di isolamento non influiscano su ciò che accade * durante * una transazione. Raccomando di rivedere questa sezione e fornire maggiori dettagli su come influiscono su ciò che vedi (o che non vedi) durante una transazione. – Gili

+0

Trovo che il tuo post risponda alle mie domande specifiche meglio di [Quassnoi's] (http://stackoverflow.com/a/16425603/14731) ma apprezzo tutti i riferimenti che ha fornito. Accetterò una risposta che combini al meglio le due (risposte specifiche in alto, riferimenti di supporto di seguito). – Gili

+0

Il blocco e l'isolamento sono intercambiabilmente complicati. Quindi ci sono libri per ottenere la conoscenza al riguardo? – Chao

Problemi correlati