PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Più thread possono causare aggiornamenti duplicati su un set vincolato?

Le garanzie dichiarate si applicano in questo caso semplice, ma non necessariamente in query leggermente più complesse. Vedi la fine della risposta per esempi.

Il caso semplice

Supponendo che col1 sia unico, abbia esattamente un valore "2" o abbia un ordinamento stabile, quindi ogni UPDATE corrisponde alle stesse righe nello stesso ordine:

Quello che accadrà per questa query è che i thread troveranno la riga con col=2 e tutti proveranno a catturare un blocco di scrittura su quella tupla. Esattamente uno di loro avrà successo. Gli altri si bloccheranno in attesa del commit della transazione del primo thread.

Quel primo tx scriverà, eseguirà il commit e restituirà un numero di righe pari a 1. Il commit rilascerà il blocco.

Gli altri tx proveranno di nuovo ad afferrare il lucchetto. Uno per uno ci riusciranno. Ogni transazione a sua volta seguirà il seguente processo:

  • Ottieni il blocco di scrittura sulla tupla contestata.
  • Ricontrolla WHERE col=2 condizione dopo aver ottenuto il blocco.
  • Il nuovo controllo mostrerà che la condizione non corrisponde più, quindi UPDATE salterà quella riga.
  • Il UPDATE non ha altre righe quindi riporterà zero righe aggiornate.
  • Commit, rilasciando il blocco per il prossimo tx cercando di impadronirsene.

In questo semplice caso, il blocco a livello di riga e il controllo delle condizioni serializzano efficacemente gli aggiornamenti. Nei casi più complessi, non tanto.

Puoi facilmente dimostrarlo. Apri diciamo quattro sessioni psql. Nella prima, blocca la tabella con BEGIN; LOCK TABLE test; . Nel resto delle sessioni viene eseguito lo stesso UPDATE s - bloccheranno il blocco del livello del tavolo. Ora rilascia il lucchetto con COMMIT la tua prima sessione. Guardali correre. Solo uno riporterà un numero di righe pari a 1, gli altri riporteranno 0. Questo è facilmente automatizzato e programmato per la ripetizione e il ridimensionamento fino a più connessioni/thread.

Per saperne di più, leggi le regole per la scrittura simultanea , pagina 11 di Problemi di concorrenza di PostgreSQL - e poi leggi il resto della presentazione.

E se col1 non è univoco?

Come ha notato Kevin nei commenti, se col non è univoco, quindi potresti abbinare più righe, quindi diverse esecuzioni di UPDATE potrebbe ottenere ordini diversi. Questo può accadere se scelgono piani diversi (diciamo che uno è tramite un PREPARE e EXECUTE e un altro è diretto, o stai pasticciando con enable_ GUC) o se il piano che utilizzano tutti utilizza un tipo instabile di valori uguali. Se ottengono le righe in un ordine diverso, tx1 bloccherà una tupla, tx2 ne bloccherà un'altra, quindi cercheranno ciascuno di ottenere blocchi reciprocamente sulle tuple già bloccate. PostgreSQL ne interromperà uno con un'eccezione deadlock. Questo è un altro buon motivo per cui tutti il codice del database dovrebbe sempre preparati a ritentare le transazioni.

Se stai attento ad assicurarti UPDATE simultaneo Se ottieni sempre le stesse righe nello stesso ordine puoi comunque fare affidamento sul comportamento descritto nella prima parte della risposta.

In modo frustrante, PostgreSQL non offre UPDATE ... ORDER BY quindi assicurarti che i tuoi aggiornamenti selezionino sempre le stesse righe nello stesso ordine non è così semplice come potresti desiderare. A SELECT ... FOR UPDATE ... ORDER BY seguito da un UPDATE separato è spesso il più sicuro.

Query più complesse, sistemi di accodamento

Se stai eseguendo query con più fasi, che coinvolgono più tuple o condizioni diverse dall'uguaglianza, puoi ottenere risultati sorprendenti che differiscono dai risultati di un'esecuzione seriale. In particolare, esecuzioni simultanee di qualsiasi cosa come:

UPDATE test SET col = 1 WHERE col = (SELECT t.col FROM test t ORDER BY t.col LIMIT 1);

o altri tentativi per creare un semplice sistema di "coda" sarà *non* funziona come ti aspetti. Consulta i documenti PostgreSQL sulla concorrenza e questa presentazione per maggiori informazioni.

Se vuoi una coda di lavoro supportata da un database, ci sono soluzioni ben collaudate che gestiscono tutti i casi d'angolo sorprendentemente complicati. Uno dei più popolari è PgQ . C'è un utile documento PgCon sull'argomento e una ricerca su Google per "coda postgresql" è pieno di risultati utili.

BTW, invece di un LOCK TABLE puoi usare SELECT 1 FROM test WHERE col = 2 FOR UPDATE; per ottenere un blocco di scrittura solo su quello su tupla. Ciò bloccherà gli aggiornamenti contro di esso ma non bloccherà le scritture su altre tuple o bloccherà le letture. Ciò ti consente di simulare diversi tipi di problemi di concorrenza.