這兩個 SQL Server 回滾有何不同?
在 SQL Server 2008 R2 中,這兩個回滾有何不同:
- 執行
ALTER
語句幾分鐘,然後點擊“取消執行”。完全回滾需要幾分鐘。- 執行相同的
ALTER
語句,但這要確保LDF
文件不夠大,無法成功完成。一旦達到LDF
限制並且不允許“自動增長”,查詢執行將立即停止(或發生回滾)並顯示以下錯誤消息:The statement has been terminated. Msg 9002, Level 17, State 4, Line 1 The transaction log for database 'SampleDB' is full. To find out why space in the log cannot be reused, see the log_reuse_wait_desc column in sys.databases
這兩者在以下幾點上有什麼不同?
- 為什麼第二次“回滾”是瞬時的?我不完全確定它是否可以稱為回滾。我的猜測是,事務日誌是隨著執行的進行而寫入的,一旦它意識到沒有足夠的空間來完全完成任務,它就會停止並顯示一些“結束”消息,而無需送出。
- 當第一次回滾花費這麼多時間時會發生什麼(回滾是單執行緒的)?
2.1。SQL Server 是否返回並撤消
LDF
文件中的條目?2.2.
LDF
回滾結束時文件大小變小(從DBCC SQLPERF(LOGSPACE)
) 3. 另一個問題:在第二種情況下,SQL ServerLDF
很快就開始使用文件。就我而言,它在最初的幾分鐘(< 4 分鐘)內從 18% 的使用率增加到 90% 的使用率。但是一旦達到 99%,它又在那裡停留了 8 分鐘,同時使用率在 99.1% 到 99.8% 之間波動。在拋出錯誤之前,它會上升(99.8%)和下降(99.2%),然後再上升(99.7%)和下降(99.5%)幾次。幕後發生了什麼?感謝任何可以幫助解釋這一點的 MSDN 連結。
在 Ali Razeghi 的建議下,我添加了 perfmon :
Disk Bytes/sec
場景一:
場景二:
如上所述,在執行了更多測試之後,我得出了一個經過計算的結論。我在這裡將所有這些內容匯總到一篇博文中,但我會將一些內容複製到這篇博文中以供後人使用。
猜想(基於一些測試)
到目前為止,我還沒有明確的解釋為什麼會這樣。但以下是我基於測試期間收集的工件的估計。
回滾在這兩種情況下都會發生。一種是顯式回滾(使用者點擊取消按鈕),另一種是隱式回滾( Sql Server 在內部做出該決定)。
在這兩種情況下,流向日誌文件的流量是一致的。請參閱下面的圖片:
場景一:
場景二:
強化這種構想的一個人工製品是在這兩種情況下擷取 Sql Trace。
- 場景 1 是不言而喻的,也就是當我們點擊“取消”時,它會回滾。
- 在場景 2 中,在隱式執行“回滾”後顯示錯誤消息。在 Sql Trace 中,我們看到錯誤消息“數據庫 ‘SampleDB’ 的事務日誌已滿”,該消息在螢幕上顯示很長時間之前。所以,我的猜測是回滾在這兩種情況下都會發生,但是錯誤消息是在成功並完全執行回滾後顯示場景 2。
場景 2 似乎需要更長的時間,因為它進展得更遠,所以回滾需要更長的時間。
無法解釋的行為:
為什麼日誌文件的使用變化如此之大?
- 它增加到 90%,然後下降到 85%,然後上升到 99%,並在那裡徘徊了很長時間。我多次看到它像這樣上下波動:99.2%、99.8%、99.1%、99.7%。為什麼會這樣?
- 一種可能的解釋是,可能有一個後台程序(類似於 Log Flush)每隔幾分鍾清理一次日誌文件。每次啟動時,都會清除一些條目,從而產生更多可用空間。
歡迎任何有助於以更好的方式解釋這種行為的想法。
我嘗試了以下實驗並得到了類似的結果。在這兩種情況下,fn_dblog() 都顯示發生了回滾,並且在場景 2 中似乎比在場景 1 中發生得更快。
順便說一句,我將 MDF 和 LDF 放在同一個外部 (USB 2.0) 磁碟上。
我最初的結論是,在這種情況下,回滾操作沒有區別,並且可能任何明顯的速度差異都與 I/O 子系統有關。這只是我目前的工作假設。
場景一:
- 創建一個數據庫,其日誌文件從 1MB 開始,以 4MB 塊增長,最大大小為 100MB。
- 打開顯式事務,執行 10 秒,然後在 SSMS 中手動取消
- 查看 fn_dblog() 計數和日誌保留大小並查看 DBCC SQLPERF(LOGSPACE)
場景二:
- 創建一個數據庫,其日誌文件從 1MB 開始,以 4MB 塊增長,最大大小為 100MB。
- 打開一個顯式事務,執行它直到日誌已滿錯誤出現
- 查看 fn_dblog() 計數和日誌保留大小並查看 DBCC SQLPERF(LOGSPACE)
性能監視器結果:
程式碼:
使用[主]; 走 IF DATABASEPROPERTYEX (N'SampleDB', N'Version') > 0 開始 ALTER DATABASE [SampleDB] SET SINGLE_USER 立即回滾; 刪除數據庫 [SampleDB]; 結尾; 走 在 PRIMARY 上創建數據庫 [SampleDB] ( NAME = N'SampleDB' , 文件名 = N'E:\data\SampleDB.mdf' , 大小 = 3MB , 文件增長 = 1MB ) 登錄 ( NAME = N'SampleDB_log' , 文件名 = N'E:\data\SampleDB_log.ldf' , 大小 = 1MB , 最大尺寸 = 100MB , 文件增長 = 4MB ); 走 使用[SampleDB]; 走 -- 添加表格 創建表 dbo.test ( c1 CHAR(8000) NOT NULL DEFAULT REPLICATE('a',8000) ) 在 [主要] 上; 走 -- 確保我們不是偽簡單的恢復模型 備份數據庫 SampleDB 到磁碟 = '零'; 走 -- 備份日誌文件 備份日誌範例數據庫 到磁碟 = '零'; 走 -- 檢查已使用的日誌空間 DBCC SQLPERF(日誌空間); 走 -- 使用 fn_dblog() 可以看到多少條記錄? SELECT * FROM fn_dblog(NULL,NULL); -- 在我的情況下大約 9 /********************************** 場景 1 **********************************/ -- 打開一個新事務然後回滾 開始交易 插入 dbo.test 預設值; GO 10000 -- 讓我們執行 10 秒,然後在 SSMS 查詢視窗中點擊取消 -- 取消交易 -- 應該需要幾秒鐘才能完成 -- 不需要回滾事務,因為取消已經為你做了。 - 就試一試吧。你會得到這個錯誤 -- 消息 3903,第 16 級,狀態 1,第 1 行 -- ROLLBACK TRANSACTION 請求沒有對應的 BEGIN TRANSACTION。 回滾交易; -- 使用的日誌空間是多少?100% 以上。 DBCC SQLPERF(日誌空間); 走 -- 使用 fn_dblog() 可以看到多少條記錄? 選擇 * FROM fn_dblog(NULL,NULL); -- 就我而言,大約是 91,926 -- fn_dblog() 顯示的總日誌保留? SELECT SUM([Log Reserve]) AS [Total Log Reserve] FROM fn_dblog(NULL,NULL); -- 大約 88.72MB /********************************** 情景 2 **********************************/ -- 吹走數據庫並重新開始 使用[主]; 走 IF DATABASEPROPERTYEX (N'SampleDB', N'Version') > 0 開始 ALTER DATABASE [SampleDB] SET SINGLE_USER 立即回滾; 刪除數據庫 [SampleDB]; 結尾; 走 在 PRIMARY 上創建數據庫 [SampleDB] ( NAME = N'SampleDB' , 文件名 = N'E:\data\SampleDB.mdf' , 大小 = 3MB , 文件增長 = 1MB ) 登錄 ( NAME = N'SampleDB_log' , 文件名 = N'E:\data\SampleDB_log.ldf' , 大小 = 1MB , 最大尺寸 = 100MB , 文件增長 = 4MB ); 走 使用[SampleDB]; 走 -- 添加表格 創建表 dbo.test ( c1 CHAR(8000) NOT NULL DEFAULT REPLICATE('a',8000) ) 在 [主要] 上; 走 -- 確保我們不是偽簡單的恢復模型 備份數據庫 SampleDB 到磁碟 = '零'; 走 -- 備份日誌文件 備份日誌範例數據庫 到磁碟 = '零'; 走 -- 現在,讓我們炸毀事務中的日誌文件 開始交易 插入 dbo.test 預設值; 去 10000 -- 回滾永遠不會觸發。試試吧。你會得到一個錯誤。 -- 消息 3903,第 16 級,狀態 1,第 1 行 -- ROLLBACK TRANSACTION 請求沒有對應的 BEGIN TRANSACTION。 回滾交易; -- 日誌文件是否 100% 滿? DBCC SQLPERF(日誌空間); -- 使用 fn_dblog() 可以看到多少條記錄? 選擇 * FROM fn_dblog(NULL,NULL); -- 就我而言,大約是 91,926 走 -- fn_dblog() 顯示的總日誌保留? SELECT SUM([Log Reserve]) AS [Total Log Reserve] FROM fn_dblog(NULL,NULL); -- 88.72MB 走