當一切正常時,沒有必要特別留意什么是事務日志,它是如何工作的。你只要確保每個數據庫都有正確的備份。當出現問題時,事務日志的理解對於采取修正操作是重要的,尤其在需要緊急恢復數據庫到指定點時。這系列文章會告訴你每個DBA應該知道的具體細節。
雖然我們想回避它,去討論下事務日志的內部結構和內部運行機制,但適當的理解下日志維護技術是有益的。這個話題在Paul Randal的講座《理解SQL Server里的日志和恢復》里已經講得很透徹,另外Kalen Delaney的《深入解析Microsoft SQL Server 2008》書里也有很詳細的講解,因此在這里我們將會簡單介紹。
虛擬日志文件(Vitual Log Files(VLF))
事務日志文件是連續的文件;換句話說,SQL Server是連續寫事務日志的(不像數據文件,它是隨機寫入的,因為數據是在隨機的數據頁里修改的)。
存儲注意事項
在寫入數據和日志文件的不同方式意味着它們也會有不同的存儲注意事項,例如對於存儲各個文件類型的硬盤,要配置合適的RAID,這個會在以后的文章里介紹。
每個插入日志文件的日志記錄會用邏輯序列號(Logical Sequence Number(LSN))標記。當數據庫和它關聯的日志文件第一次創建時,第1條日志記錄標志着邏輯文件的開始,同時也是物理文件的開始。LSN接下來也是自增長;最近增加的日志記錄總會有最大的LSN,也標志着邏輯文件的結束(等下會詳細講解)。所有日志記錄與提供的事務在LSN鏈(LSN chain)里鏈接,LSN上有指針前后指向當前操作的成功的和進行中的事務操作。
在內部,SQL Server把事務日志文件分成許多所謂的虛擬日志文件(virtual log files(VLFs))段。圖1描述了8個VLFs組成的事務日志,還有標記了日志的活動部分。
2.1 有8個VLFs的日志記錄
在第1篇文章里我們提到,任何與打開事務相關日志記錄都需要回滾的可能。另外,在數據庫里還有很多其他使用事務日志的活動(包括復制,鏡像和修改數據快照),也需要保持事務日志記錄直到這些活動已經處理。在圖2.1里顯示的最小LSN的日志記錄,是定義為“用作成功的數據庫范圍回滾或數據庫里其他活動和操作的最老記錄“。有時候它們也稱為日志“頭”。
活動日志記錄的原因
活動事務可以讓日志記錄活動,除此之外,還有很多原因讓它活動。這個在接下來的文章都會談到。
現在可以說,如果一條日志記錄對於任何事務或活動需要的話,這個日志記錄就是活動的,它對應的VLF也是活動的一部分。
如圖2.1所示,最近的日志記錄總有最大LSN,這標志着日志的邏輯尾。接下來的記錄都會寫在日志邏輯尾。在最小LSN和和最大LSN之間的文件部分稱為活動日志(active log)。
活動日志不包含“活動”(例如打開)事務的細節,這很重要。例如,假設有個開始於上午9點,持續30分鍾執行的打開事務(T1)日志記錄定義了最小LSN。如果接下來的事務(T2)開始於上午9點10分,結束於9點11分,它還會是活動日志的一部分,因為相關日志記錄的LSN比最小LSN大。在上午9點30分,當T1提交時,對於開始於上午9點25分的打開事務(T3)的日志記錄會有新的最小LSN。到這時,對於T2的日志記錄不會是活動日志的一部分。
任何包含活動日志任何部分的VLF都被認為是活動VLF。例如,圖2.1的VLF3就是個活動VLF,即使它包含的大部分日志記錄不是活動日志的一部分。一旦事務開始並提交,我們可以(簡單)想象出日志頭會在圖2.1里從左往右移動,因此剛才包含活動記錄部分的VLFS現在變成了不活動(VLF1和VLF2),剛才沒用到的(VLF8)的VLFS會變成活動日志部分。
標記一個VLF為“不活動”具體做什么取決於數據庫使用的恢復模式,接下來我們會談到。
日志截斷與空間重用
這里要注意的重點是日志文件里截斷的最小單位不是各個日志記錄或日志塊,是VLF。如果在VLF里的一條日志記錄還是活動日志部分,那么整個VLF被認為是活動的且不能被截斷(cannot be truncated)。
一把來說,一個VLF會是2個物理狀態的1個:活動(active)或不活動(inactive)。但是,基於VLF的可能不同“行為”,我們可以分出4個邏輯狀態:
- 活動(Active)——這個狀態的VLF是活動的,因為它至少包含活動日志部分的一條記錄,因此它需要被回滾或其他目的。
- 可恢復(Recoverable)——這個狀態的VLF是不活動的,但沒被截斷或備份,空間不可以重用。
- 可重用(Reusable)——這個狀態的VLF是不活動的,它已被截斷或備份,空間可以重用。
- 未使用(Unused)——這個狀態的VLF是不活動的,在它里面還沒有被記錄的日志記錄。
把一個VLF標記為不活動——按照我們的邏輯狀態,這表示從狀態2切換到狀態3——被稱為日志截斷(log truncation)。
這個日志截斷什么時候發生取決於使用的恢復模式。當數據庫在簡單(Simple)恢復模式時,活動的VLF在檢查點操作時變成不活動。當檢查點發生時,緩存中的任何臟頁寫回到硬盤,然后日志中的空間變成可重用。
但是,在完整(FULL)或大容量日志(BULL LOGGED)模式里,只有日志備份可以把活動的VLF變成不活動。這樣的話,一旦日志備份已完成備份,任何VLFs不在需要是不活動的,因此是可重用的。
在圖2.2,我們看看到檢查點(或日志備份)的結果,VLF1和VLF2已被截斷且是不活動。活動日志的開始現在是VLF3的開始,VLF8還從未用過,因此它是不活動的(狀態4)。
2.2 截斷后,有8個VLF的事務日志
下一個要考慮的問題,當活動日志到達VLF7尾時會發生什么。日志文件中空間簡單來想都是反復被重用的,盡管有讓空間重用模式變得相當專制的復雜因素,對此在這個系列文章里我們不會深入探討。
不過,在最簡單的情況下,一旦日志的邏輯尾到達VLF尾,SQL Server會開始重用接下來順序的不活動VLF。在圖2.1,會是VLF8。當VLF8滿了,它會回繞重用VLF1和VLF2。如果沒有更多的可用VLF,日志會需要自動增長,增加更多的VLF。由於自動增長被停用或磁盤提供的日志文件滿了(沒磁盤空間了),活動日志的邏輯尾會遇上日志文件的邏輯尾,事務日志滿了,就會發生9002錯誤。
這個架構解釋了原因,例如,一個長時間運行的事務,或由於某個原因復制的事務沒有發送到派發的數據庫,或者是丟失連接的鏡像,還有其它的原因,會造成日志增長非常大。例如,考慮下圖2.2,關聯最小LSN的事務非常長時間運行。日志已經包裹了,填滿了VLF1,VLF2和VLF8,沒有不活動的VLF了。即使在最小LSN后的每個事務已提交,在這些VLFs沒有可重用的空間,因為所有的的VLF還是活動日志部分。
我們很容易演示這個例子。首先,重新執行1.1代碼刪除和重建TestDB數據庫。
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) ;
然后創建一個小表,在顯性事務里更新表里的行,把事務停留在打開狀態(不要提交它)。
1 USE TestDB 2 CREATE TABLE Test 3 ( 4 num INT, 5 Filler CHAR(8000) 6 ) 7 GO 8 9 INSERT INTO dbo.Test 10 ( num, Filler ) 11 VALUES ( 1, -- num - int 12 REPLICATE('A', 8000) -- Filler - char(8000) 13 ) 14 15 BEGIN TRAN 16 UPDATE dbo.Test SET Filler=REPLICATE('B',8000) WHERE num=1 17 18 DBCC SQLPERF(LOGSPACE) ; 19 20 --ROLLBACK
在新開會話里,進行數據庫完整備份,插入100萬條記錄后,查看日志空間占用。
1 -- full backup of the database 2 BACKUP DATABASE TestDB 3 TO DISK ='C:\Backups\TestDB.bak' 4 WITH INIT; 5 GO 6 7 USE TestDB ; 8 GO 9 IF OBJECT_ID('dbo.LogTest', 'U') IS NOT NULL 10 DROP TABLE dbo.LogTest ; 11 --===== AUTHOR: Jeff Moden 12 --===== Create and populate 1,000,000 row test table. 13 -- "SomeID" has range of 1 to 1000000 unique numbers 14 -- "SomeInt" has range of 1 to 50000 non-unique numbers 15 -- "SomeLetters2";"AA"-"ZZ" non-unique 2-char strings 16 -- "SomeMoney"; 0.0000 to 99.9999 non-unique numbers 17 -- "SomeDate" ; >=01/01/2000 and <01/01/2010 non-unique 18 -- "SomeHex12"; 12 random hex characters (ie, 0-9,A-F) 19 SELECT TOP 1000000 20 SomeID = IDENTITY( INT,1,1 ), 21 SomeInt = ABS(CHECKSUM(NEWID())) % 50000 + 1 , 22 SomeLetters2 = CHAR(ABS(CHECKSUM(NEWID())) % 26 + 65) 23 + CHAR(ABS(CHECKSUM(NEWID())) % 26 + 65) , 24 SomeMoney = CAST(ABS(CHECKSUM(NEWID())) % 10000 / 100.0 AS MONEY) , 25 SomeDate = CAST(RAND(CHECKSUM(NEWID())) * 3653.0 + 36524.0 AS DATETIME) , 26 SomeHex12 = RIGHT(NEWID(), 12) 27 INTO dbo.LogTest 28 FROM sys.all_columns ac1 29 CROSS JOIN sys.all_columns ac2 ; 30 31 DBCC SQLPERF(LOGSPACE) ;
進行日志備份。
1 -- now backup the transaction log 2 BACKUP LOG TestDB 3 TO DISK ='C:\Backups\TestDB_log.bak' 4 WITH INIT ; 5 GO 6 7 DBCC SQLPERF(LOGSPACE) ;
可以看到,日志備份后,空間不會被重用。
我們把剛才的事務回滾。
1 ROLLBACK
再次備份日志。
1 -- now backup the transaction log 2 BACKUP LOG TestDB 3 TO DISK ='C:\Backups\TestDB_log.bak' 4 WITH INIT ; 5 GO 6 7 DBCC SQLPERF(LOGSPACE) ;
可以看到,日志空間已經被重用了。
在這樣的情況下,如果活動日志占用的“區域”很大,大量的空間沒被重用,這樣的話,日志文件大小會增大(增長並增長……)。可以延遲日志文件截斷的其他因素會在第8篇——救命,我的日志滿了里討論。
太多虛擬日志文件?
一般來說,SQL Server決定VLF的大小優化和個數分配。但是,頻繁自動增長的事務日志,在小增量里會有很大數的小VLF。這個現象稱為日志碎片(log fragmentation),我們可以通過第1篇里的代碼演示下,同時可以使用DBCC LogInfo命令來詢問VLF架構。
使用DBCC LogInfo詢問日志信息
DBCC LogInfo是個未公開且不支持的命令——微軟對此很少介紹。但是,它可以用來詢問VLFs。它每個VLF返回一行,除了別的以外,還有VLF的狀態。0值狀態表示這個VLF是可用的(在狀態3和4里),如上介紹,2值狀態表示這個是不可用的(在狀態1和2里)。在后續的文章我們會詳細介紹它。
我們刪除並重建TestDB數據。
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 ) ;
然后運行DBCC LogInfo命令。
1 -- how many VLFs? 2 DBCC Loginfo 3 GO
現在我們並沒有返回任何有意義的列;4行返回表示我們有4個VLF。現在我們往表里插入100條記錄到TetsDB數據庫的LogTest表,再次執行DBCC LogInfo命令。
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 ;
1 USE TestDB 2 -- how many VLFs? 3 DBCC Loginfo 4 GO
現在我們有131行返回,這表示我們有131個VLF!來自model數據庫的增長屬性決定日志文件有非常小的初始大小,然后在相對小的增量里增長。對於數據庫對象,對於這里活動,這些屬性是不合適的,會導致大量的VLF。
日志文件碎片對性能會造成很大的影響,尤其在災難恢復,還原,日志備份;換句話說,這些操作會讀日志文件。我們會在第7篇——事務日志的大小和增長詳細討論它,還會展示如何通過修正日志文件的大小來避免碎片。但是,為了給你它會有的影響的大致情況,Lichi Shea已經演示了:但與16個VLF的數據庫和20000個VLF的數據相比,在進行數據修改的性能上會帶來很大的影響。
最后,在一個日志文件里VLF的合理個數問題取決於日志的大小。一般來說,微軟認為超過200個VLF就要關注它造成問題的可能,但在很大的日志文件里(例如500GB)只有200個VLF也會是個問題,這些VLF太大,限制令空間重用。Kimberly Tripp的“事務日志VLF——太多還是太少”詳細討論了這個問題。
小結
在這篇文章里,我們按照事務日志架構的談了一些背景知識,我們很有必要理解這些知識,我們也談了日志文件的截斷、空間重用和碎片等潛在問題。
在下篇文章里,我們會詳細談到在數據庫還原與恢復里,事務日志是如何使用的。