テンポラルテーブルの他のすべての既存の実装(私の知る限り)にも同じ欠陥があるため、これは実際に設計上の欠陥であると思いますが、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回だけ書き込む必要があります。これは通常、このシナリオに当てはまります。とにかく、
もちろん、(私の知る限り)この種のシステムは実装されたことがないので、確実に機能するかどうかはわかりません。おそらく何か足りないものがあるかもしれませんが、理由はわかりません。なぜ機能しなかったのか。
20160707 11:04:58
、すべての行をそのタイムスタンプで更新します。しかし、この更新も数秒間実行され、で終了します20160707 11:05:02
。今、どのタイムスタンプがトランザクションの正しい終了ですか?または、で使用Read Uncommited
していて20160707 11:05:00
、行が返されたが後でAS OF
表示されないと仮定します。