Errore PostgreSQL: could not serialize access
ERROR: could not serialize access due to concurrent update
-- oppure, a livello Serializable:
ERROR: could not serialize access due to read/write dependencies among transactions
DETAIL: Reason code: Canceled on identification as a pivot, during commit attempt.
HINT: The transaction might succeed if retried.
Questo errore esiste solo ai livelli di isolamento Repeatable Read e Serializable, ed è quei livelli che lavorano come previsto: PostgreSQL non è riuscito a produrre un risultato coerente con le garanzie che hai richiesto, quindi ha interrotto la tua transazione invece di restituire silenziosamente qualcosa di sbagliato.
Cosa significa questo errore
Le due varianti corrispondono ai due livelli:
- "due to concurrent update" (Repeatable Read): la tua transazione ha provato a modificare una riga che un'altra transazione ha cambiato e committato dopo che il tuo snapshot è stato preso. PostgreSQL non può applicare la tua scrittura a dati che non hai mai visto, quindi ti interrompe.
- "due to read/write dependencies among transactions" (Serializable): il meccanismo SSI ha trovato un pattern di letture e scritture tra transazioni concorrenti che non poteva corrispondere ad alcun ordine di esecuzione una-alla-volta. Nota la frase nella documentazione e nella pratica: Serializable può interrompere le transazioni preventivamente — occasionali falsi positivi fanno parte del gioco.
In entrambi i casi la transazione è stata sottoposta a rollback e, come dice l'HINT, avrebbe probabilmente successo se semplicemente eseguita di nuovo. Lo SQLSTATE è 40001 in entrambi i casi.
Cause comuni
- Girare a Repeatable Read o Serializable con writer concorrenti sulle stesse righe — righe calde e contatori sono i punti di collisione tipici.
- Transazioni lunghe: più vecchio è il tuo snapshot, più è probabile che qualcuno abbia committato un cambiamento in conflitto nel frattempo.
- A Serializable, query che leggono ampiamente: uno scan sequenziale prende un predicate lock sull'intera tabella, quindi entra in conflitto con qualsiasi scrittura concorrente su quella tabella, rilevante o no.
- Un ORM o framework che imposta silenziosamente il livello di isolamento — i team a volte scoprono di aver girato a Repeatable Read solo quando questo errore compare sotto carico.
Come diagnosticarlo
- Conferma a quale livello stai effettivamente girando:
SHOW default_transaction_isolation;e controlla se l'applicazione lo imposta per transazione. - Logga e conta le occorrenze di SQLSTATE
40001: un basso tasso di fondo sotto Serializable è normale e sano; un picco legato a un endpoint identifica il carico che collide. - Identifica i due lati del conflitto dai log applicativi (quali transazioni toccano le stesse righe o tabelle intorno al momento del fallimento). I reason code nel
DETAILdell'errore sono utili soprattutto a confermare che è l'SSI all'opera, non a puntare al peer.
Come risolverlo
- Riprova l'intera transazione su SQLSTATE
40001— incluse le sue letture, dato che i valori letti la prima volta sono esattamente ciò che può essere cambiato. Tentativi limitati, piccolo backoff, nessun side effect (email, chiamate HTTP) dentro il blocco riprovato. - Tieni le transazioni brevi e tocca meno righe possibile.
- A Serializable, fai usare gli indici alle letture: gli index scan precisi prendono predicate lock stretti e collidono molto meno degli scan sequenziali.
- Se un punto caldo genera continui retry, valuta di gestire quel singolo percorso con Read Committed più locking esplicito (
SELECT ... FOR UPDATE) — la contesa si risolve aspettando invece che interrompendo.
🔍
Lettura correlata: Livelli di isolamento delle transazioni in pratica — cosa garantisce ciascun livello, il write skew e un'intera sezione sul riprovare correttamente.
