2009-04-03 21 views
9

Come in molti database, sto progettando un database che dovrebbe tenere traccia delle versioni precedenti delle righe modificate in ciascuna tabella.gestione delle righe cronologiche nella banca dati

La soluzione standard a questo problema è mantenere una tabella di cronologia per ogni tabella di dati, e ogni volta che una riga deve essere aggiornata nella tabella dati, una copia della riga corrente viene inserita nella tabella della cronologia e la riga nella tabella dati viene aggiornata.

gli svantaggi di questa soluzione per me:

  • mantenimento di 2 tabelle anziché 1, (nel caso la struttura delle esigenze tabella cambiate)
  • l'applicazione deve conoscere entrambe le tabelle invece di una
  • nomi delle tabelle potrebbero devono essere brevi per mantenere una convenzione del nome della tabella e il nome della tabella di storia (some_table, SOME_TABLE_HIST per esempio)

ho un Sto considerando una soluzione diversa e vorrei sapere se è ok. per ogni tabella, si aggiunge la colonna IS_LAST

  • quando una riga viene inserita nella tabella, otterrà inserito con IS_LAST = 1.
  • quando una riga viene aggiornata, una copia della riga originale verrà duplicata nella stessa tabella con la modifica di IS_LAST = 0 e la riga originale verrà aggiornata secondo necessità (mantenendo ancora IS_LAST = 1).

presupporre che nel mio caso le righe vengano aggiornate in media 10 volte. anche supporre che almeno il 90% delle azioni eseguite dall'applicazione si verifica solo sulla versione recente delle righe.

il mio database è un Oracle 10g in modo da mantenere snella la tabella "attiva", possiamo dividere la tabella in 2 partizioni: la partizione IS_LAST = 1 e la partizione IS_LAST = 0.

Il partizionamento è un buon metodo per risolvere i problemi di cronologia dei dati?

Questa soluzione limita il potenziale di altre partizioni a queste tabelle?

grazie!

risposta

2

Vorrei creare due tabelle: una per il tipo di valori IsLast e una per quelli storici. Quindi installerei un trigger che inserisce il valore nella tabella storica ogni volta che isLast viene aggiornato.

+3

È Oracle, perché preoccuparsi? Basta partizionare su quella colonna e attivare la migrazione di Row. È integrato, perché riscrivere e mantenere due tabelle. –

0

Il limite principale che mi viene in mente è che una parte sostanziale della tabella sarà costituita da dati cronologici, il che significa problemi di indicizzazione e potenzialmente l'introduzione di ulteriore complessità nelle query CRUD.

C'è qualche ragione particolare per cui non vuoi usare quella che sembra essere la solita soluzione a questa situazione?

+0

È Oracle, la partizione risolve la tua preoccupazione. Devi solo partizionare su Is_last e attivare la migrazione delle righe e voilà, le tue query che per quanto riguarda is_last = 1 non vedranno mai i vecchi dati. –

0

Come verranno definite le chiavi primarie? Ci saranno molte righe con la stessa chiave primaria a causa del keaping delle righe della cronologia nella stessa tabella.

Inoltre, non sembra che sia possibile conoscere l'ordine delle righe della cronologia quando una singola riga "reale" viene modificata più volte.

(un progetto ho lavorato su, abbiamo generato tutte le tabelle di cronologia e trigger utilizzando CodeSmith, questo ha funzionato molto bene.)

6

prima domanda dovrebbe essere: cosa vorresti fare con quei dati? Se non hai requisiti aziendali chiari, non farlo.

Ho fatto qualcosa di simile e dopo 3 anni di funzionamento c'è circa il 20% di "dati validi" e il resto è "versioni precedenti". Ed è 10 milioni + 40 milioni di record. Negli ultimi tre anni abbiamo avuto 2 (due) richieste per indagare sulla storia dei cambiamenti ed entrambe le volte le richieste sono state sciocche - registriamo il timestamp del cambiamento del record e ci è stato chiesto di verificare se le persone lavorassero gli straordinari (dopo le 17:00).

Ora, siamo bloccati con un database di grandi dimensioni che contiene l'80% dei dati di cui nessuno ha bisogno.

EDIT:

Dal momento che hai chiesto per le possibili soluzioni, io descrivere quello che abbiamo fatto. È un po 'diverso dalla soluzione che stai considerando.

  1. Tutte le tabelle hanno la chiave primaria surrogata.
  2. Tutte le chiavi primarie sono generate da una singola sequenza. Funziona bene perché Oracle può generare e memorizzare i numeri in cache, quindi non ci sono problemi di prestazioni. Usiamo ORM e vogliamo che ogni oggetto in memoria (e corrispondente record nel database) abbia un identificatore univoco
  3. Usiamo ORM e le informazioni di mappatura tra la tabella del database e la classe è in forma di attributi.

Registriamo tutti i cambiamenti in un'unica tabella di archivio con i seguenti colonne:

  • id (chiave primaria surrogata)
  • time stamp
  • tabella originale
  • id del record originale
  • ID utente
  • tipo di transazione (inserire, aggiornare, eliminare)
  • registra dati come campo varchar2
    • questo è dati effettivi sotto forma di coppie nome/valore.

cosa funziona in questo modo:

  • ORM ha inserimento/aggiornamento ed eliminare comandi che seguono.
  • abbiamo creato una classe di base per tutti i nostri oggetti di business che prevale inserimento/aggiornamento e eliminare i comandi
    • inserimento/aggiornamento/delete comandi creano stringa in forma di coppie fieldname/valore utilizzando la riflessione. Il codice cerca informazioni sulla mappatura e legge il nome del campo, il valore associato e il tipo di campo. Quindi creiamo qualcosa di simile a JSON (abbiamo aggiunto alcune modifiche). Quando viene creata una stringa che rappresenta lo stato corrente dell'oggetto, viene inserita nella tabella di archivio.
  • quando oggetto nuovo o aggiornato viene salvato nella tabella del database, viene salvato nella sua tabella di destinazione e contemporaneamente viene inserito un record con il valore corrente nella tabella di archivio.
  • quando l'oggetto viene eliminato, cancelliamo dalla sua tabella di destinazione e, allo stesso tempo siamo inserire un record nella tabella di archivio che hanno tipo di transazione = "DELETE"

Pro:

  • siamo non hanno tabelle di archivio per ogni tabella nel database. Inoltre, non dobbiamo preoccuparci di aggiornare la tabella degli archivi quando le modifiche dello schema.
  • L'archivio completo è separato da "dati correnti", pertanto l'archivio non impone alcun impatto sulle prestazioni sul database. Lo mettiamo su un tablespace separato su un disco separato e funziona perfettamente.
  • abbiamo creato 2 moduli per la visualizzazione dell'archivio:
    • spettatore generale che può elencare tabella di archivio in base al filtro sulla tabella di archivio. Filtra i dati che l'utente può inserire nel modulo (intervallo temporale, utente, ...). Mostriamo ogni record in forma nome-campo/valore e ogni cambiamento è codificato a colori. Gli utenti possono vedere tutte le versioni per ogni record e possono vedere chi e quando hanno apportato le modifiche.
    • visualizzatore di fatture - questo era complesso, ma abbiamo creato un modulo che mostra una fattura molto simile al modulo di immissione della fattura originale, ma con alcuni pulsanti aggiuntivi che possono mostrare diverse generazioni. Ci sono voluti notevoli sforzi per creare questo modulo. Il modulo è stato usato poche volte e poi dimenticato perché non era necessario nel flusso di lavoro corrente.
  • codice per la creazione di record di archivio si trova nella singola classe C#. Non c'è bisogno di trigger su ogni tabella nel database.
  • prestazioni è molto buona. Nelle ore di punta, il sistema viene utilizzato da circa 700-800 utenti. Questa è l'applicazione ASP.Net. Sia ASP.Net che Oracle sono in esecuzione su un unico XEON con 8 GB di RAM.

Contro:

  • formato unico archivio tavolo è più difficile da leggere rispetto alla soluzione in cui v'è una tabella di archivio per ciascuna delle tabelle di dati.
  • ricerca su campo non id nella tabella archivio è difficile: possiamo utilizzare solo l'operatore LIKE su stringa.

Quindi, di nuovo, verificare i requisiti sull'archivio. Non è un compito banale, ma i guadagni e l'utilizzo possono essere minimi.

+0

ma l'argomento sulla dimensione del database non dovrebbe essere vero anche se OP ha una tabella _HIST? – Learning

+0

@zendar, usi il partizionamento? – tuinstoel

+0

No. Le cose funzionano bene come sono adesso. È solo che abbiamo 5 volte più record di quanto dovremmo avere. L'abbiamo fatto perché era "una caratteristica molto importante" di cui nessuno ora ha bisogno. Quindi ho messo in discussione la motivazione di Asaf per questo. Forse può risparmiarsi tempo e dolore. – zendar

0

Vorrei utilizzare la partizione IS_LAST=1 e il sistema di partizione IS_LAST=0. Poiché è partizionato, sarà veloce (eliminazione delle partizioni) e non dovrai mai eseguire una query sull'unione della tabella normale e della tabella della cronologia.

Vorrei utilizzare IS_LAST = 'Y'/'N' e non 1/0. 1 e 0 sono privi di significato.

C'è un trucco speciale che può aiutare guarrantee che c'è massimo una riga con IS_LAST='Y' per entità: è possibile creare un indice univoco basato funzione con una funzione che restituisce null quando IS_LAST='N' e restituire l'id quando IS_LAST='Y'.È spiegato qui: http://www.akadia.com/services/ora_function_based_index_1.html

1

Se ho 1 o 2 tabelle di storia da conservare, lo farei esattamente come suggerito da Tuinstoel. Ma se avessi dozzine di tavoli per farlo, mi spingerei più verso una soluzione descritta da Zendar. La ragione è questa.

Come si fa a rispondere a domande quali,

  • che cosa è cambiato da ieri quando tutto andava bene?

  • L'utente SMITHG ha apportato modifiche?

Queste domande richiedono una query per tabella, sia che si tratti di una tabella _hist separata o di una partizione all'interno della tabella. Non importa, è un enorme elenco di domande. Se hai un tavolo centrale simile a questo, allora è un pezzo di torta.

table_name, Column_name, PK, Before_value, After_value, User, timestamp 

Inserti avere solo dopo valori,

Elimina avere solo valori precedenti,

Aggiorna avere entrambi, ma solo per le colonne che hanno cambiato.

Alcune varianti

è possibile includere una colonna per I/U/D, se si preferisce È possibile escludere i valori delle colonne per inserti e basta registrare il PK e ho dato i valori corretti sono ancora nella tabella.

Poiché questo è Oracle, è possibile partizionare su nome_tabella, quindi in pratica si ha effettivamente una "tabella" di hist per tabella reale.

È possibile rispondere facilmente alle domande di cui sopra, che ritengo siano, molto semplicemente, le domande più frequenti. E gestisce ogni domanda a cui puoi rispondere con le partizioni o le tabelle _hist.

0

Il rilevamento in base al tempo consente di ottenere l'effetto che si sta cercando su base giornaliera e alla fine dell'attività o a mezzanotte a seconda del tempo di volume minimo della transazione se è stata eseguita una procedura per spostare i dati finali nella tabella della storia quindi sarebbe d'aiuto ?? in questo modo tutti gli aggiornamenti sarebbero inseriti e non è richiesto nessun blocco. Cordiali saluti, Andy

1

Poiché si utilizza Oracle, è possibile controllare Oracle Flashback Technology. Registra le modifiche di tutte le modifiche nel database, sia i dati che la struttura. Registra inoltre data e ora e nome utente.

Non l'ho usato, ma sembra in grado.

+0

Questa è solo una caratteristica di llg, se sono su quello, quindi grande, altrimenti, non è un'opzione. –

+0

Il flashback è introdotto in 9i, circa 8-9 anni fa. Nella versione 10g è diventato abbastanza maturo. – zendar

0

Tutto dipende da quello che hai:

  • Stai usando Standard o Enterprise Edition? Il partizionamento è incluso solo come opzione in cima a Enterprise Edition. Maggiori informazioni su questo here.
  • Si potrebbe prendere in considerazione l'idea di andare con Workspace Manager se si è alla ricerca di una soluzione facile in cui non è necessario mantenere il proprio codice. Tuttavia, ci sono alcune limitazioni che ho trovato (ad esempio, la manutenzione dell'indice Oracle Text sembra difficile, se non impossibile, anche se l'ho vista solo su 10gR2).
  • Altrimenti, andrei con la soluzione di zvolkov (tabella live con un trigger che scrive sulla tabella della cronologia) o la soluzione di Mark Brady (registro delle modifiche). Ho usato entrambi i modelli e ognuno ha i suoi pro e contro.
  • @zendar: la query di flashback funziona solo fino all'annullamento. Non è una soluzione a lungo termine, ma solo una soluzione per guardare al massimo qualche ora (a seconda della quantità di conservazione che hai specificato).
0

Come con gli altri, utilizzo un ORM (Propel) con un oggetto di base contenente i metodi di salvataggio personalizzati &. Questi metodi sovrascrivono lo standard risparmi & eliminato fornito con l'ORM. Controllano per vedere quali colonne sono cambiate e creano 1 riga nella tabella delle modifiche per ogni colonna modificata.

schema per change tabella: change_pk, user_fk, nome_utente, session_id, ip_address, metodo, table_name, row_fk, nome_campo, FIELD_VALUE, most_recent, date_time

Esempio: 1, 4232, 'Gnarls Barkley', ' f2ff3f8822ff23 ',' 234.432.324.694 ',' UPDATE ',' User ', 4232,' first_name ',' Gnarles ',' Y ',' 2009-08-20 10:10:10 ';

Problemi correlati