Mat ed Erwin hanno entrambi ragione, e sto solo aggiungendo un'altra risposta per espandere ulteriormente ciò che hanno detto in un modo che non si adatta a un commento. Dal momento che le loro risposte non sembrano soddisfare tutti, e c'è stato un suggerimento che gli sviluppatori PostgreSQL dovrebbero essere consultati, e io sono uno, lo elaborerò.
Il punto importante qui è che sotto lo standard SQL, all'interno di una transazione in esecuzione a READ COMMITTED
livello di isolamento della transazione, la restrizione è che il lavoro delle transazioni senza commit non deve essere visibile. Quando il lavoro delle transazioni impegnate diventa visibile dipende dall'implementazione. Quello che stai sottolineando è una differenza nel modo in cui due prodotti hanno scelto di implementarlo. Nessuna implementazione viola i requisiti della norma.
Ecco cosa succede in PostgreSQL, in dettaglio:
Esecuzioni S1-1 (1 riga eliminata)
La vecchia riga viene lasciata in posizione, perché S1 potrebbe ancora tornare indietro, ma S1 ora mantiene un blocco sulla riga in modo che qualsiasi altra sessione che tenti di modificare la riga attenda per vedere se S1 esegue il commit o il rollback. Qualsiasi lettura della tabella può ancora vedere la vecchia riga, a meno che non tenti di bloccarla con SELECT FOR UPDATE
o SELECT FOR SHARE
.
S2-1 funziona (ma è bloccato poiché S1 ha un blocco in scrittura)
S2 ora deve aspettare per vedere il risultato di S1. Se S1 dovesse eseguire il rollback anziché il commit, S2 eliminerebbe la riga. Si noti che se S1 avesse inserito una nuova versione prima del rollback, la nuova versione non sarebbe mai stata lì dal punto di vista di qualsiasi altra transazione, né la vecchia versione sarebbe stata eliminata dal punto di vista di qualsiasi altra transazione.
Percorsi S1-2 (1 riga inserita)
Questa riga è indipendente da quella precedente. Se ci fosse stato un aggiornamento della riga con id = 1, le versioni vecchie e nuove sarebbero state correlate e S2 potrebbe eliminare la versione aggiornata della riga quando è stata sbloccata. Il fatto che una nuova riga abbia gli stessi valori di alcune righe esistenti in passato non lo rende uguale a una versione aggiornata di quella riga.
S1-3 funziona, rilasciando il blocco di scrittura
Quindi i cambiamenti di S1 sono persistenti. Una riga è sparita. È stata aggiunta una riga.
S2-1 funziona, ora che può ottenere il blocco. Ma segnala 0 righe eliminate. HUH ???
Ciò che accade internamente è che c'è un puntatore da una versione di una riga alla versione successiva di quella stessa riga se viene aggiornata. Se la riga viene eliminata, non esiste una versione successiva. Quando una READ COMMITTED
transazione si risveglia da un blocco in un conflitto di scrittura, segue quella catena di aggiornamento fino alla fine; se la riga non è stata eliminata e se soddisfa ancora i criteri di selezione della query verrà elaborata. Questa riga è stata eliminata, quindi la query di S2 va avanti.
S2 può o meno accedere alla nuova riga durante la scansione della tabella. In tal caso, vedrà che la nuova riga è stata creata dopo l' DELETE
avvio dell'istruzione S2 e quindi non fa parte dell'insieme di righe visibili ad essa.
Se PostgreSQL dovesse riavviare l'intera istruzione DELETE di S2 dall'inizio con una nuova istantanea, si comporterebbe come SQL Server. La comunità PostgreSQL non ha scelto di farlo per motivi di prestazioni. In questo semplice caso non noteresti mai la differenza nelle prestazioni, ma se eri in dieci milioni di righe in un DELETE
blocco, lo faresti sicuramente. C'è un compromesso qui in cui PostgreSQL ha scelto le prestazioni, poiché la versione più veloce è ancora conforme ai requisiti dello standard.
S2-2 funziona, segnala una violazione del vincolo chiave unica
Certo, la fila esiste già. Questa è la parte meno sorprendente dell'immagine.
Mentre qui c'è un comportamento sorprendente, tutto è conforme allo standard SQL e nei limiti di ciò che è "specifico dell'implementazione" secondo lo standard. Può certamente sorprendere se si presume che il comportamento di qualche altra implementazione sarà presente in tutte le implementazioni, ma PostgreSQL si impegna molto per evitare errori di serializzazione nel READ COMMITTED
livello di isolamento e consente alcuni comportamenti che differiscono da altri prodotti per raggiungere questo obiettivo.
Ora, personalmente non sono un grande fan del READ COMMITTED
livello di isolamento delle transazioni nell'implementazione di qualsiasi prodotto. Tutti consentono alle condizioni di gara di creare comportamenti sorprendenti dal punto di vista transazionale. Una volta che qualcuno si abitua ai comportamenti strani consentiti da un prodotto, tende a considerare quello "normale" e gli scambi scelti da un altro prodotto strano. Ma ogni prodotto deve fare una sorta di compromesso per qualsiasi modalità non effettivamente implementata come SERIALIZABLE
. Dove gli sviluppatori di PostgreSQL hanno scelto di tracciare la linea READ COMMITTED
è minimizzare il blocco (le letture non bloccano le scritture e le scritture non bloccano le letture) e minimizzare la possibilità di errori di serializzazione.
Lo standard richiede che le SERIALIZABLE
transazioni siano predefinite, ma la maggior parte dei prodotti non lo fa perché causa un calo delle prestazioni rispetto ai livelli di isolamento delle transazioni più lassisti. Alcuni prodotti non forniscono nemmeno transazioni realmente serializzabili quando SERIALIZABLE
viene scelto, in particolare Oracle e versioni di PostgreSQL precedenti alla 9.1. Ma utilizzare le SERIALIZABLE
transazioni autentiche è l'unico modo per evitare effetti sorprendenti dalle condizioni di gara e le SERIALIZABLE
transazioni devono sempre bloccare per evitare le condizioni di gara o ripristinare alcune transazioni per evitare lo sviluppo di condizioni di gara. L'implementazione più comune delle SERIALIZABLE
transazioni è il rigoroso blocco a due fasi (S2PL) che presenta sia errori di blocco che di serializzazione (sotto forma di deadlock).
Informativa completa: ho collaborato con Dan Ports del MIT per aggiungere transazioni veramente serializzabili a PostgreSQL versione 9.1 usando una nuova tecnica chiamata Serializable Snapshot Isolation.