Transaction

為什麼時態表記錄事務的開始時間?

  • February 20, 2018

更新時態表中的行時,該行的舊值儲存在歷史表中,事務開始時間為SysEndTime. 目前表中的新值的事務開始時間為SysStartTime.

SysStartTime並且SysEndTimedatetime2時態表用來記錄行是目前版本的列。事務開始時間是包含更新的事務開始的時間。

波爾 說:

系統 datetime2 列中記錄的時間基於事務本身的開始時間。例如,在單個事務中插入的所有行都將在對應於 SYSTEM_TIME 週期開始的列中記錄相同的 UTC 時間。

**範例:**我開始更新 Orders 表中的所有行,20160707 11:00:00事務需要 5 分鐘才能執行。這會在歷史表中為每一行創建一行SysEndTimeas 20160707 11:00:00。目前表中的所有行都有一個SysStartTimeof 20160707 11:00:00

如果有人在20160707 11:01:00(更新正在執行時)執行查詢,他們將看到舊值(假設預設讀取已送出隔離級別)。

但是,如果有人隨後使用AS OF語法來查詢時態表,20160707 11:01:00他們將看到新值,因為他們SysStartTime將是20160707 11:00:00.

對我來說,這意味著它不會像當時那樣顯示那些行。如果它使用事務結束時間,問題就不會存在。

**問題:**這是設計使然嗎?我錯過了什麼嗎?

我認為它使用事務開始時間的唯一原因是它是事務開始時唯一的“已知”時間。它不知道事務何時開始時結束,並且在結束時應用結束時間需要時間,這會使它應用的結束時間無效。這有意義嗎?

應該允許您重新創建問題。

這個想法是跟踪邏輯時間與物理時間。邏輯只是指使用者/應用程序期望插入/更新/刪除的時間。無論出於何種原因,DML 操作都可能需要一段時間,這一事實沒有意義,甚至使用者也不容易確定和理解。如果您曾經不得不向會計師(我有)解釋鎖定與閂鎖爭用,這是一個可比較的情況。

例如,當 Bob “告訴”應用程序 Bob 部門的所有員工將20160707 11:00:00在 $ 42/min from that time. Bob doesn’t care that for this to be effected, the app has to make 2 reads and 6 writes across the database per employee and their data + log files sit on a bunch of RAID-5 SATA II drives so it takes about 7 minutes to finish the task for all 256 of Bob’s employees. Bob, his accountant and the payroll manager care that all his employees are paid $ 42/分鐘開始20160707 11:00:00。否則,更新時間的員工20160707 11:00:01會有些惱火,而更新時間的員工20160707 11:00:07將聚集在工資部門之外。

有一些有效的案例可以跟踪物理時間,例如調試和取證,但對於最終使用者來說,這通常是沒有意義的。Tlog 保留了每個寫入操作的順序和時間資訊(除其他外),因此如果您知道如何查看,它就在那裡。

我相信這確實是一個設計缺陷,儘管它並非特定於 SQL Server 2016,因為所有其他現有的臨時表實現(據我所知)都具有相同的缺陷。因此,臨時表可能出現的問題相當嚴重。與一般可能出錯的情況相比,您範例中的情況較為溫和:

外鍵引用損壞:假設我們有兩個臨時表,表 A 具有對錶 B 的外鍵引用。現在假設我們有兩個事務,都在 READ COMMITTED 隔離級別上執行:事務 1 在事務 2 之前開始,事務 2向表 B 中插入一行並送出,然後事務 1 在表 A 中插入一行,並引用 B 的新添加行。由於向 B 中添加新行已經送出,因此滿足外鍵約束並且事務1 能夠成功送出。但是,如果我們在事務 1 開始和事務 2 開始之間的某個時間查看數據庫“AS OF”,那麼我們會看到表 A 引用了不存在的 B 行。所以在這種情況下,時態表提供了一個不一致的數據庫視圖。這當然不是 SQL:2011 標準的意圖,該標準指出,

系統版本表中的歷史系統行形成了過去的不可變快照。在創建歷史系統行時生效的任何約束在該行是目前系統行時已經被檢查,因此永遠不需要對歷史系統行實施約束。

非唯一主鍵:假設我們有一個表,其中有一個主鍵和兩個事務,都處於 READ COMMITTED 隔離級別,其中會發生以下情況:在事務 1 開始之後但在它接觸到該表之前,事務 2 刪除了某個表的行和送出。然後,事務 1 插入一個新行,其主鍵與被刪除的行相同。這很好,但是當您查看事務 1 開始和事務 2 開始之間的時間時,我們會看到兩行具有相同的主鍵。

並發更新錯誤:假設我們有一個表和兩個事務,它們都更新其中的同一行,同樣在 READ COMMITTED 隔離級別。事務 1 首先開始,但事務 2 是第一個更新行的。然後事務 2 送出,事務 1 然後對行進行不同的更新並送出。這一切都很好,除瞭如果這是一個臨時表,在事務 1 中執行更新時,當系統將所需的行插入歷史表時,生成的 SysStartTime 將是事務 2 的開始時間,而 SysEndTime將是事務 1 的開始時間,這不是有效的時間間隔,因為 SysEndTime 將在 SysStartTime 之前。在這種情況下,SQL Server 會拋出錯誤並回滾事務(例如,請參閱本次討論)。這是非常不愉快的,因為在 READ COMMITTED 隔離級別,並發問題不會導致徹底失敗,這意味著應用程序不一定準備好進行重試嘗試。特別是,這違反了微軟文件中的“保證”:

當您對將受益於版本控制的表啟用系統版本控制時,此行為可確保您的舊應用程序將繼續工作。(連結

時態表的其他實現通過提供一個選項來處理這種情況(兩個並發事務更新同一行),如果時間戳無效(請參閱此處此處)自動“調整”時間戳。這是一個醜陋的解決方法,因為它具有破壞事務原子性的不幸後果,因為同一事務中的其他語句通常不會以相同的方式調整其時間戳;即,通過這種解決方法,如果我們在某些​​時間點查看數據庫“AS OF”,那麼我們可能會看到部分執行的事務。

解決方案:您已經提出了明顯的解決方案,即實現使用事務結束時間(即送出時間)而不是開始時間。是的,當我們在事務中間執行語句時,不可能知道送出時間將是多少(因為它在將來,或者如果事務要滾動,甚至可能不存在背部)。但這並不意味著解決方案無法實現;它只需要以不同的方式完成。例如,在執行 UPDATE 或 DELETE 語句時,在創建歷史記錄行時,系統可以只輸入目前事務 ID 而不是開始時間,然後系統可以在事務送出後將 ID 轉換為時間戳.

在這種實現的上下文中,我建議在送出事務之前,它添加到歷史表的任何行都不應該是使用者可見的。從使用者的角度來看,應該只是在送出時添加了這些行(帶有送出時間戳)。特別是,如果事務從未成功送出,則它不應該出現在歷史記錄中。當然,這與 SQL:2011 標準不一致,該標準將歷史記錄(包括時間戳)的插入描述為發生在 UPDATE 和 DELETE 語句時(而不是送出時)。但我認為這並不重要,考慮到由於上述問題,該標準從未正確實施(並且可以說永遠不可能),

從性能的角度來看,系統似乎不希望必須返回並重新訪問歷史記錄行以填寫送出時間戳。但根據如何完成,成本可能非常低。我不太熟悉 SQL Server 內部的工作方式,但例如 PostgreSQL 使用預寫日誌,這使得如果在表的相同部分執行多個更新,這些更新將被合併,以便數據只需要寫入物理表頁一次——這通常適用於這種情況。在任何情況下,

當然,由於(據我所知)這種系統從未實施過,我不能肯定它會起作用——也許我缺少一些東西——但我看不出有任何理由為什麼它不能工作。

引用自:https://dba.stackexchange.com/questions/143241