履歴データを保存する方法


162

一部の同僚と私は、履歴データを保存するための最良の方法について議論しました。現在、一部のシステムでは、個別のテーブルを使用して履歴データを格納し、現在のアクティブなレコード用に元のテーブルを保持しています。それでは、テーブルFOOがあるとします。私のシステムでは、すべてのアクティブなレコードはFOOに入り、すべての履歴レコードはFOO_Histに入ります。ユーザーはFOOのさまざまなフィールドを更新できるため、更新されたすべての情報を正確に把握したいと考えています。FOO_Histは、自動インクリメントHIST_IDを除いて、FOOとまったく同じフィールドを保持します。FOOが更新されるたびに、次のようなFOO_Histへの挿入ステートメントを実行しますinsert into FOO_HIST select * from FOO where id = @id

私の同僚は、これは設計が悪いと言います。これは、履歴上の理由でテーブルの正確なコピーを用意する必要がなく、履歴用であることを示すフラグを付けて別のレコードをアクティブテーブルに挿入する必要があるためです。

履歴データストレージを処理するための標準はありますか?100万レコードをはるかに超える可能性があることを考えると、同じテーブル内のすべての履歴レコードでアクティブレコードを乱雑にしたくないようです(私は長期的に考えています)。

あなたまたはあなたの会社はこれをどのように扱いますか?

私はMS SQL Server 2008を使用していますが、回答を汎用的で任意のDBMSに限定したいと思います。

回答:


80

運用システム内で直接履歴データをサポートすると、アプリケーションは他の方法よりもはるかに複雑になります。一般に、システム内でレコードの履歴バージョンを操作するという厳しい要件がない限り、これを行うことはお勧めしません。

よく見ると、履歴データのほとんどの要件は次の2つのカテゴリのいずれかに分類されます。

  • 監査ログ: これは、監査テーブルで行う方がよいでしょう。システムデータディクショナリからメタデータを読み取ることにより、監査ログテーブルとトリガーを作成するスクリプトを生成するツールを作成するのはかなり簡単です。このタイプのツールは、監査ロギングをほとんどのシステムに組み込むために使用できます。データウェアハウスを実装する場合は、このサブシステムを変更されたデータキャプチャに使用することもできます(以下を参照)。

  • 履歴レポート:過去の状態、「現状」の位置、または経時的な分析レポートに関するレポート。上記の種類の監査ログテーブルをクエリすることで、単純な履歴レポート要件を満たすことができる場合があります。より複雑な要件がある場合は、履歴を運用システムに直接統合するよりも、レポート用のデータマートを実装する方が経済的です。

    緩やかに変化するディメンションは、履歴状態を追跡および照会するための最も単純なメカニズムであり、履歴追跡の多くは自動化できます。汎用ハンドラーを書くのはそれほど難しくありません。一般に、履歴レポートでは最新のデータを使用する必要がないため、通常、バッチ更新メカニズムで問題ありません。これにより、コアとレポートシステムのアーキテクチャが比較的シンプルになります。

要件がこれらの2つのカテゴリのいずれかに該当する場合は、運用システムに履歴データを保存しない方がよいでしょう。履歴機能を別のサブシステムに分離すると、おそらく全体的な労力が少なくなり、トランザクションデータベースと監査/レポートデータベースが生成され、意図した目的に非常によく機能します。


私はあなたが言っていることを理解していると思います。つまり、FOO_Histテーブルで行ったのは、実際に監査テーブルを作成することでした。更新時に監査テーブルに挿入するためにトリガーを使用する代わりに、プログラムでステートメントを実行しました。あれは正しいですか?
アーロン

6
かなり。ただし、この種の監査ログはトリガーを使用して実行することをお勧めします。トリガーは、変更(手動のデータ修正を含む)が監査ログに記録されることを確認します。監査するテーブルが10〜20個以上ある場合は、トリガージェネレーターツールを構築する方がおそらくすべてより速いでしょう。監査ログのディスクトラフィックに問題がある場合は、監査ログテーブルを別のディスクセットに配置できます。
ConcernedOfTunbridgeWells

はい、私は100%同意します。ありがとうございました。
アーロン

40

特定の標準的な方法があるとは思いませんが、可能な方法を投入すると思いました。Oracleと、XMLを使用してアプリケーションデータを格納する社内Webアプリケーションフレームワークで働いています。

マスター-詳細モデルと呼ばれるものを使用します。これは最も単純なもので構成されています。

たとえばWidgets、IDだけを含むマスターテーブルがよく呼び出されます。多くの場合、時間の経過とともに変化しない、または履歴データではないデータが含まれます。

詳細/履歴テーブルの例としてWidget_Details、少なくとも次のものが含まれています。

  • ID-主キー。詳細/履歴ID
  • MASTER_ID-この例では「WIDGET_ID」と呼ばれ、これはマスターレコードへのFKです。
  • START_DATETIME-そのデータベース行の開始を示すタイムスタンプ
  • END_DATETIME-そのデータベース行の終わりを示すタイムスタンプ
  • STATUS_CONTROL-単一のchar列は行のステータスを示します。「C」は、現在、NULL、または「A」が履歴/アーカイブされることを示します。END_DATETIMEがNULLになるとインデックスを作成できないため、これだけを使用します
  • CREATED_BY_WUA_ID-行が作成される原因となったアカウントのIDを格納します
  • XMLDATA-実際のデータを保存します

したがって、基本的に、エンティティはマスターに1行、詳細に1行を持つことから始まります。NULLの終了日とSTATUS_CONTROLが「C」の詳細。更新が発生すると、現在の行は現在時刻のEND_DATETIMEを持つように更新され、status_controlはNULL(または、必要に応じて 'A')に設定されます。詳細テーブルに新しい行が作成され、同じマスターにリンクされたまま、status_control 'C'、更新を行う人のID、および新しいデータがXMLDATA列に格納されます。

これが私たちの歴史的モデルの基礎です。作成/更新ロジックはOracle PL / SQLパッケージで処理されるため、関数に現在のID、ユーザーID、および新しいXMLデータを渡すだけで、内部的に、履歴モデルでそれを表すために行のすべての更新/挿入を行います。開始時間と終了時間は、テーブル内のその行がいつアクティブになるかを示します。

ストレージは安価です。通常、データを削除することはなく、監査証跡を保持することを好みます。これにより、データがいつどのように表示されたかを確認できます。status_control = 'C'にインデックスを付けるか、ビューを使用することで、乱雑さは問題になりません。明らかに、クエリでは考慮に入れる必要があるので、常に現在の(NULL end_datetimeおよびstatus_control = 'C')バージョンのレコードを使用する必要があります。


こんにちはクリス、そうするなら、ID(主キー)を変更する必要がありますよね?別のテーブルで使用されている場合、別のテーブルとの関係はどうですか?
projo 2013

@projoマスターテーブルのIDはPKであり、概念的には、処理するあらゆる概念の「PK」です。詳細テーブルのIDは、マスターの履歴バージョン(詳細の別の列)を識別するためのPKです。リレーションシップを形成するときは、概念の真のPK(つまり、マスターテーブルのIDまたは詳細のMASTER_ID列)を参照し、STATUS_CONTROL = 'C'を使用して最新バージョンを取得していることがよくあります。または、詳細IDを参照して、特定の時点に何かを関連付けることもできます。
Chris Cameron-Mills

+1このパターンを実装し、いくつかの大規模プロジェクトで大成功を収めました。
Three Value Logic

同じアプローチを使用しています。しかし、START_DATETIMEのみを格納し、END_DATETIMEを格納しない方が良いのではないかと思います
bat_ventzi

私の経験のいくつかのバリエーション。エンティティが「終了」している場合、つまりアーカイブまたは削除されている場合、「C」ステータスコントロールを持つ詳細レコードがない(つまり現在の行がない)可能性がありますが、いつ発生したかはわかりません。または、最後の行にend_datetimeを設定し、「終了」「C」行が存在することで、エンティティが現在削除/アーカイブされていることを示すことができます。最後に、別の列であるSTATUSを使用してこれを表すことができます。
Chris Cameron-Mills

15

あなたのアプローチは正しいと思います。履歴テーブルはインデックスのないメインテーブルのコピーである必要があります。テーブルにも更新タイムスタンプがあることを確認してください。

すぐに他の方法を試すと、問題が発生します。

  • メンテナンスのオーバーヘッド
  • selectのフラグを増やす
  • クエリのスローダウン
  • テーブル、インデックスの増加

7

では上記のSQL Serverの2016年と、呼ばれる新しい機能があり時間的な表の目的はでこの課題を解決することを開発者から最小限の労力で。テンポラルテーブルの概念は、チェンジデータキャプチャ(CDC)に似ていますが、CDCを使用していた場合に手動で行わなければならないことのほとんどがテンポラルテーブルによって抽象化されている点が異なります。


2

変更データキャプチャ:https : //docs.microsoft.com/en-us/sql/relational-databases/track-changes/about-change-data-capture-sql-server?view=sql-server-2017

SQL Server 2008 R2でサポートされていますが、SQL Server 2008でもサポートされている可能性があります。


Change Data Captureは、データ履歴の一時的な保存のみを目的としています。SQL Serverテンポラルテーブルと変更データキャプチャと変更追跡を参照してください。
エドワードブレイ


1

Azure SQLを使用していて、複数のテーブルを使用するのが面倒すぎたので、使い始めたオプションを追加したかっただけです。テーブルに挿入/更新/削除トリガーを追加し、「FOR JSON AUTO」機能を使用して、前/後の変更をjsonに変換しました。

 SET @beforeJson = (SELECT * FROM DELETED FOR JSON AUTO)
SET @afterJson = (SELECT * FROM INSERTED FOR JSON AUTO)

これにより、変更前/変更後のレコードのJSON表現が返されます。次に、変更が発生したときのタイムスタンプが付いた履歴テーブルにこれらの値を保存します(現在の問題のレコードのIDも保存します)。シリアル化プロセスを使用して、スキーマが変更された場合にデータがどのようにバックフィルされるかを制御できます。

私はここのこのリンクからこれについて学びました


0

テーブルを分割するだけでいいのですか?

「SQL Server 2008を使用したパーティションテーブルとインデックスの戦略データベーステーブルのサイズが数百ギガバイト以上に増加すると、新しいデータのロード、古いデータの削除、インデックスの維持がより困難になる可能性があります。テーブルのサイズのみでは、このような操作にかかる時間が大幅に長くなります。ロードまたは削除する必要のあるデータでさえ非常に大きくなる可能性があるため、テーブルでのINSERTおよびDELETE操作は実用的ではありません。MicrosoftSQL Server 2008データベースソフトウェアでは、そのような操作を管理しやすくするためにテーブルパーティションを提供しています。」


はい、テーブルを分割できますが、履歴データを処理するときの標準ですか?履歴データをアクティブデータと同じテーブルに含める必要がありますか?これらは私が議論したかった質問です。それはSQL Server 2008に関係するので、これはまた、任意ではありません
アーロン

0

本当の問題は、履歴データとアクティブデータを一緒にレポートに使用する必要があるかどうかです。その場合は、それらを1つのテーブルに保持し、アクティブなクエリで使用するアクティブなレコードのビューを分割して作成します。(法的問題などを調査するために)時々それらを見るだけでよい場合は、それらを別のテーブルに配置します。


2
それはすることはより困難であるJOINカップルの履歴レポートには二つのテーブルまたはそれはより困難な歴史的な懸念を認識するすべての単一のテーブルの挿入/更新/削除を修正するのですか?実際には、監査ログには履歴テーブルの現在のデータさえ含まれるため、レポートで現在のテーブルを使用する必要はありません。

0

別のオプションは、[毎日|毎時|何でも]ベースで運用データをアーカイブすることです。ほとんどのデータベースエンジンは、アーカイブへのデータの抽出をサポートしています

基本的に、アイデアは、スケジュールされたWindowsまたはCRONジョブを作成することです。

  1. 運用データベースの現在のテーブルを決定します
  2. すべてのテーブルからすべてのデータをCSVまたはXMLファイルに選択します
  3. エクスポートされたデータをZIPファイルに圧縮します。アーカイブを容易にするために、ファイル名に世代のタイムスタンプを付けるのが望ましいです。

多くのSQLデータベースエンジンには、この目的に使用できるツールが付属しています。たとえば、LinuxでMySQLを使用する場合、CRONジョブで次のコマンドを使用して抽出をスケジュールできます。

mysqldump --all-databases --xml --lock-tables=false -ppassword | gzip -c | cat > /media/bak/servername-$(date +%Y-%m-%d)-mysql.xml.gz

2
誰かが値を変更してアーカイブサイクル内で値を元に戻すと、その更新は失われるため、これは履歴データにはまったく適切ではありません。また、時間の経過に伴う1つのエンティティへの変更を確認したり、エンティティを部分的に復元したりする簡単な方法もありません。
Sgoettschkes 2017年

0

私はこの古い投稿を知っていますが、いくつかのポイントを追加したかっただけです。このような問題の基準は、状況に最も適したものです。このようなストレージの必要性、および履歴/監査/変更追跡データの潜在的な使用を理解することは非常に重要です。

監査(セキュリティ目的):すべての監査可能なテーブルに共通のテーブルを使用します。値フィールドの前と後の列名を格納する構造を定義します。

Archive / Historical:以前の住所、電話番号などを追跡する場合など、別のテーブルを作成する場合FOO_HISTは、アクティブなトランザクションテーブルスキーマが将来大幅に変更されない場合に適しています(履歴テーブルを同じ構造にする必要がある場合)。テーブルの正規化、列のデータ型変更の追加/削除が予想される場合は、履歴データをxml形式で保存します。次の列(ID、日付、スキーマバージョン、XMLData)を持つテーブルを定義します。これにより、スキーマの変更を簡単に処理できます。ただし、xmlを処理する必要があるため、データの取得がある程度複雑になる可能性があります。



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