當一切正常時,沒有必要特別留意什么是事務日志,它是如何工作的。你只要確保每個數據庫都有正確的備份。當出現問題時,事務日志的理解對於采取修正操作是重要的,尤其在需要緊急恢復數據庫到指定點時。這系列文章會告訴你每個DBA應該知道的具體細節。
事務日志是存儲對應數據庫所有事務和數據修改記錄的文件(每個數據庫都有對應的日志文件)。在造成SQL Server意外關閉的災難事件里,例如實例或硬件故障,事務日志用來恢復數據庫,用來保證數據的完好無損(完整性)。在重啟前,數據庫進入恢復過程,事務日志被讀取保證所有有效,提交的數據寫入數據文件(前滾)而生效,未提交的事務會撤銷(回滾)。簡單來說,事務日志是SQL Server保證數據庫完整性和事務ACID(原子性、一致性、隔離性、持久性)屬性,尤其是持久性的基本架構。
根據事務日志管理DBA的一些重要職責如下:
- 選擇正確恢復模式——SQL Server提供3個恢復模式:完整(默認),簡單和大容量日志。DBA必須根據業務需求對數據庫選擇正確的模式,然后用這個模式建立合適的維護流程。
- 進行事務日志備份——除非使用簡單模式,DBA進行常規的事務日志備份非常重要。一旦拿到備份文件,日志文件可以隨后應用到一個完整備份來進行數據庫恢復,因此可以重建先前某個時間點存在的數據庫,例如剛好在故障前。
- 監視管理日志增長——在忙碌的數據庫中事務日志大小可以很快的增長。如果沒有定期備份,或者大小不合適,或者指定不正確的增長率,事務日志文件會被填滿,導致臭名昭著的“9002”(事務日志已滿)錯誤,它把SQL Server進入“只讀”模式(或“資源等待”模式,這只在恢復期間發生)。
- 優化日志讀寫和可用性——另外對於例如備份的基本維護外,DBA必須采取措施來保證事務日志的達到預期的功能。這包括硬件方面的考慮,也有避免例如日志碎片的情況,它會影響事務性能。
在這個系列文章里,我們會關注這些核心維護工作的每個細節。這里,第一篇,我們從SQL Server如何使用事務日志開始,還有影響DBA生活的最重要的2個方式,即數據庫恢復與磁盤空間管理。
SQL Server如何使用事務日志
在SQL Server里,事務日志是個物理文件,按照常理來說是LDF的擴展名,當並不強制。在數據庫創建的時候會自動生成,隨同主數據文件一起,一般是MDF的擴展名,但任何擴展名也是可以使用的,它存儲數據庫對象和數據本身。事務日志,一般以一個單獨物理文件生成,也可以用多個文件生成。但是,在多個文件的情況下,SQL Server還是把它當作簡單的序列文件,就其本身而論,SQL Server不能並行寫入多個日志文件,因此使用多個日志文件並不會帶來性能上的提升。這個會在第7篇——事務日志的的大小和增長里詳談。
任何時候T-SQL代碼對數據庫對象或它包含的數據做出的改變(DDL),不但在數據文件里數據和對象會更新,而且在事務日志里修改的細節會作為日志記錄(log record)進行記錄。每個日志記錄包含細節,關於進行修改的事務ID,事務何時開始和結束,哪些頁被修改,哪些數據被修改等等。
備注:事務日志並不是審計跟蹤。它不提供對數據庫做出改變的審計跟蹤;他不保持對數據庫已執行命令的記錄,只有數據如何改變的結果。
當一個數據被修改,相關的數據頁,希望是從數據緩存讀取,如果他們還沒在緩存的話會首先從磁盤重新獲取。在數據緩存里數據被修改,在日志緩存里,描述事務影響的日志記錄被創建。當一個事務被提交,日志記錄會寫入磁盤上的事務日志。但是,實際的數據改變還沒寫入磁盤,直到隨后數據庫檢查點(Database checkpoint)發生時。在緩存里的任何頁,自從磁盤讀取后已被修改,這樣的話緩存里數據值和磁盤上的值不一樣,這就是所謂的臟頁。這些臟頁會包含:
- 已提交的數據並寫入事務日志文件但沒寫入數據文件;
- 通過打開事務修改的數據,但還未提交(或回滾)。
定期的數據庫檢查點處理掃描數據緩存,把所有的臟頁寫入磁盤,在這個點,日志文件里的修改會在物理數據文件里體現。即使事務還是打開的情況也會發生;在檢查點期間,臟頁相關的打開事務都會寫入磁盤,即在臟頁寫入數據文件前,SQL Server總會保證這些打開事務的日志記錄都從日志緩存寫入事務日志文件。
備注:另一個掃描數據緩存的惰性寫入器(LazyWriter),也會寫臟數據頁到磁盤,除檢查點外,它會因為內存壓力而這樣做。
這里需注意的要點是日志緩存管理器總保證描述修改的日志記錄,在數據頁寫入物理數據文件前,會寫入磁盤上的事務日志。這種機制被稱為預寫式日志(Write-Ahead Logging)。它是SQL Server保證事務持久性(Durability)的基本機制(圍觀下數據庫事務的ACID屬性)。
通過總首先寫入修改到日志文件,SQL Server有可以保證所有提交事務的影響最終會在數據文件里體現的基本機制,還有源於磁盤上未完成事務的任何數據修改,例如對於哪些既不提交也不回滾的最終都不會在數據文件里體現。
如果數據庫崩潰,例如,在某個事務T1提交后,但在寫入數據文件生效前重啟了,數據庫恢復進程(databse recovery process)會初始化,嘗試調節事務日志文件和數據文件內容的一致。它會讀取事務日志文件並保證事務T1的記錄在日志文件里的所有操作,被“前滾”(重做)這樣的話,它們會在數據文件里體現。
同樣,數據庫崩潰后,恢復進程會“后滾”(撤銷)數據庫里的任何未提交事務的數據修改,通過從日志文件里讀取相關操作,在數據上進行逆物理操作。
在崩潰事件里,這個方式里,SQL Server會返回數據庫到一個一致的狀態。一般來說,回滾(撤銷)進程會在下列情況發生:
- 對於顯性事務的ROLLBACK命令
- 發生錯誤且XACT_ABORT設置打開
- 如果數據庫檢測到數據庫和客戶端策動事務的通訊中斷
在這個情況下,日志記錄屬於中斷的事務,或者是顯性觸發ROLLBACK命令的事務,這些日志記錄會被讀取並回滾修改。在這些方式里,SQL Server保證事務相關的所有操作都作為一個單元要么全部成功,要么全部失敗。同樣在日常操作期間,事務日志是SQL Server重現它的一個基礎,用來保證數據一致性(consistency)和完整性(integrity)。
另外,事務日志扮演了另一個重要角色,在災難發生時,它提供數據庫可以恢復到先前時間點的機制。使用合適的計划和管理,在它們損壞或不可用時,你可以使用這些日志文件備份來恢復你所有數據到某個時間點。
事務日志和數據庫恢復
剛才提到,一個事務日志文件存儲一系列的日志記錄,根據事務開始的時間排序,它提供了修改的歷史記錄和已對數據庫發出的事務。每個日志記錄包含進行改變事務的ID,事務什么時候開始和結束,那些頁被修改,對數據所做的修改等等。事務日志文件里日志記錄由多個部分組成,即虛擬日志文件(Virtual Log Files(VLFs)),這個會在第2篇——事務日志架構里詳細講解。
SQL Server的預寫式日志(write-ahead logging)機制保證修改的描述(例如日志記錄)會在數據本身修改寫入數據文件前寫入VLF。因此,一個日志記錄會包含關閉(已提交)事務或打開(未提交)事務的詳細信息,在數據被事務修改的不同情況下,會已經或沒有寫入數據文件,取決於檢查點是否已發生。
備注:通過定期將臟頁從緩存寫入磁盤,數據庫檢查點過程控制着數據庫恢復操作期間的工作量。如果SQL Server需要為大量臟頁相關的提交事務前滾做出改變,恢復過程會非常長。
在恢復期間,和打開事務相關的任何日志記錄都需要回滾操作,且總會稱為所謂活動(active)VLF的一部分,在日志文件里都會保留。和關閉事務相關的日志記錄也會成為活動VLF的一部分,直到到達檢查點,在整個VLF里沒有打開事務相關的日志記錄,在這個點VLF變成不活動(inactive)。
在這些不活動的日志記錄里本質上提供了先前已完成的數據庫事務的“歷史”,對這些不活動的VLFs的不同操作取決於數據庫的恢復模式。我們會在第3篇到第6篇詳細討論這些恢復模式,但這里的關鍵點是,如果你使用完整(或大容量日志)數據庫恢復模式,事務日志在不活動的VLFs保留日志記錄,直到(最近不久)進行了一次日志備份。
通過備份事務日志,我們可以在活動日志里的所有日志文件,也包括不活動VLFs里的日志文件,捕獲到備份文件。這些日志備份可以用來恢復你的數據庫到先前的某個時間點;甚至很有希望恢復到”災難“發生前的一個時間點。在災難這樣的事件里,日志備份文件可以追加到完全數據庫備份的副本,在數據庫恢復期間,在完整備份后發生的任何事務都會”前滾“,恢復數據庫並恢復到給定的時間點,因此會最小化任何數據丟失。當然,這是假設你已經做這些日志備份,並把它們傳送到一個安全的地方。如果你的日志備份文件和活動日志文件在同個硬盤,那個硬盤崩潰后,你也會丟失你的備份。
當數據庫在簡單恢復模式(在第3篇-第4篇會詳細介紹),在活動VLFs里的日志記錄會保留,因為它們對於回滾操作需要。但是,當檢查點發生時,活動的VLFs會被清理,這就是說在這些VLFs里日志記錄會被新的日志記錄覆蓋。這就是為什么簡單恢復模式的數據庫操作指的是自動截斷模式,在日志里沒有“歷史”維護,因此它不能在日志備份里捕獲,也不能作為恢復的一部分。
控制日志文件大小
希望剛才的介紹已經讓你明白,對於大多數運行在完整恢復模式的生產數據庫,進行定期的日志文件備份是必須的,這樣可以讓數據庫恢復到特定的時間點。
當在完整(或大容量日志)模式下,還有一點重要的原因需要進行定期的日志文件備份,那就是控制日志文件大小。記住,在SQL Server數據庫里,對於每個修改數據或對象的事務都有日志記錄寫入日志文件。在忙碌的系統里,會有很多並發的事務,或者一個事務寫入很多數據,事務日志文件會增長會非常快。
當在完整(或大容量日志)模式下,將非活動VLFs里的日志記錄備份到文件,是唯一截斷這些活動VLFs的方法,就是說這些被日志記錄占用的空間可以被重用。
對於截斷和事務日志大小的備注:截斷日志文件有個常規誤區:日志記錄被刪除,文件大小會變小。不是這樣的:日志文件截斷只是標記這些空間可以重用。截斷,在不同恢復模式下的上下文里,會在接下來的文章里詳細討論。
因此,當在完整(或大容量日志)模式下進行定期的事務日志備份的重要原因是控制日志文件的大小。
事務日志備份的簡單例子
為了簡單演示下在這篇文章里我們已經討論的概念,我們會舉一個完整恢復模式下的數據庫的事務日志備份例子。在接下來的文章里,每個流程和命令會詳細講解。
在1.1的代碼里,我們在SQL Server 2008實例上創建一個新的TestDB數據庫,使用DBCC SQLPERF(LOGSAPCE)來獲得日志日志文件大小。
1 USE master ; 2 IF EXISTS ( SELECT name 3 FROM sys.databases 4 WHERE name = 'TestDB' ) 5 DROP DATABASE TestDB ; 6 CREATE DATABASE TestDB ON 7 ( 8 NAME = TestDB_dat, 9 FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TestDB.mdf' 10 ) LOG ON 11 ( 12 NAME = TestDB_log, 13 FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TestDB.ldf' 14 ) ; 15 DBCC SQLPERF(LOGSPACE) ;
可以看到,當前日志文件大小近1MB,30%滿。
備注:在實例上創建的用戶數據庫的初始大小和增長屬性是由model數據庫的屬性決定的,默認情況下每個數據庫使用的恢復模式是(完整,在這里)。我們會在第7篇——事務日志的大小和增長詳細講解這些屬性的影響。
我們可以到本地硬盤的對應目錄下確認下文件的大小。
現在我們對TestDB進行數據文件的備份。(你首先要創建“Backups”目錄)。注意這個備份操作是在完整恢復模式下進行;在第3篇——事務日志,備份和恢復會有更多詳細講解。
1 -- full backup of the database 2 BACKUP DATABASE TestDB 3 TO DISK ='C:\Backups\TestDB.bak' 4 WITH INIT; 5 GO
備份完成后,數據文件、日志文件的大小及日志空間的使用率都沒有改變,這一點也不奇怪,因為當前在數據庫沒有用戶表和數據。我們在數據庫上創建LogTest表,往表里插入100萬條記錄,再檢查下數據庫大小。不要擔心代碼的細節;這里最重要的是我們插入了很多行。這個代碼在你電腦上可能會運行幾十秒,並不是因為代碼並不高效:它只是在后台運行,寫入數據和日志文件。
1 USE TestDB ; 2 GO 3 IF OBJECT_ID('dbo.LogTest', 'U') IS NOT NULL 4 DROP TABLE dbo.LogTest ; 5 --===== AUTHOR: Jeff Moden 6 --===== Create and populate 1,000,000 row test table. 7 -- "SomeID" has range of 1 to 1000000 unique numbers 8 -- "SomeInt" has range of 1 to 50000 non-unique numbers 9 -- "SomeLetters2";"AA"-"ZZ" non-unique 2-char strings 10 -- "SomeMoney"; 0.0000 to 99.9999 non-unique numbers 11 -- "SomeDate" ; >=01/01/2000 and <01/01/2010 non-unique 12 -- "SomeHex12"; 12 random hex characters (ie, 0-9,A-F) 13 SELECT TOP 1000000 14 SomeID = IDENTITY( INT,1,1 ), 15 SomeInt = ABS(CHECKSUM(NEWID())) % 50000 + 1 , 16 SomeLetters2 = CHAR(ABS(CHECKSUM(NEWID())) % 26 + 65) 17 + CHAR(ABS(CHECKSUM(NEWID())) % 26 + 65) , 18 SomeMoney = CAST(ABS(CHECKSUM(NEWID())) % 10000 / 100.0 AS MONEY) , 19 SomeDate = CAST(RAND(CHECKSUM(NEWID())) * 3653.0 + 36524.0 AS DATETIME) , 20 SomeHex12 = RIGHT(NEWID(), 12) 21 INTO dbo.LogTest 22 FROM sys.all_columns ac1 23 CROSS JOIN sys.all_columns ac2 ; 24 DBCC SQLPERF(LOGSPACE) ;
可以看到日志文件已經膨脹到近100MB,日志已經近93%滿(每個數據庫上看到的結果都會不一樣)。如果我們插入更多的數據,它會繼續增長來容納更多的數據,文件大小我們可以在本地硬盤的對應目錄上進行確認(數據文件已經增長到54M)。
我們可以用剛才的代碼再次備份數據文件,同樣也不會對數據文件、日志文件和日志空間使用率造成影響。現在我們備份下事務日志文件,再次檢查下文件大小。
1 -- now backup the transaction log 2 BACKUP Log TestDB 3 TO DISK ='C:\Backups\TestDB_log.bak' 4 WITH INIT; 5 GO 6 DBCC SQLPERF(LOGSPACE) ;
日志文件還是一樣的物理大小,但是通過備份文件,SQL Server可以截斷日志,在“不活動”VLFs里日志文件標記空間作為重用;更多的日志記錄會被添加而不需要物理增長文件。當然還有,我們把日志捕獲到備份文件,這樣的話我們可以使用這個文件作為數據庫恢復進程,如果我們想把TestDB數據庫恢復到上一狀態。
小結
在這篇文章里,我們介紹了事務日志,解釋了SQL Server如何使用它來維護數據的一致性(consistency)和完整性(integrity),通過預寫式日志(write-ahead logging)機制。我們也介紹了並簡潔演示了,一個DBA如何捕獲事務日式文件內容到備份文件,它可以被重用來恢復數據庫,作為恢復過程的一部分。最后,我們強調了備份在控制事務日志文件大小的重要性。
在下一篇文章,我們會進一步看下事務日志的架構。