Credo che questo sia davvero un difetto di progettazione, sebbene non specifico di SQL Server 2016, poiché tutte le altre implementazioni esistenti delle tabelle temporali (per quanto ne so) hanno lo stesso difetto. I problemi che possono sorgere con le tabelle temporali per questo motivo sono abbastanza gravi; lo scenario nel tuo esempio è lieve rispetto a ciò che può andare storto in generale:
Riferimenti a chiave esterna non funzionanti : supponiamo di avere due tabelle temporali, con la tabella A con un riferimento a chiave esterna alla tabella B. Ora diciamo che abbiamo due transazioni, entrambe in esecuzione a un livello di isolamento READ COMMITTED: la transazione 1 inizia prima della transazione 2, transazione 2 inserisce una riga nella tabella B e esegue il commit, quindi la transazione 1 inserisce una riga nella tabella A con un riferimento alla riga appena aggiunta di B. Poiché l'aggiunta della nuova riga a B è già stata impegnata, il vincolo di chiave esterna viene soddisfatto e la transazione 1 è in grado di impegnarsi correttamente. Tuttavia, se dovessimo visualizzare il database "AS OF" tra qualche tempo tra l'inizio della transazione 1 e l'inizio della transazione 2, vedremmo la tabella A con un riferimento a una riga di B che non esiste. Quindi in questo caso,la tabella temporale fornisce una vista incoerente del database . Questo ovviamente non era l'intento dello standard SQL: 2011, che afferma che
Le righe di sistema storiche in una tabella con versione di sistema formano istantanee immutabili del passato. Eventuali vincoli in vigore al momento della creazione di una riga di sistema storica sarebbero già stati controllati quando quella riga era una riga di sistema corrente, quindi non è mai necessario imporre vincoli su righe di sistema storiche.
Chiavi primarie non univoche : supponiamo di avere una tabella con una chiave primaria e due transazioni, entrambe a un livello di isolamento READ COMMITTED, in cui si verifica quanto segue: Dopo l'inizio della transazione 1 ma prima che tocchi questa tabella, la transazione 2 elimina un determinato riga della tabella e si impegna. Quindi, la transazione 1 inserisce una nuova riga con la stessa chiave primaria di quella che è stata eliminata. Questo va bene, ma quando guardiamo la tabella AL MOMENTO tra l'inizio della transazione 1 e l'inizio della transazione 2, vedremo due righe con la stessa chiave primaria.
Errori sugli aggiornamenti simultanei : supponiamo di avere una tabella e due transazioni che aggiornano entrambe la stessa riga, sempre a un livello di isolamento READ COMMITTED. La transazione 1 inizia per prima, ma la transazione 2 è la prima ad aggiornare la riga. La transazione 2 quindi esegue il commit e la transazione 1 esegue un aggiornamento diverso sulla riga e esegue il commit. Va tutto bene, tranne che se si tratta di una tabella temporale, dopo l'esecuzione dell'aggiornamento nella transazione 1 quando il sistema va a inserire la riga richiesta nella tabella della cronologia, SysStartTime generato sarà l'ora di inizio della transazione 2, mentre SysEndTime sarà l'ora di inizio della transazione 1, che non è un intervallo di tempo valido poiché SysEndTime sarebbe precedente a SysStartTime. In questo caso, SQL Server genera un errore e ripristina la transazione (ad esempio, vederequesta discussione ). Questo è molto spiacevole, dal momento che a livello di isolamento READ COMMITTED non ci si aspetterebbe che problemi di concorrenza porterebbero a veri e propri guasti, il che significa che le applicazioni non saranno necessariamente preparate a tentare di riprovare. In particolare, ciò è contrario a una "garanzia" nella documentazione di Microsoft:
Questo comportamento garantisce che le applicazioni legacy continueranno a funzionare quando si abilita il controllo delle versioni del sistema su tabelle che trarranno vantaggio dal controllo delle versioni. ( link )
Altre implementazioni di tabelle temporali hanno affrontato questo scenario (due transazioni simultanee che aggiornano la stessa riga) offrendo un'opzione per "aggiustare" automaticamente i timestamp se non sono validi (vedere qui e qui ). Questa è una brutta soluzione, in quanto ha la sfortunata conseguenza di rompere l'atomicità delle transazioni, dal momento che altre dichiarazioni all'interno delle stesse transazioni non avranno generalmente i loro timestamp adeguati allo stesso modo; vale a dire, con questa soluzione alternativa, se visualizziamo il database "COME DA" determinati punti nel tempo, potremmo vedere transazioni eseguite parzialmente.
Soluzione: Hai già suggerito la soluzione ovvia, che consiste nell'implementazione per utilizzare l'ora di fine della transazione (ovvero l'ora di commit) anziché l'ora di inizio. Sì, è vero che quando eseguiamo un'istruzione nel mezzo di una transazione, è impossibile sapere quale sarà il tempo di commit (com'è in futuro, o potrebbe anche non esistere se la transazione dovesse essere lanciata indietro). Ma ciò non significa che la soluzione sia inattuabile; deve solo essere fatto in un modo diverso. Ad esempio, quando si esegue un'istruzione UPDATE o DELETE, nella creazione della riga della cronologia il sistema potrebbe semplicemente inserire l'ID transazione corrente anziché un'ora di inizio, quindi l'ID può essere convertito in un timestamp in un secondo momento dal sistema dopo che la transazione è stata eseguita .
Nel contesto di questo tipo di implementazione, suggerirei che prima di eseguire il commit della transazione, tutte le righe che aggiunge alla tabella cronologica non devono essere visibili all'utente. Dal punto di vista dell'utente, dovrebbe semplicemente apparire che queste righe vengono aggiunte (con il timestamp di commit) al momento del commit. In particolare, se la transazione non viene mai eseguita correttamente, non dovrebbe mai comparire nella cronologia. Naturalmente, ciò è incompatibile con lo standard SQL: 2011 che descrive gli inserimenti nella cronologia (inclusi i timestamp) come avvenuti al momento delle istruzioni UPDATE e DELETE (al contrario del tempo del commit). Ma non penso che questo sia davvero importante, considerando che lo standard non è mai stato correttamente implementato (e probabilmente non lo sarà mai) a causa dei problemi sopra descritti,
Dal punto di vista delle prestazioni, potrebbe sembrare indesiderabile che il sistema debba tornare indietro e rivisitare le righe della cronologia per riempire il timestamp di commit. Ma a seconda di come viene fatto, il costo potrebbe essere piuttosto basso. Non ho molta familiarità con il modo in cui SQL Server funziona internamente, ma PostgreSQL ad esempio utilizza un registro write-ahead, il che rende tale che se più aggiornamenti vengono eseguiti sulle stesse parti di una tabella, tali aggiornamenti vengono consolidati in modo che il i dati devono essere scritti solo una volta nelle pagine della tabella fisica, e ciò si applica in genere in questo scenario. In ogni caso,
Naturalmente, poiché (per quanto ne so) questo tipo di sistema non è mai stato implementato, non posso dire con certezza che funzionerebbe - forse c'è qualcosa che mi manca - ma non vedo alcun motivo perché non potrebbe funzionare.
20160707 11:04:58
e ora aggiorni tutte le righe con quel timestamp. Ma questo aggiornamento viene eseguito anche per alcuni secondi e termina20160707 11:05:02
ora, quale data è la fine corretta della transazione? O assumere hai usatoRead Uncommited
a20160707 11:05:00
, e ottenuto righe restituite, ma in seguitoAS OF
non le visualizza.