2014-07-02 15 views
5

Quando si utilizza una transazione di database per raggruppare più aggiornamenti, dovrei includere anche SELECT all'interno della transazione? Ad esempio, consente di dire che:Devo includere SELECT in una transazione?

  1. ottenere un record
  2. controllo delle autorizzazioni di modifica per il record, utilizzando i dati dal record
  3. aggiornamento alcuni record
  4. aggiornamento alcuni altri record

Devo iniziare la transazione prima della fase "ottieni un record" o solo attorno agli aggiornamenti?

Sto utilizzando Postgres/Django transaction.atomic() ma non credo che importi qui.

risposta

4

La versione breve: "Dipende".

La versione lunga:

Se stai facendo un ciclo di lettura-modifica-scrittura, quindi non solo deve essere in una transazione, ma è necessario SELECT ... FOR UPDATE tutti i record più tardi intende modificare. Altrimenti rischierai di perdere le scritture, in cui sovrascrivi un aggiornamento effettuato da qualcun altro durante la lettura del record e quando hai scritto l'aggiornamento.

SERIALIZABLE isolamento della transazione può anche aiutare con questo.

Hai davvero bisogno di capire la concorrenza e l'isolamento. Sfortunatamente l'unica semplice, facile risposta "basta fare X" senza comprenderla è iniziare ogni transazione bloccando tutte le tabelle coinvolte. La maggior parte delle persone non vuole farlo.

Suggerisco una lettura (o due, o tre o quattro - è materiale duro) di the tx isolation docs. Sperimentare sessioni simultanee psql (più terminali) per creare condizioni di competizione e conflitti.

+1

Una nota per gli utenti di Django: 'SELECT ... PER UPDATE' è disponibile tramite Django's select_for_update' (https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.select_for_update) –

+1

@ScottStafford ... ma sfortunatamente 'SELECT ... FOR SHARE', o PostgreSQL 'SELECT ... FOR KEY SHARE' /' FOR KEY UPDATE'. –

+0

Quindi, per assicurarmi di seguire l'angolo di Django, una chiamata come "MyObject.objects.get (pk = 5)' non bloccherà nulla se all'interno di un 'transaction.atomic()' o no. Per fare questo ho bisogno di essere entrambi all'interno di un 'transaction.atomic()' E usare un modulo come 'MyObject.objects.select_for_update(). Get (pk = 5)', per assicurarmi che MyObject 5 non possa essere cambiato fino a dopo la mia transazione finisce. –

1

Idealmente (se possibile) si farebbe tutti i quattro passaggi in una singola data-modifying CTE (che avviene automaticamente all'interno di una singola transazione).

Ciò non esclude ancora le condizioni di gara, ma le rende molto improbabili, perché il periodo di tempo compreso tra SELECT .. FOR UPDATE e un successivo UPDATE è ridotto al minimo. (Sì, è comunque necessario utilizzare FOR UPDATE (or another appropriate locking level) per contrastare le condizioni di gara in caso di accesso concomitante.)

Questo non è l'approccio tipico (inefficiente) di un framework web come Django. Ma è l'approccio superiore. Ottimizza le prestazioni in diversi modi:

  • round trip Meno al server db (probabilmente più importante)
  • minimizzare i tempi di blocco
  • accettano Postgres per ottimizzare le query

Quando si utilizza SELECT .. FOR UPDATE in un CTE che modifica i dati, tenere presente che unreferenced CTEs are not executed at all, che non consente di bloccare le righe come previsto.

esempi di codice per CTE dati modificanti:

There are many more on SO. Try a seach.

+0

Molto interessante. Nel mio caso, posso permettermi più viaggi al DB in cambio della manutenibilità del codice e mantenere la logica di autorizzazione in un posto nel codice. Sto cercando la correttezza della transazione, a dispetto, i giochi che l'ORM gioca, qui. Inoltre, i CTE mi spaventano. –

+0

@ScottStafford: le funzioni lato server SQL o PL/pgSQL possono essere alternative meno spaventose con vantaggi simili ... –

Problemi correlati