Perché le tabelle temporali registrano l'ora di inizio della transazione?


8

Quando si aggiorna una riga in una tabella temporale, i vecchi valori per la riga vengono archiviati nella tabella cronologica con l'ora di inizio della transazione come SysEndTime. I nuovi valori nella tabella corrente avranno l'ora di inizio della transazione come SysStartTime.

SysStartTimee SysEndTimesono datetime2colonne utilizzate dalle tabelle temporali per registrare quando una riga era la versione corrente. L'ora di inizio della transazione è l'ora di inizio della transazione contenente gli aggiornamenti.

BOL dice:

I tempi registrati nelle colonne datetime2 di sistema si basano sull'ora di inizio della transazione stessa. Ad esempio, tutte le righe inserite all'interno di una singola transazione avranno lo stesso orario UTC registrato nella colonna corrispondente all'inizio del periodo SYSTEM_TIME.

Esempio: inizio ad aggiornare tutte le righe nella tabella dei miei ordini su 20160707 11:00:00e l'esecuzione della transazione richiede 5 minuti. Questo crea una riga nella tabella della cronologia per ogni riga con SysEndTimeas 20160707 11:00:00. Tutte le righe nella tabella corrente avranno un SysStartTimedi 20160707 11:00:00.

Se qualcuno eseguisse una query su 20160707 11:01:00(mentre l'aggiornamento è in esecuzione) vedrebbe i vecchi valori (supponendo che il livello di isolamento di commit della lettura sia predefinito).

Ma se qualcuno dovesse usare la AS OFsintassi per interrogare la tabella temporale com'era, 20160707 11:01:00vedrebbero i nuovi valori perché i loro SysStartTimesarebbero 20160707 11:00:00.

Per me questo significa che non mostra quelle righe come erano in quel momento. Se utilizzasse l'ora di fine della transazione il problema non esisterebbe.

Domande: è di progettazione? Mi sto perdendo qualcosa?

L'unica ragione per cui posso pensare che stia utilizzando l'ora di inizio della transazione è che è l'unico "noto" all'avvio della transazione. Non sa quando la transazione finirà quando inizia e ci vorrebbe del tempo per applicare l'ora di fine alla fine, che invaliderebbe l'ora di fine che stava applicando. ha senso?

Ciò dovrebbe consentire di ricreare il problema.


1
Hai risposto alla tua domanda, se usi l'ora di fine della transazione hai un altro aggiornamento alla fine della transazione: l'aggiornamento termina 20160707 11:04:58e ora aggiorni tutte le righe con quel timestamp. Ma questo aggiornamento viene eseguito anche per alcuni secondi e termina 20160707 11:05:02ora, quale data è la fine corretta della transazione? O assumere hai usato Read Uncommiteda 20160707 11:05:00, e ottenuto righe restituite, ma in seguito AS OFnon le visualizza.
dnoeth

@dnoeth Sì, suppongo che questa 'domanda' sia più un chiarimento della mia teoria.
James Anderson,

Non mi sono tuffato nell'implementazione di SQL Server, ma Teradata aveva tabelle bilaterali per anni e consiglio sempre di leggere questo Case Study di Richard Snodgrass (il ragazzo che "ha inventato" le query temporali), si basa sulla sintassi SQL pre-ANSI di Teradata , ma i concetti sono gli stessi: cs.ulb.ac.be/public/_media/teaching/infoh415/…
dnoeth

Risposte:


4

L'idea è di tracciare il tempo logico rispetto al tempo fisico. Logico si riferisce semplicemente a ciò che un utente / app si aspetta dal tempo di un inserimento / aggiornamento / eliminazione. Il fatto che l'operazione DML possa richiedere del tempo per qualsiasi motivo, non è significativa o persino facilmente determinata e compresa da un utente. Se hai mai dovuto spiegare la contesa tra blocco e blocco a un contabile (ho), è una situazione comparabile.

Ad esempio, quando Bob "dice" all'app che tutti i dipendenti del dipartimento di Bob inizieranno a guadagnare $ 42 / min a 20160707 11:00:00, Bob (e i suoi dipendenti) si aspettano che la paga di tutti sia calcolata a $ 42 / min da quel momento. A Bob non importa che ciò avvenga, l'app deve fare 2 letture e 6 scritture nel database per dipendente e i loro file di dati + registro si trovano su un gruppo di unità RAID-5 SATA II, quindi ci vogliono circa 7 minuti per completare l'attività per tutti i 256 dipendenti di Bob. Bob, il suo commercialista e il responsabile delle buste paga si preoccupano che tutti i suoi dipendenti siano pagati $ 42 / min a partire 20160707 11:00:00. Altrimenti, i dipendenti che sono stati aggiornati 20160707 11:00:01saranno leggermente infastiditi mentre quelli i cui registri sono stati aggiornati 20160707 11:00:07si raduneranno al di fuori del reparto buste paga.

Esistono casi d'uso validi per tenere traccia del tempo fisico come debug e analisi forense, ma per l'utente finale è generalmente insignificante. Tlog conserva sia le informazioni di ordinazione che di tempistica per ciascuna delle operazioni di scrittura (tra le altre cose), quindi è lì se sai come guardare.


Bei punti. Immagino che la tecnologia sia adatta solo ad alcuni casi d'uso come quello che hai citato. Per i motivi sopra indicati, sembra che non sarebbe adatto per il monitoraggio del prezzo o dei valori di borsa che possono variare in brevissimo tempo.
James Anderson,

In realtà no. Questo è un problema di perf e scale. Le tabelle temporali funzionano ancora se è necessario mantenere un punto nella cronologia del prezzo delle azioni. Devi solo assicurarti che gli inserti siano molto granulari e possano essere completati in una finestra molto piccola. Altrimenti, le modifiche successive verranno bloccate e se la velocità in entrata è abbastanza elevata, si verificano timeout e potenziale perdita di dati se l'app non è in grado di gestire i tentativi. Se si esegue il DB da IO di fusione o con tabelle ottimizzate per la memoria, è possibile gestire facilmente decine di migliaia di inserti al secondo a ben oltre centomila al secondo.
SQLmojoe,

3

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.


0

Al momento del commit della transazione, tutti i dati devono essere scritti all'interno delle pagine dei dati (in memoria e sul disco nel file di registro). Compresi SysStartTimee SysEndTimecolonne. Come si può sapere l'ora di fine della transazione prima che sia effettivamente completata?

A meno che non sia possibile prevedere il futuro, l'utilizzo dell'ora di inizio della transazione è l'unica opzione, anche se potrebbe essere meno intuitivo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.