Ogni volta che richiedi SERIALIZABLE
isolamento il DB tenterà di fare in modo che insiemi simultanei di query sembrano essere stati eseguiti in serie in termini di risultati che producono. Questo non è sempre possibile, ad es. quando due transazioni hanno dipendenze reciproche. In questo caso, PostgreSQL interromperà una delle transazioni con un errore di serializzazione che ti dice che dovresti riprovare.
Codice che utilizza SERIALIZABLE
deve essere sempre pronto a ritentare le transazioni. Deve controllare SQLSTATE
e, in caso di errori di serializzazione, ripetere la transazione.
Vedi la documentazione sull'isolamento delle transazioni .
In questo caso, penso che il tuo principale malinteso possa essere questo:
poiché non è niente del genere, è un INSERT ... SELECT
che tocca vo_business.repositoryoperation
sia per la lettura che per la scrittura. Questo è abbastanza per creare una potenziale dipendenza con un'altra transazione che fa lo stesso, o una che legge e scrive nella tabella in un altro modo.
Inoltre, il codice di isolamento serializzabile può in alcune circostanze degenerare in una conservazione delle informazioni sulla dipendenza a livello di blocco per motivi di efficienza. Quindi potrebbe non essere necessariamente una transazione che tocca le stesse righe, ma solo lo stesso blocco di archiviazione, specialmente sotto carico.
PostgreSQL preferirà interrompere una transazione serializzabile se non è sicuro che sia sicura. Il sistema di prova ha dei limiti. Quindi è anche possibile che tu abbia appena trovato un caso che lo inganna.
Per essere sicuro avrei bisogno di vedere entrambe le transazioni fianco a fianco, ma ecco una prova che mostra un insert ... select
può entrare in conflitto con se stesso. Apri tre psql
sessioni ed eseguire:
session0: CREATE TABLE serialdemo(x integer, y integer);
session0: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
session0: LOCK TABLE serialdemo IN ACCESS EXCLUSIVE MODE;
session1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
session2: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
session1: INSERT INTO serialdemo (x, y)
SELECT 1, 2
WHERE NOT EXISTS (SELECT 1 FROM serialdemo WHERE x = 1);
session2: INSERT INTO serialdemo (x, y)
SELECT 1, 2
WHERE NOT EXISTS (SELECT 1 FROM serialdemo WHERE x = 1);
session0: ROLLBACK;
session1: COMMIT;
session2: COMMIT;
session1 si commetterà bene. session2 fallirà con:
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.
Non è lo stesso errore di serializzazione del tuo caso e non dimostra che tuo le istruzioni possono entrare in conflitto tra loro, ma mostra che un insert ... select
non è atomico come pensavi.