テンポラルテーブルがトランザクションの開始時間を記録するのはなぜですか?


8

テンポラルテーブルの行を更新するとき、行の古い値は、トランザクションの開始時刻をとして履歴テーブルに保存されますSysEndTime。現在のテーブルの新しい値には、トランザクションの開始時刻としてがありSysStartTimeます。

SysStartTimeそして、SysEndTimeされているdatetime2行が現在のバージョンであったときに記録する時間テーブルによって使用されるカラム。トランザクション開始時間は、更新を含むトランザクションが開始した時間です。

BOLさんのコメント:

システムのdatetime2列に記録される時間は、トランザクション自体の開始時間に基づいています。たとえば、単一のトランザクション内に挿入されたすべての行は、SYSTEM_TIME期間の開始に対応する列に記録された同じUTC時間を持ちます。

例: Ordersテーブルのすべての行の更新を開始20160707 11:00:00し、トランザクションの実行に5分かかります。これにより、履歴テーブルにSysEndTimeas を使用して各行の行が作成され20160707 11:00:00ます。現在のテーブルのすべての行がありますSysStartTimeのを20160707 11:00:00

誰かが20160707 11:01:00(更新の実行中に)クエリを実行すると、古い値が表示されます(デフォルトの読み取りコミット分離レベルを想定)。

しかし、誰かがAS OF構文を使用してテンポラルテーブルをそのままクエリする20160707 11:01:00と、新しい値が表示されSysStartTimeます20160707 11:00:00

これは、当時のようにそれらの行が表示されないことを意味します。トランザクションの終了時刻を使用した場合、問題は存在しません。

質問:これは仕様ですか?何か不足していますか?

トランザクションの開始時間を使用していると考えることができる唯一の理由は、それがトランザクションの開始時に唯一「既知」であるということです。開始時にトランザクションがいつ終了するかは不明であり、終了時に終了時刻を適用するのに時間がかかり、適用していた終了時刻が無効になります。これは理にかなっていますか?

これにより、問題を再現できます。


1
トランザクションの終了時間を使用する場合、トランザクションの終了時に別の更新がある場合、独自の質問に回答しました。更新が終了し20160707 11:04:58、すべての行をそのタイムスタンプで更新します。しかし、この更新も数秒間実行され、で終了します20160707 11:05:02。今、どのタイムスタンプがトランザクションの正しい終了ですか?または、で使用Read Uncommitedしていて20160707 11:05:00、行が返されたが後でAS OF表示されないと仮定します。
dnoeth 16

@dnoethええ、この「質問」は私の理論をより明確にするものだと思います。
James Anderson

私はSQL Serverの実装については詳しく知りませんでしたが、Teradataには何年にもわたってバイテンポラルテーブルがあり、TeradataのANSI SQL構文に基づいているRichard Snodgrass(時間クエリを「発明した」人)からのこのケーススタディを常に読むことをお勧めします。 :しかし、概念は同じですcs.ulb.ac.be/public/_media/teaching/infoh415/...
dnoeth

回答:


4

アイデアは、論理時間と物理時間を追跡することです。論理とは、単にユーザー/アプリが挿入/更新/削除の時間を想定しているものを指します。何らかの理由でDML操作に時間がかかる場合があるという事実は、意味がなく、ユーザーが簡単に判断して理解することさえできません。ロックとラッチの競合を会計士に説明しなければならなかった場合(私が持っている)、それは同等の状況です。

たとえば、ボブの部署のすべての従業員がで$ 42 /分を作成し始めるアプリをボブが「伝える」と20160707 11:00:00、ボブ(および彼の従業員)は、すべての人の給与がその時点から$ 42 /分で計算されることを期待します。ボブはこれが影響を受けることを気にしません、アプリは従業員ごとにデータベース全体で2回の読み取りと6回の書き込みを行う必要があり、データ+ログファイルはRAID-5 SATA IIドライブの束に置かれるため、約7分かかりますボブの全従業員256人のタスクを完了するため。ボブ、彼の会計士、および給与計算マネージャーは、彼のすべての従業員に、毎分42ドルが支払われるようにしてい20160707 11:00:00ます。さもなければ、更新された従業員は20160707 11:00:01わずかに迷惑になりますが、更新された従業員20160707 11:00:07は給与部門の外に集まります。

デバッグやフォレンジックなどの物理的な時間を追跡するための有効な使用例がありますが、エンドユーザーにとっては、通常は意味がありません。Tlogは、(特に)各書き込み操作の順序とタイミングの両方の情報を保持するため、見方を知っていればそこにあります。


素敵なポイント。このテクノロジーは、あなたが言及しているような特定のユースケースにのみ適していると思います。上記で述べた理由により、非常に短期間で変更される可能性のある価格または株価の追跡に使用するのは不適切であると思われます。
James Anderson

実は違う。これはパフォーマンスとスケールの問題です。株価の特定時点の履歴を保持する必要がある場合でも、テンポラルテーブルは機能します。挿入が非常に細かく、非常に小さなウィンドウ内で完了できることを確認する必要があります。そうしないと、その後の変更がブロックされ、受信レートが十分に高い場合、タイムアウトが発生し、アプリが再試行を処理できない場合にデータが失われる可能性があります。フュージョンIOをオフにして、またはメモリ最適化テーブルを使用してDBを実行すると、1秒あたり数万の挿入から1秒あたり10万を超える挿入を簡単に処理できます。
SQLmojoe

3

テンポラルテーブルの他のすべての既存の実装(私の知る限り)にも同じ欠陥があるため、これは実際に設計上の欠陥であると思いますが、SQL Server 2016に固有のものではありません。これが原因でテンポラル表で発生する可能性のある問題はかなり深刻です。あなたの例のシナリオは、一般的にうまくいかない場合に比べて穏やかです:

壊れた外部キー参照:2つのテンポラルテーブルがあり、テーブルAにテーブルBへの外部キー参照があるとします。ここで、2つのトランザクションがあり、どちらもREAD COMMITTED分離レベルで実行されているとします。テーブルBに行を挿入してコミットし、トランザクション1がBの新しく追加された行への参照を含む行をテーブルAに挿入します。Bへの新しい行の追加はすでにコミットされているため、外部キー制約が満たされ、トランザクション1は正常にコミットできます。ただし、トランザクション1が開始されてからトランザクション2が開始されるまでの間にデータベース "AS OF"を表示すると、存在しないBの行への参照を持つテーブルAが表示されます。この場合、テンポラルテーブルは、データベースの一貫性のないビューを提供します。もちろん、これはSQL:2011標準の意図ではありませんでした。

システムバージョン管理されたテーブルの履歴システム行は、過去の不変のスナップショットを形成します。履歴システム行が作成されたときに有効だった制約は、その行が現在のシステム行であるときにすでにチェックされているため、履歴システム行に制約を適用する必要はありません。

一意でない主キー:プライマリキーと2つのトランザクションを含むテーブルがあり、どちらもREAD COMMITTED分離レベルにあるとします。この場合、次のことが起こります。トランザクション1が開始した後、このテーブルに触れる前に、トランザクション2が特定のテーブルの行とコミット。次に、トランザクション1は、削除されたものと同じ主キーを持つ新しい行を挿入します。これは問題ありませんが、トランザクション1が開始されてからトランザクション2が開始されるまでの時間のテーブルAS OFを見ると、同じ主キーを持つ2つの行が表示されます。

同時更新のエラー:テーブルと2つのトランザクションがあり、どちらも同じ行をREAD COMMITTED分離レベルで更新するとします。トランザクション1が最初に開始されますが、トランザクション2が最初に行を更新します。次にトランザクション2がコミットし、トランザクション1が行に対して別の更新を実行してコミットします。これは問題ありませんが、これがテンポラルテーブルである場合を除き、トランザクション1で更新を実行すると、システムが必要な行を履歴テーブルに挿入しようとすると、生成されたSysStartTimeがトランザクション2の開始時間になり、SysEndTimeトランザクション1の開始時刻になります。これは、SysEndTimeがSysStartTimeより前になるため、有効な時間間隔ではありません。この場合、SQL Serverはエラーをスローし、トランザクションをロールバックします(たとえば、この議論)。これは非常に不愉快です。READCOMMITTED分離レベルでは、同時実行性の問題が完全な失敗につながるとは予想されないため、アプリケーションが再試行を行う準備が必ずしも整っていないことを意味します。特に、これはMicrosoftのドキュメントの「保証」に反しています。

この動作により、バージョニングの恩恵を受けるテーブルでシステムのバージョニングを有効にしても、レガシーアプリケーションが引き続き機能することが保証されます。(リンク

テンポラルテーブルの他の実装では、タイムスタンプが無効な場合に自動的に「調整」するオプションを提供することで、このシナリオ(同じ行を更新する2つの同時トランザクション)に対処しました(ここここを参照)。同じトランザクション内の他のステートメントでは通常、タイムスタンプが同じように調整されないため、これはトランザクションの原子性を壊すという残念な結果をもたらすため、これは醜い回避策です。つまり、この回避策では、データベースを「ある時点で」特定の時点で表示すると、部分的に実行されたトランザクションが表示される場合があります。

解決:あなたはすでに明白な解決策を提案しました、それは実装が開始時間の代わりにトランザクション終了時間(すなわちコミット時間)を使用することです。はい、トランザクションの途中でステートメントを実行しているときは、コミット時間がどうなるかを知ることは不可能です(将来の場合、またはトランザクションがロールされる場合は存在しない可能性もあります)。バック)。しかし、これはソリューションが実装不可能であることを意味するものではありません。別の方法で行う必要があります。たとえば、UPDATEまたはDELETEステートメントを実行する場合、履歴行の作成時に、システムは開始時刻の代わりに現在のトランザクションIDを入力するだけでよく、トランザクションがコミットした後、IDは後でシステムによってタイムスタンプに変換されます。 。

この種の実装のコンテキストでは、トランザクションがコミットされる前に、トランザクションが履歴テーブルに追加する行がユーザーに表示されないようにすることをお勧めします。ユーザーの観点からは、これらの行は(コミットのタイムスタンプとともに)コミット時に追加されているように見えるだけです。特に、トランザクションが正常にコミットされない場合は、履歴に表示されません。もちろん、これは履歴(タイムスタンプを含む)への挿入を(コミット時ではなく)UPDATEおよびDELETEステートメント時に発生するものとして説明するSQL:2011標準と一致しません。しかし、上記の問題のために標準が適切に実装されなかった(そしておそらく間違いなく実装できない)ことを考えると、これは本当に重要だとは思いません。

パフォーマンスの観点から、コミットタイムスタンプを入力するためにシステムが履歴行に戻って再度アクセスする必要があるのは望ましくないように思われるかもしれません。しかし、これがどのように行われるかによって、コストはかなり低くなる可能性があります。SQL Serverが内部でどのように動作するかはあまり詳しくありませんが、たとえばPostgreSQLはログ先行書き込みを使用しているため、テーブルの同じ部分で複数の更新が実行された場合、それらの更新が統合され、データは物理テーブルページに1回だけ書き込む必要があります。これは通常、このシナリオに当てはまります。とにかく、

もちろん、(私の知る限り)この種のシステムは実装されたことがないので、確実に機能するかどうかはわかりません。おそらく何か足りないものがあるかもしれませんが、理由はわかりません。なぜ機能しなかったのか。


0

トランザクションをコミットする時点では、すべてのデータをデータページ内(メモリ内とディスク上のログファイル内)に書き込む必要があります。含むSysStartTimeSysEndTime列。実際に完了する前に、トランザクションの終了時間をどのように知ることができますか?

将来を予測できない場合は、トランザクションの開始時刻を使用することが唯一の選択肢です。直感的にわかりにくい場合でも使用できます。

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