データベースのコンテンツを制御するバージョン


16

ユーザーが編集可能なコンテンツを含むWebプロジェクトに取り組んでおり、データベースにある実際のコンテンツのバージョントラッキングを実行できるようにしたいと考えています。基本的に、wikiスタイルの変更履歴を実装したいと思います。

いくつかのバックグラウンド調査を行うと、データベーススキーマをバージョン管理する方法に関するドキュメントがたくさんあります(私のものは実際には既に制御されています)が、データベースコンテンツの変更を追跡する方法に関する既存の戦略は、少なくともスキーマバージョン管理の雪崩では失われます私の検索で。

私は自分の変更追跡を実装するいくつかの方法を考えることができますが、それらはすべてかなり粗雑に見えます:

  • 変更ごとに行全体を保存し、主キーを使用して行をソースIDに関連付けます(現在私が傾倒しているのは、最も単純な方法です)。ただし、多くの小さな変更により、大量のテーブルが膨張する可能性があります。
  • 各変更の前/後/ユーザー/タイムスタンプを保存し、変更を関連する列に関連付ける列名を付けます。
  • before / after / user / timestampを各列のテーブルに保存します(結果としてテーブルが多すぎます)。
  • 列ごとに変更ごとにdiff / user / timestampを保存します(つまり、特定の日付に戻るには、変更履歴全体をたどる必要があります)。

ここで最善のアプローチは何ですか?自分自身を転がすことは、おそらく他の誰かの(より良い)コードベースを再発明しているように思えます。


PostgreSQLのボーナスポイント。


この質問は、SO:stackoverflow.com/questions/3874199/…で既に議論されています。「データベースレコードの履歴」についてはGoogle、さらにいくつかの記事があります。
Doc Brown


SQL-Serverのトランザクションログを使用してトリックを行わないのはなぜですか?
トーマスジャンク

回答:


11

私が通常使用している手法は、end_timestampフィールドを使用して完全なレコードを保存することです。1行のみがnull end_timestampを持つことができるというビジネスルールがあり、これはもちろん現在アクティブなコンテンツです。

このシステムを採用する場合、ルールを実施するためにインデックスまたは制約を追加することを強くお勧めします。一意のインデックスには1つだけのnullを含めることができるため、これはOracleでは簡単です。他のデータベースはさらに問題になる可能性があります。データベースにルールを強制させると、コードが正直になります。

多くの小さな変更が肥大化することは間違いありませんが、これをコードやレポートの単純さとトレードオフする必要があります。


他のデータベースエンジンの動作は異なる可能性があることに注意してください。たとえば、MySQLは一意のインデックスを持つ列で複数のNULL値を許可します。これにより、この制約を強制するのがはるかに難しくなります。
qbd

実際のタイムスタンプの使用は安全ではありませんが、一部のMVCCデータベースは、タプルとともに最小および最大トランザクションシリアル番号を保存することにより内部的に動作します。
user2313838

「一意のインデックスには1つだけのnullを含めることができるため、これはOracleでは簡単です」。違う。Oracleは、インデックスにNULL値をまったく含めません。一意のインデックスを持つ列のnullの数に制限はありません。
ジェラート

@Gerratこの要件を持つデータベースを設計してから数年が経ち、そのデータベースにはアクセスできなくなりました。標準の一意のインデックスは複数のnullをサポートできることは正しいですが、一意の制約または機能インデックスを使用したと思います。
キウィロン

8

Microsoft SQL Serverを使用する場合、Change Data Captureと呼ばれる機能が既にあることに注意してください。以前のリビジョンに後でアクセスするためのコードを記述する必要があります(CDCはそのための特定のビューを作成します)が、少なくともテーブルのスキーマを変更したり、変更追跡自体を実装したりする必要はありません。

フードの下で、何が起こるかです:

  • CDCは、リビジョンを含む追加のテーブルを作成し、

  • 元のテーブルは以前と同じように使用されます。つまり、更新はこのテーブルに直接反映されます。

  • CDCテーブルには、変更された値のみが保存されます。つまり、データの重複は最小限に抑えられます。

変更が別のテーブルに保存されるという事実には、2つの大きな結果があります。

  • 元のテーブルからの選択は、CDCを使用しない場合と同じくらい高速です。よく覚えていれば、更新にCDCが発生するため、更新も同様に高速です(CDCがデータの整合性をどのように管理しているかはよく覚えていませんが)。

  • 元のテーブルのスキーマにいくつかの変更を加えると、CDCが削除されます。たとえば、列を追加した場合、CDCはその処理方法を知りません。一方、インデックスまたは制約を追加することは問題ありません。頻繁に変更されるテーブルでCDCを有効にすると、これはすぐに問題になります。CDCを失うことなくスキーマを変更できるソリューションがあるかもしれませんが、私はそれを検索していません。


6

「哲学的に」問題をコードで最初に解決します。そして、それを実現するためにコードとデータベースと「交渉」します。

例として、一般的な記事を扱っている場合、記事の最初のコンセプトは次のようになります。

class Article {
  public Int32 Id;
  public String Body;
}

次の最も基本的なレベルでは、改訂のリストを保持したいと思います。

class Article {
  public Int32 Id;
  public String Body;
  public List<String> Revisions;
}

そして、現在の本文が最新版であることに気づくかもしれません。それは次の2つのことを意味します。各リビジョンに日付または番号を付ける必要があります。

class Revision {
  public Int32 Id;
  public Article ParentArticle;
  public DateTime Created;
  public String Body;
}

そして...そして記事の現在の本文は最新のリビジョンと区別する必要はありません:

class Article {
  public Int32 Id;
  public String Body {
    get {
      return (Revisions.OrderByDesc(r => r.Created))[0];
    }
    set {
      Revisions.Add(new Revision(value));
    }
  }
  public List<Revision> Revisions;
}

いくつかの詳細が欠落しています。ただし、おそらく2つのエンティティ必要であることを示しています。1つは記事(またはその他のヘッダータイプ)を表し、もう1つはリビジョンのリスト(グループにとって「哲学的」な意味のあるフィールドをグループ化する)です。最初は特別なデータベース制約は必要ありません。コードはそれ自体のリビジョンを気にかけないためです。それらはリビジョンを知っている記事のプロパティです。

そのため、特別な方法でリビジョンにフラグを立てたり、「現在の」記事をマークするためにデータベースの制約に頼ったりする必要はありません。それらにタイムスタンプを付けて(自動IDでも構いません)、親のArticleに関連させ、「最新の」ものが最も関連性の高いものであることを記事に任せます。

そして、ORMに哲学的でない詳細を処理させます。または、すぐに使えるORMを使用していない場合は、カスタムユーティリティクラスでそれらを非表示にします。

かなり後で、ストレステストを行った後、そのリビジョンプロパティを遅延読み込みにするか、Body属性を最上位のリビジョンのみに遅延読み込みさせることを考えるかもしれません。ただし、この場合のデータ構造は、これらの最適化に対応するために変更する必要はありません。


2

必要な処理を実行する監査ログを設定する方法を順を追って説明する監査追跡トリガー用のPostgreSQL wikiページがあります。

変更の完全な元データと、更新の新しい値のリストを追跡します(挿入と削除の場合、値は1つのみです)。古いバージョンを復元する場合は、監査レコードから元のデータのコピーを取得できます。データに外部キーが含まれる場合、一貫性を維持するためにこれらのレコードもロールバックする必要がある場合があります。

一般的に、データベースアプリケーションがほとんどの時間を現在のデータだけに費やしている場合、現在のデータとは別のテーブルで代替バージョンを追跡する方が良いと思います。これにより、アクティブなテーブルインデックスがより管理しやすくなります。

追跡する行が非常に大きく、スペースが深刻な懸念事項である場合、変更を分解して最小限の差分/パッチを保存しようとすることができますが、それは間違いなくすべての種類のデータ型をカバーするためのより多くの作業です。これは以前に行ったことがありますが、すべての変更を1つずつ逆方向にたどってデータの古いバージョンを再構築するのは苦痛でした。


1

さて、古いバージョンの行をテーブルごとの履歴ログにコピーするトリガーである、最も単純なオプションを使用することにしました。

データベースが肥大化しすぎた場合、必要に応じて、マイナーな履歴の変更の一部を折りたたむことができます。

トリガー関数を自動的に生成したかったため、解決策はかなり複雑になりました。私はSQLAlchemyなので、いくつかの継承hijinksを実行して履歴テーブルを作成できましたが、実際のトリガー関数は、PostgreSQL関数を適切に生成し、1つのテーブルの列を別の正しく。

とにかく、すべてここの githubにあります

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.