データベースのレコードをバージョン管理する方法


176

データベースにレコードがあり、管理者と一般ユーザーの両方が更新を実行できるとします。

レコードを前のリビジョンにロールバックできるように、このテーブルのすべての変更をバージョン管理する方法について、良いアプローチ/アーキテクチャを誰かが提案できますか?

回答:


164

FOO管理者とユーザーが更新できるテーブルがあるとしましょう。ほとんどの場合、FOOテーブルに対するクエリを記述できます。幸せな日々。

次に、FOO_HISTORYテーブルを作成します。これには、FOOテーブルのすべての列があります。主キーはFOOと同じで、RevisionNumber列があります。からFOO_HISTORYへの外部キーがありFOOます。UserIdやRevisionDateなど、リビジョンに関連する列を追加することもできます。すべての*_HISTORYテーブル(Oracleシーケンスまたは同等のもの)全体で、RevisionNumbersを常に増やしていきます。1秒間に1つの変更のみがあるとは考えないでください(つまりRevisionDate、主キーに入れないでください)。

更新するたびにFOO、更新を行う直前に古い値をに挿入しますFOO_HISTORY。これは、プログラマーが誤ってこのステップを見落とさないように、設計の基本的なレベルで行います。

行を削除するFOO場合は、いくつかの選択肢があります。カスケードしてすべての履歴を削除するか、削除済みFOOとしてフラグを付けて論理削除を実行します。

このソリューションは、現在の値に大きな関心があり、履歴にたまにしか関心がない場合に適しています。常に履歴が必要な場合は、有効な開始日と終了日を設定し、すべてのレコードをそのまま保持できFOOます。次に、すべてのクエリでこれらの日付を確認する必要があります。


1
データアクセスレイヤーが監査テーブルを直接サポートしていない場合は、データベーストリガーで監査テーブルを更新できます。また、システムデータディクショナリからのイントロスペクションを使用するトリガーを作成するコードジェネレーターを構築することは難しくありません。
ConcernedOfTunbridgeWells

44
以前のデータではなく、実際に新しいデータを挿入することをお勧めします。これにより、履歴テーブルにすべてのデータが含まれます。重複データを格納しますが、履歴データが必要な場合に両方のテーブルでの検索に対処するために必要な特別なケースを排除します。
Nerdfest 2008年

6
個人的には何も削除しないことをお勧めします(これを特定のハウスキーピングアクティビティに任せる)。挿入/更新/削除かどうかを指定する「アクションタイプ」列を用意します。削除の場合は、通常どおり行をコピーしますが、アクションタイプの列には「削除」を入力します。
Neil Barnwell、

3
@Hydrargyrum現在の値を保持するテーブルは、履歴テーブルのビューよりもパフォーマンスが向上します。現在の値を参照する外部キーを定義することもできます。
WW。

2
There is a foreign key from FOO_HISTORY to FOO':悪い考えです。履歴を変更せずにfooからレコードを削除したいと思います。通常の使用では、履歴テーブルは挿入専用でなければなりません。
Jasen 2015

46

データベースレコードのコンテンツのバージョン管理を探していると思います(誰かが質問/回答を編集するときにStackOverflowが行うように)。出発点としては、リビジョン追跡を使用するデータベースモデルを検討することから始めるのがよいでしょう。

頭に浮かぶ最良の例は、WikipediaエンジンであるMediaWikiです。ここでデータベースダイアグラム、特にリビジョンテーブルを比較してください

使用しているテクノロジーに応じて、適切なdiff / mergeアルゴリズムを見つける必要があります。

.NETの場合は、この質問を確認してください。


30

BIの世界では、バージョン管理するテーブルにstartDateとendDateを追加することでこれを実現できます。最初のレコードをテーブルに挿入すると、startDateに値が入力されますが、endDateはnullです。2番目のレコードを挿入すると、最初のレコードのendDateも2番目のレコードのstartDateで更新されます。

現在のレコードを表示する場合は、endDateがnullのレコードを選択します。

これは、タイプ2の緩やかに変化するディメンションと呼ばれることもあります。TupleVersioningも参照してください


このアプローチを使用すると、テーブルが非常に大きくなりますか?
Niels Bosma 2008年

1
はい。ただし、テーブルにインデックスを作成したりパーティションを作成したりすることで対処できます。また、少数の大きなテーブルしかありません。ほとんどははるかに小さくなります。
ConcernedOfTunbridgeWells

私が間違っていない場合、ここでの唯一の落とし穴は、変更が毎秒1回に制限されるということです。
pimbrouwers

@pimbrouwersはい、それは最終的にフィールドの精度とそれらを移入する関数に依存します。
Dave Neeley、

9

SQL 2008にアップグレードします。

SQL 2008でSQL変更追跡を使用してみてください。タイムスタンプや廃棄列のハックの代わりに、この新機能を使用して、データベース内のデータの変更を追跡できます。

MSDN SQL 2008変更追跡


7

この問題に対する1つの優れた解決策は、Temporalデータベースを使用することです。多くのデータベースベンダーは、この機能をそのまま、または拡張機能を介して提供しています。私はPostgreSQLでテンポラルテーブル拡張を正常に使用しましたが、他の人にもそれがあります。データベースのレコードを更新すると、データベースはそのレコードの以前のバージョンも保持します。


6

2つのオプション:

  1. 履歴テーブルを用意する-オリジナルが更新されるたびに、この履歴テーブルに古いデータを挿入します。
  2. 監査テーブル-変更前と変更後の値を保存-監査テーブルの変更された列と、誰がいつ更新したかなどのその他の情報

5

SQLトリガーを介してSQLテーブルの監査を実行できます。トリガーから、2つの特別なテーブル(挿入および削除)にアクセスできます。これらのテーブルには、テーブルが更新されるたびに挿入または削除された正確な行が含まれています。トリガーSQLでは、これらの変更された行を取得して、監査テーブルに挿入できます。このアプローチは、監査がプログラマーに対して透過的であることを意味します。それらからの努力や実装に関する知識は必要ありません。

このアプローチの追加の利点は、SQL操作がデータアクセスDLLを介して行われたか、手動のSQLクエリを介して行われたかに関係なく、監査が行われることです。(監査はサーバー自体で実行されるため)。


3

あなたはどのデータベースを言うのではなく、私はそれを投稿タグに表示しません。Oracleの場合は、Designerに組み込まれているアプローチとしてジャーナルテーブルを使用することをお勧めします。他のデータベースの場合も、基本的には同じ方法をお勧めします...

別のDBに複製したい場合、または単に理解したい場合の動作方法は、テーブルにはシャドウテーブルも作成され、同じフィールド仕様の通常のデータベーステーブルのみが作成されます。 、およびいくつかの追加フィールド:最後に実行されたアクション(文字列、挿入の場合は通常の値「INS」、更新の場合は「UPD」、削除の場合は「DEL」)、アクションが実行された日時、実行したユーザーのユーザーIDなどそれ。

トリガーを介して、テーブルの任意の行に対するすべてのアクションは、新しい値、どのアクションがいつ、どのユーザーによって実行されたかを示すジャーナルテーブルに新しい行を挿入します。行を削除しない(少なくとも過去数か月は)。はい、それは大きくなり、数百万行になりやすくなりますが、ジャーナリングが開始されてから、または古いジャーナル行が最後にパージされてから、いつ誰が最後に変更を行ったかから、いつでも任意のレコードの値を簡単に追跡できます。

Oracleでは、必要なすべてのものがSQLコードとして自動的に生成され、コンパイル/実行するだけで済みます。それを検査するための基本的なCRUDアプリケーション(実際には「R」のみ)が付属しています。


2

私も同じことをしています。授業計画のデータベースを作っています。これらの計画には、アトミック変更のバージョン管理の柔軟性が必要です。言い換えれば、レッスンプランの変更は、どれだけ小さくても許可する必要がありますが、古いバージョンもそのままにしておく必要があります。このようにして、レッスン作成者は、生徒が使用しているときにレッスンプランを編集できます。

それが機能する方法は、学生がレッスンを完了すると、彼らの結果は彼らが完成したバージョンに添付されるということです。変更が加えられた場合、結果は常にバージョンを示します。

このようにして、レッスン基準が削除または移動されても、結果は変わりません。

私が現在これを行っている方法は、1つのテーブルですべてのデータを処理することです。通常、idフィールドは1つだけですが、このシステムでは、idとsub_idを使用しています。sub_idは、更新と削除を通じて、常に行に残ります。IDは自動的に増分されます。授業計画ソフトウェアは最新のsub_idにリンクします。学生の結果はIDにリンクされます。変更がいつ発生したかを追跡するためのタイムスタンプも含めましたが、バージョン管理を行う必要はありません。

私が変更した後、変更する可能性があることの1つは、前述のendDate nullのアイデアを使用することです。私のシステムでは、最新バージョンを見つけるために、max(id)を見つける必要があります。他のシステムは単にendDate = nullを探します。別の日付フィールドがあるためにメリットが出るかどうかはわかりません。

私の2セント。


2

@WWながら。答えは良い答えです。もう1つの方法は、バージョン列を作成し、すべてのバージョンを同じテーブルに保持することです。

1つのテーブルアプローチでは、次のいずれかを行います。

  • フラグを使用して最新のワードプレスを示します
  • または、バージョンよりも厄介outer joinです。

outer joinリビジョン番号を使用するメソッドのSQLの例は次のとおりです。

SELECT tc.*
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- path in this case is our natural id.

悪いニュースは、上記はを必要としouter join、外部結合は遅くなる可能性があることです。良い知らせは、トランザクションなし1つの書き込み操作でそれを行うことができるため(データベースがアトミックであると仮定すると)、新しいエントリの作成は理論的に安価です。

の新しいリビジョンを作成する例は'/stuff'次のとおりです。

INSERT INTO text_content (id, path, data, revision, revision_comment, enabled, create_time, update_time)
(
SELECT
(md5(random()::text)) -- {id}
, tc.path
, 'NEW' -- {data}
, (tc.revision + 1)
, 'UPDATE' -- {comment}
, 't' -- {enabled}
, tc.create_time
, now() 
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- {path}
)

古いデータを使用して挿入します。これは、1つの列のみを更新し、楽観的ロックやトランザクションを回避したい場合に特に便利です。

フラグアプローチと履歴テーブルアプローチでは、2つの行を挿入/更新する必要があります。

outer joinリビジョン番号アプローチのもう1つの利点は、トリガーを使用して常に複数テーブルのアプローチにリファクタリングできることです。これは、トリガーが本質的に上記のようなことを行う必要があるためです。


2

AlokはAudit table上記の提案をしたので、私の投稿で説明したいと思います。

私はこのスキーマレスの単一テーブルデザインをプロジェクトに採用しました。

スキーマ:

  • id-整数の自動インクリメント
  • ユーザー名-STRING
  • テーブル名-STRING
  • oldvalue-TEXT / JSON
  • newvalue-TEXT / JSON
  • createdon-DATETIME

このテーブルは、各テーブルの履歴レコードを一度にすべて保持でき、1つのレコードで完全なオブジェクト履歴を保持できます。このテーブルは、データが変化するトリガー/フックを使用してデータを入力でき、ターゲット行の古い値と新しい値のスナップショットを格納します。

このデザインの長所:

  • 履歴管理のために管理するテーブルの数が少なくなります。
  • 各行の古い状態と新しい状態の完全なスナップショットを格納します。
  • 各テーブルを簡単に検索できます。
  • テーブルごとにパーティションを作成できます。
  • テーブルごとにデータ保持ポリシーを定義できます。

このデザインの短所:

  • システムに頻繁な変更がある場合、データサイズが大きくなる可能性があります。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.