1. 程式人生 > >(4.19)深入理解SQLSERVER的日誌鏈

(4.19)深入理解SQLSERVER的日誌鏈

arch 多人 -a 名稱 -m 方法 便在 出了 建數據庫

您真的理解了SQLSERVER的日誌鏈了嗎?

轉自:https://www.cnblogs.com/lyhabc/p/3460272.html

先感謝宋沄劍給本人指點迷津,還有郭忠輝童鞋今天在QQ群裏拋出的問題

這個問題跟宋沄劍討論了三天,再次感謝宋沄劍

一直以來,SQLSERVER提供了一個非常好的管理工具:SSMS

又因為這個管理工具太好了,所有操作的簡單化,以至於使我們中毒太深

對於SQLSERVER內部的一些概念搞得不清不楚

比如這些概念:日誌備份鏈,備份日誌鏈,日誌鏈,備份鏈,備份集

大部分都是由於SSMS的界面所導致,有時候有些問題做一下實驗就可以驗證了,偏偏我們信賴了GUI

閱讀下文之前大家可以先看一下宋沄劍的文章

SQL Server CheckPoint的幾個誤區

再談SQL Server中日誌的的作用

SQL Server誤區30日談-Day20-破壞日誌備份鏈之後,需要一個完整備份來重新開始日誌鏈

先說清楚這些概念吧

SQLSERVER只有日誌鏈,備份記錄(有些人也叫備份鏈)本人覺得叫備份記錄更合適

下面三個東西說的都是同一樣東西

備份集=備份記錄=備份鏈

備份集:比如備份的集合,比如有對一個數據庫的完備1、差備、日備1、完備2、日備2,這些數據庫的備份的集合就是備份集

不過我更喜歡叫備份記錄

備份記錄實際上指 SELECT * FROM [msdb].[dbo].[backupset]

截斷日誌跟日誌鏈斷裂是否是同一樣東西?

截斷日誌跟日誌鏈斷裂不是同一樣東西


什麽是日誌鏈

其實大家可以把bak文件理解成一個壓縮包,完整備份差異備份的時候會把數據和日誌一起帶進壓縮包,

日誌備份的時候只會把日誌帶進壓縮包

技術分享圖片

我們先從一個實驗開始吧

測試環境:SQLSERVER2012 開發版

腳本

為了不產生額外的日誌,所以腳本裏面沒有select into語句,本來想select into進去臨時表再對臨時表進行排序

但是因為select into會產生額外的日誌,只有直接對fn_dblog進行排序了

創建數據庫

技術分享圖片 技術分享圖片
1 USE master
2 GO
3 --創建數據庫
4 CREATE DATABASE LogChainTest;
5 GO
6 --改為完整恢復模式
7 ALTER DATABASE LogChainTest SET RECOVERY FULL;
8 GO
技術分享圖片

查看當前的事務日誌

技術分享圖片
1 USE [LogChainTest]
2 GO
3 SELECT * FROM [sys].[fn_dblog](NULL,NULL) ORDER BY [Begin Time] ASC

技術分享圖片

進行完整備份

技術分享圖片 技術分享圖片
1 --第一個完整備份
2 DECLARE @strbackup NVARCHAR(100)
3 --改為日期加時間的
4 SET @strbackup = ‘C:\LogChainTest_full1_‘
5     + REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120), ‘-‘, ‘‘), ‘ ‘,
6                       ‘‘), ‘:‘, ‘‘) + ‘.bak‘
7 BACKUP DATABASE LogChainTest TO DISK =@strbackup  WITH INIT,CHECKSUM ;
8 GO
技術分享圖片

查看bak文件中的事務日誌

技術分享圖片 技術分享圖片
 1 SELECT  *
 2 FROM    fn_dump_dblog(NULL, NULL, N‘DISK‘, 1,
 3                       N‘c:\LogChainTest_full1_20131206202536.bak‘, DEFAULT,
 4                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
 5                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
 6                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
 7                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
 8                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
 9                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
10                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
11                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
12                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
13                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
14                       DEFAULT, DEFAULT)
技術分享圖片

技術分享圖片

我們再查看此時的數據庫事務日誌

技術分享圖片
1 USE [LogChainTest]
2 GO
3 SELECT * FROM [sys].[fn_dblog](NULL,NULL) ORDER BY [Begin Time] ASC

技術分享圖片

發現完整備份之後事務日誌比之前少了69-10=59行

我們發現bak文件中只記錄AllocUnitId,而不記錄表名,可能因為bak文件裏的日誌給SQLSERVER還原用的

而不是給用戶查看事務日誌用的,所以SQLSERVER幹脆不記錄表名了,以節省備份時間

技術分享圖片

技術分享圖片

看到這裏大家會有問題了,為什麼日誌會截斷了?完整備份之後事務日誌比之前少了69-10=59行

這裏只能說明SQLSERVER把一些跟本數據庫無關緊要的日誌截斷了,例如創建數據庫時候修改master數據庫的表

而不能說完整備份可以截斷日誌

而paul的文章給出了解釋:

If you switch recovery models to FULL or BULK_LOGGED, until you take the first full backup,

you are still essentially in the SIMPLE recovery model, and so the log will truncate on checkpoint.

文章地址:

http://www.sqlskills.com/blogs/paul/misconceptions-around-the-log-and-log-backups-how-to-convince-yourself/

問題:為什麼bak文件裏的日誌的最後的三條記錄會是

LOP_BEGIN_CKPT

LOP_XACT_CKPT

LOP_END_CKPT

我們用下圖來表示吧

技術分享圖片

這裏大家可以看一下宋沄劍的文章:再談SQL Server中日誌的的作用

將CheckPoint標記寫入日誌(標記中包含當前數據庫中活動的事務信息),並將Log Block寫入持久化存儲


我在開頭說過事務日誌中會放進去bak文件裏,但是並不是整個事務日誌文件裏的日誌記錄全部放進去

而是把(1)已經checkpoint了的 (2)LAZY WRITTER (3)EAGER WRITTER

還是看宋沄劍的文章吧,這麼復雜的過程我就不概括了:再談SQL Server中日誌的的作用

還有paul的文章:

Debunking a couple of myths around full database backups(揭穿一系列數據庫完備的誤區)

More on how much transaction log a full backup includes(數據庫完備包含了多少事務日誌)

實際上checkpoint和數據庫備份有著密切聯系,備份的時候SQLSERVER需要將哪些數據存入去bak文件

而在備份期間所新生成的事務和變化的數據要不要存入bak文件,這裏面比較復雜,就不詳細說了

不過有一點要說的是:在數據庫備份之前,數據庫引擎會自動執行checkpoint,以便在備份中包含對數據庫頁的全部更改。

我摘抄了網上的一些資料

技術分享圖片 技術分享圖片
 1 http://blog.csdn.net/tjvictor/article/details/5209604
 2 導致CheckPoint檢查點的事件: 1.在數據庫備份之前,數據庫引擎會自動執行checkpoint,以便在備份中包含對數據庫頁的全部更改。
 3 
 4 2.日誌的活動部分超出了服務器在 recovery interval 服務器配置選項中指定的時間內可以恢復的大小。
 5 
 6 3.日誌的 70% 已滿,並且數據庫處於日誌截斷模式。
 7 
 8 當下列條件都為 TRUE 時,數據庫就處於日誌截斷模式:數據庫使用的是簡單恢復模式,並且在執行上一條引用數據庫的 BACKUP DATABASE 語句後,發生下列事件之一:
 9 
10 在數據庫中執行一項最小日誌記錄大容量復制操作或一條最條小日誌記錄的 WRITETEXT 語句。
11 
12 執行一個在數據庫中添加或刪除文件的 ALTER DATABASE 語句。
13 
14 4.停止服務器也會在服務器上的每個數據庫中發出一個檢查點命令。下列停止 SQL Server 的方法將為每個數據庫執行檢查點:
15 
16 使用 SQL Server 配置管理器。
17 
18 使用 SQL Server Management Studio。
19 
20 使用 SHUTDOWN 語句。
21 --------------------------------------------------------------------------
22 http://www.cnblogs.com/CareySon/p/3315041.html
23 5.將恢復間隔設置為1分鐘,意味著每1分鐘會對所有的數據庫做一次CheckPoint
24 
25     錯誤。將恢復間隔設置為1分鐘不能想成建立一個Agent,每分鐘寫一個CheckPoint命令,這是兩碼事。這只是意味著每分鐘去檢查一次是否需要做CheckPoint,如果期間積累的日誌量足夠,才會對積累足夠日誌量的數據庫去做CheckPoint。即使中間積累了巨量的日誌,不到1分鐘也不會做CheckPoint。
技術分享圖片

技術分享圖片

那麽大家可以將bak文件裏的事務日誌當作為數據庫事務日誌

備份腳本

技術分享圖片 技術分享圖片
 1 USE master
 2 GO
 3 --創建數據庫
 4 CREATE DATABASE LogChainTest;
 5 GO
 6 --改為完整恢復模式
 7 ALTER DATABASE LogChainTest SET RECOVERY FULL;
 8 GO
 9 
10 
11 
12 
13 
14 
15 --第一個完整備份
16 DECLARE @strbackup NVARCHAR(100)
17 --改為日期加時間的
18 SET @strbackup = ‘C:\LogChainTest_full1_‘
19     + REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120), ‘-‘, ‘‘), ‘ ‘,
20                       ‘‘), ‘:‘, ‘‘) + ‘.bak‘
21 BACKUP DATABASE LogChainTest TO DISK =@strbackup  WITH INIT,CHECKSUM ;
22 GO
23 
24 
25 
26 
27 
28 --第一個差異備份
29 USE LogChainTest
30 GO
31 CREATE TABLE tt(id INT)
32 INSERT INTO tt
33 SELECT 1
34 DECLARE @strbackup NVARCHAR(100)
35 --改為日期加時間的
36 SET @strbackup = ‘C:\LogChainTest_diff_‘
37     + REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120), ‘-‘, ‘‘), ‘ ‘,
38                       ‘‘), ‘:‘, ‘‘) + ‘.bak‘
39 BACKUP DATABASE LogChainTest TO DISK = @strbackup WITH INIT, DIFFERENTIAL;
40 GO
41 
42 
43 
44 --第一個日誌備份
45 USE LogChainTest
46 GO
47 INSERT INTO tt
48 SELECT 2
49 DECLARE @strbackup NVARCHAR(100)
50 --改為日期加時間的
51 SET @strbackup = ‘C:\LogChainTest_log1_‘
52     + REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120), ‘-‘, ‘‘), ‘ ‘,
53                       ‘‘), ‘:‘, ‘‘) + ‘.bak‘
54 BACKUP LOG LogChainTest TO DISK = @strbackup WITH INIT;
55 GO
56 
57 
58 
59 
60 --第二個完整備份
61 USE master
62 GO
63 DECLARE @strbackup NVARCHAR(100)
64 --改為日期加時間的
65 SET @strbackup = ‘C:\LogChainTest_full2_‘
66     + REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120), ‘-‘, ‘‘), ‘ ‘,
67                       ‘‘), ‘:‘, ‘‘) + ‘.bak‘
68 BACKUP DATABASE LogChainTest TO DISK = @strbackup WITH INIT;
69 GO
70 
71 
72 --第二個日誌備份
73 USE LogChainTest
74 GO
75 INSERT INTO tt
76 SELECT 3
77 DECLARE @strbackup NVARCHAR(100)
78 --改為日期加時間的
79 SET @strbackup = ‘C:\LogChainTest_log2_‘
80     + REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120), ‘-‘, ‘‘), ‘ ‘,
81                       ‘‘), ‘:‘, ‘‘) + ‘.bak‘
82 BACKUP LOG LogChainTest TO DISK = @strbackup WITH INIT;
83 GO
技術分享圖片

備份策略:完整備份1-》差異備份-》日誌備份1-》完整備份2-》日誌備份2

還原腳本

技術分享圖片 技術分享圖片
 1 --差異備份和日誌備份1打亂
 2 USE master
 3 GO
 4 --還原第一個完整備份
 5 RESTORE DATABASE LogChainTest FROM DISK=‘C:\LogChainTest_full1_20131206230857.bak‘ 
 6 WITH REPLACE ,CHECKSUM, NORECOVERY
 7 GO
 8 
 9 --還原第一個日誌備份
10 RESTORE LOG LogChainTest FROM DISK=‘c:\LogChainTest_diff_20131206230920.bak‘ 
11 WITH  NORECOVERY
12 GO 
13 
14 --還原差異備份
15 RESTORE DATABASE LogChainTest FROM DISK=‘c:\LogChainTest_diff_20131205222718.bak‘ 
16 WITH NORECOVERY
17 GO
18 
19 消息 3136,級別 16,狀態 3,第 1 行
20 無法還原此差異備份,因為該數據庫尚未還原到正確的早期狀態。
21 消息 3013,級別 16,狀態 1,第 1 行
22 RESTORE DATABASE 正在異常終止。
23 
24 
25 
26 
27 --還原第二個日誌備份,沒有報錯
28 RESTORE LOG LogChainTest FROM DISK=‘C:\LogChainTest_log2_20131206230927.bak‘ 
29 WITH RECOVERY
30 GO 
31 
32 
33 
34 
35 --可以查詢出id列有三行記錄
36 USE [LogChainTest]
37 GO
38 SELECT * FROM [dbo].[tt]
技術分享圖片

上面的還原腳本,我先還原日誌備份1,再還原差異備份結果就報錯了

1 消息 3136,級別 16,狀態 3,第 1 行
2 無法還原此差異備份,因為該數據庫尚未還原到正確的早期狀態。
3 消息 3013,級別 16,狀態 1,第 1 行
4 RESTORE DATABASE 正在異常終止。

還有,為什麼不用還原完整備份2數據也沒有丟失??

我們每次備份的時候,無論是完備、差備、日備都會把日誌拷貝到bak文件裏

而拷貝的時候會有一個last lsn確保日誌順序

技術分享圖片

當我先還原日誌備份1,然後還原差異備份的時候因為last lsn的順序不對所以就報錯了

為什麼不用還原完整備份2數據也沒有丟失??

這裏先說一下完備、差備、日備的大概方式

完備:復制數據和少量的log到bak

差備:復制有差異的數據和少量的log到bak

日備:不復制數據,如果是第一次日備,會把所有的log復制到bak,如果是第二次日備,會把自上一次日備到這次日備的log復制到bak

paul的文章裏有解釋:

http://www.sqlskills.com/blogs/paul/misconceptions-around-the-log-and-log-backups-how-to-convince-yourself/

A log backup is *ALL* the log generated since the last log backup

備份策略:完整備份1-》差異備份-》日誌備份1-》完整備份2-》日誌備份2

我們沒有還原完整備份2(相當於丟失了完整備份2),我們的還原順序是

還原完整備份1(復制數據,根據redo/undo log保證事務一致性)

還原差異備份(復制差異數據,根據redo/undo log保證事務一致性)

還原日誌備份1(數據全靠redo/undo log來恢復,根據redo/undo log保證事務一致性)

還原日誌備份2(數據全靠redo/undo log來恢復,根據redo/undo log保證事務一致性)

因為日誌備份2裏面已經包含了從日誌備份1到日誌備份2的所有log,所以SQLSERVER可以憑借這些log來把數據恢復

而日誌備份1裏面已經包含了從完整備份1到日誌備份1的所有log

所以,按理說,我們只需要還原完備1,日備1,日備2就可以恢復全部數據

測試:

我們使用下面備份腳本和還原腳本,看一下不還原日誌備份1,直接還原日誌備份2看有沒有問題

備份腳本

技術分享圖片 View Code

還原腳本

技術分享圖片 View Code

技術分享圖片

技術分享圖片

插入的數據太少,日誌太少,搞得文件的size不那麽明顯

結果報錯

1 消息 4305,級別 16,狀態 1,第 2 行
2 此備份集中的日誌開始於 LSN 35000000017200001,該 LSN 太晚,無法應用到數據庫。可以還原包含 LSN 35000000008600001 的較早的日誌備份。
3 消息 3013,級別 16,狀態 1,第 2 行
4 RESTORE LOG 正在異常終止。

因為沒有還原日誌備份1,缺少了完備1到日備1之間的日誌,所以就報錯了

我們使用下面的腳本來進行還原,只還原完備1,日備1,日備2

技術分享圖片 View Code

技術分享圖片

這次成功了,數據都沒有丟失,那麽說明我丟失了差異備份、完整備份2也沒有關系

如果我丟失了日備1、差備、完備2,只有完備1和日備2,那麽這個時候你只能祈禱了,你只能還原完備1

差備、日備1、完備2、日備2的數據都已經丟失


BAK文件中日誌數量的多少

我剛才說

完備:復制數據和少量的log到bak

差備:復制有差異的數據和少量的log到bak

日備:不復制數據,如果是第一次日備,會把所有的log復制到bak,如果是第二次日備,會把自上一次日備到這次日備的log復制到bak

我怎麼看出來的?

測試:

我們看一下每次備份完畢後,bak文件裏面的日誌數量

技術分享圖片 技術分享圖片
 1 USE master
 2 GO
 3 SELECT  *
 4 FROM    fn_dump_dblog(NULL, NULL, N‘DISK‘, 1,
 5                       N‘c:\LogChainTest_full1_20131207102535.bak‘, DEFAULT,
 6                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
 7                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
 8                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
 9                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
10                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
11                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
12                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
13                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
14                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
15                       DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
16                       DEFAULT, DEFAULT)
技術分享圖片

完備1

技術分享圖片

差備

技術分享圖片

日備1

技術分享圖片

完備2

技術分享圖片

日備2

技術分享圖片

在完備2的時候bak中的日誌只有44行,說明完整備份只存儲一些必要的日誌,不是所有日誌都存儲

完備存儲這些日誌的作用是在還原的時候根據這些log去redo/undo 保證事務一致性,所以只會寫入少量日誌

因為完備和差備都是復制數據,所以就沒有必要像日備那樣全部事務日誌都復制到bak裏面

而日備2為什麼只有73行記錄,因為在日備1的時候SQLSERVER已經截斷了事務日誌,日備2的日誌就像我前面說的

如果是第二次日備,會把自上一次日備到這次日備的log復制到bak

如果我們不想在backup log 的時候截斷事務日誌,可以使用NO_TRUNCATECOPY_ONLY這兩個backup option

備份腳本 NO_TRUNCATE

技術分享圖片 View Code

我們看一下第一個日誌備份和第二個日誌備份之後,數據庫事務日誌和bak文件裏面的日誌數量
日備1 數據庫日誌

技術分享圖片

日備1 bak文件日誌

技術分享圖片

日備2 數據庫日誌

技術分享圖片

日備2 bak文件日誌

技術分享圖片

備份腳本 COPY_ONLY

技術分享圖片 View Code

我們看一下第一個日誌備份和第二個日誌備份之後,數據庫事務日誌和bak文件裏面的日誌數量

日備1 數據庫日誌

技術分享圖片

日備1 bak文件日誌

技術分享圖片

日備2 數據庫日誌

技術分享圖片

日備2 bak文件日誌

技術分享圖片

大家可以看一下這篇帖子

完整備份能截斷日誌嗎?


差異備份的作用

既然SQLSERVER靠bak文件裏的日誌來進行redo/undo,就像上面說的那樣,靠完備1,日備1,日備2就可以恢復所有數據

那麽差異備份有什麽用呢??

為什麼要有差異備份呢?

差異備份是為了RTO(Recovery Time Objective)

詳見:http://blog.sina.com.cn/s/blog_59388e440100oq52.html

如果只做日誌備份RTO有可能保證不了

之前說過:差備:復制有差異的數據和少量的log到bak

差異備份:靠DCM頁面復制粘貼把bak文件裏的數據復制粘貼到mdf文件的數據頁

日誌備份:redo/undo log

這兩個選項肯定是復制粘貼在速度上占優勢

當還原了差異備份之後,SQLSERVER根據差異備份時候的log使數據庫保存了事務一致性,然後還原日備1

還原日備1的時候,SQLSERVER根據差備的last lsn,只需要redo/undo 差備-》日備1這段時間的log就可以了

這樣節省了時間,不用redo/undo 完備1-》日備1這段時間的log,從而保證了RTO

而日誌備份,本人覺得是為了保證RPO(Recovery Point Objective)


被神化的日誌鏈

實際上日誌鏈就是我上面說的數據庫事務日誌,只是備份的時候,SQLSERVER把事務日誌放進去bak文件裏

我畫了幾張圖

上面那個實驗的理解圖

技術分享圖片

-------------------------------------------------------------------------------------------------------------------------

技術分享圖片

------------------------------------------------------------------------------------------------------------

技術分享圖片

----------------------------------------------------------------------------------------------------------------

技術分享圖片

大家可以使用下面兩個SQL語句

1 SELECT * FROM [sys].[fn_dblog]()
2 SELECT * FROM [sys].[fn_dump_dblog]()

在完整備份、差異備份、日誌備份測試一下在哪種備份類型下日誌會被截斷,截斷的意思(數據庫事務日誌的記錄數比bak文件裏的日誌記錄數少)

就像我在開頭做的那個實驗一樣

技術分享圖片

GUI界面下,默認就是截斷事務日誌,很多人都以為截斷事務日誌要加XX backup option才可以截斷

技術分享圖片

如何查看last_log_backup_lsn這個值

select last_log_backup_lsn
from sys.database_recovery_status WHERE [database_id]=DB_ID(‘test‘)

last_log_backup_lsn這個值在boot page的last_log_backup_lsn項裏保存,表示對數據庫執行最後一次事務日誌備份中的最大LSN號,也可以說是下一次事務日誌備份的開始LSN

實驗

技術分享圖片
USE [test]
select last_log_backup_lsn
from sys.database_recovery_status WHERE [database_id]=DB_ID(‘test‘)


BACKUP DATABASE [test] TO DISK =‘D:\DBBackup\testfull.bak‘
USE [test]
select last_log_backup_lsn
from sys.database_recovery_status WHERE [database_id]=DB_ID(‘test‘)
--34000000031500001




BACKUP LOG [test] TO DISK =‘D:\DBBackup\testlog1.bak‘
USE [test]
select last_log_backup_lsn
from sys.database_recovery_status WHERE [database_id]=DB_ID(‘test‘)
--34000000032300001




BACKUP LOG [test] TO DISK =‘D:\DBBackup\testlog2.bak‘
USE [test]
select last_log_backup_lsn
from sys.database_recovery_status WHERE [database_id]=DB_ID(‘test‘)

--34000000032800001

USE [master]
RESTORE DATABASE [test] 
FROM  DISK = N‘D:\DBBackup\testfull.bak‘ WITH  FILE = 1, 
MOVE N‘test‘ TO N‘D:\MSSQL\test.mdf‘,  
MOVE N‘test_log‘ TO N‘D:\MSSQL\test_log.ldf‘,  
NOUNLOAD,NORECOVERY , STATS = 5

GO

USE [master]
RESTORE DATABASE [test] 
FROM  DISK = N‘D:\DBBackup\testlog2.bak‘ WITH  FILE = 1, 
NOUNLOAD,NORECOVERY , STATS = 5

GO
消息 4305,級別 16,狀態 1,第 2 行
此備份集中的日誌開始於 LSN 34000000032300001,該 LSN 太晚,無法應用到數據庫。可以還原包含 LSN 34000000031500001 的較早的日誌備份。
消息 3013,級別 16,狀態 1,第 2 行
RESTORE DATABASE 正在異常終止。
技術分享圖片

可以看到,還原日誌備份的時候是讀取boot page的 last_log_backup_lsn的值來判斷日誌序列,此處應該先還原LSN 34000000032300001的日誌備份


日誌鏈斷裂的情況

paul的文章說了 SQL Server誤區30日談-Day20-破壞日誌備份鏈之後,需要一個完整備份來重新開始日誌鏈

下面這幾種操作都有可能引起日誌鏈斷裂

(1)由完整恢復模式或大容量事務日誌恢復模式轉為簡單恢復模式

(2)從數據庫鏡像進行恢復

(3)備份日誌時指定了NO_LOG 或 WITH TRUNCATE_ONLY(還好在SQL Server 2008中這個選項被取消了)

本人覺得日誌鏈斷裂是一個非常專業的名稱

很多人以為,我做了下面的備份策略:完備1-》差備-》日備1-》完備2-》日備2

如果差備丟失了就認為是日誌鏈斷裂了,數據庫不能還原到日備1

其實日誌鏈斷裂通俗的理解就是:沒有將日誌放進去bak文件裏

怎樣的情況才叫 沒有將日誌放進去bak文件裏呢??

我們知道當我們進行完備、差備、日備的時候都會把日誌放進去bak文件裏

情況一:

當你將數據庫恢復模式由完整恢復模式或大容量事務日誌恢復模式轉為簡單恢復模式

大家還是先看一下這篇文章吧:SQL Server日誌在簡單恢復模式下的角色

簡單恢復模式的機制是:文章中有這樣一句話

“在簡單恢復模式下,每一次CheckPoint,都會去檢查是否有日誌可以截斷,如果有inactive的VLF時,
CheckPoint都會將可截斷部分進行截斷,並將MinLSN向後推”

簡單來講就是簡單恢復模式不是在backup log DB 的情況下截斷日誌

技術分享圖片

而是在checkpoint的時候截斷日誌,那麽既然在checkpoint的時候已經截斷了日誌,在備份的時候數據庫的事務日誌

就沒有不活動日誌用於歸檔(把日誌放進去bak文件)

技術分享圖片

我們使用下面的腳本進行日誌備份就會報錯

技術分享圖片 View Code
1 消息 4208,級別 16,狀態 1,第 6 行
2 當恢復模式為 SIMPLE 時,不允許使用 BACKUP LOG 語句。請使用 BACKUP DATABASE 或用 ALTER DATABASE 更改恢復模式。
3 消息 3013,級別 16,狀態 1,第 6 行
4 BACKUP LOG 正在異常終止。

但是完整備份和差異備份則不受影響

備份腳本

技術分享圖片 View Code

完整備份和差異備份可以用下圖來理解,少量活動日誌放到bak文件裏用於保證事務一致性
完整備份差異備份時依然會將last lsn寫入bak文件裏

技術分享圖片

還原腳本

技術分享圖片 View Code

先還原差備2再還原差備1就報錯

1 消息 4305,級別 16,狀態 1,第 1 行
2 此備份集中的日誌開始於 LSN 35000000028200004,該 LSN 太晚,無法應用到數據庫。可以還原包含 LSN 35000000024100001 的較早的日誌備份。
3 消息 3013,級別 16,狀態 1,第 1 行
4 RESTORE LOG 正在異常終止。

實際上完整和差備都是復制數據和少量活動日誌到bak裏面,所以還原是沒有問題的
但是日備不同,日備需要將完備到第一個日備的log,或者自上一次日備到這次日備的log全部放進去bak文件

因為簡單恢復模式是一checkpoint就截斷日誌,根本無辦法保存完整的log,所以是不允許日備的

情況二:

備份日誌時指定了NO_LOG 或 WITH TRUNCATE_ONLY(還好在SQL Server 2008中這個選項被取消了)

TRUNCATE_ONLY的意思是只截斷日誌不備份日誌到bak文件裏(只能用在backup log語句)

NO_LOG的意思是不備份日誌到bak文件裏(不備份日誌到bak文件裏意味著不能backup log,當然也意味著不能截斷日誌)

我們轉到SQLSERVER2005

技術分享圖片

備份腳本

NO_LOG

技術分享圖片 View Code

備份策略:完備-》差備-》日備

大家可以看到執行日備的時候沒有產生bak文件

技術分享圖片

查看bak文件裏的日誌

技術分享圖片 View Code

技術分享圖片

完備0行

技術分享圖片

差備0行

其實可以用下圖來理解

技術分享圖片

bak文件裏只有數據沒有日誌,連保證事務一致性的少量的活動日誌都沒有

備份腳本

TRUNCATE_ONLY

技術分享圖片 View Code

備份策略:日備

大家可以看到執行日備的時候沒有產生bak文件

技術分享圖片

查看日誌備份前數據庫事務日誌

技術分享圖片

查看日誌備份前數據庫事務日誌

技術分享圖片

其實可以用下圖來理解

技術分享圖片

truncate_only只是截斷了日誌,沒有產生bak文件,更不用說備份日誌到bak文件裏面了

我們再做一個實驗

備份腳本

技術分享圖片 View Code

技術分享圖片

當我進行到第三個日誌備份的時候就報錯了

1 (1 行受影響)
2 消息 4214,級別 16,狀態 1,第 8 行
3 無法執行 BACKUP LOG,因為當前沒有數據庫備份。
4 消息 3013,級別 16,狀態 1,第 8 行
5 BACKUP LOG 正在異常終止。

可以用下圖來理解

技術分享圖片

(2)從數據庫鏡像進行恢復這種情況由於沒有研究過就不說了

小結:

截斷日誌跟日誌鏈斷裂不是同一樣東西!!

截斷日誌:針對數據庫事務日誌

日誌鏈斷裂:針對bak裏的日誌

大家不要混淆了


不神秘的事務日誌尾部

當你的數據庫損壞或置疑,你可以嘗試進行尾日誌備份

尾日誌指的是哪個地方? 為什麼要進行尾日誌備份?

假如有下面的腳本

技術分享圖片 View Code

在第二個日誌備份之後還插入了一條記錄到tt表

如果這時候數據庫損壞,那麽你可以備份事務日誌尾部,把最後的事務日誌記錄(INSERT INTO tt
SELECT 3)放進去bak文件裏,然後進行還原數據庫

使用下面腳本,備份日誌尾部

註意:數據庫離線的狀態下是不能備份日誌尾部的!!

網上很多文章都誤導人

由於數據庫 ‘LogChainTest‘ 離線,無法打開該數據庫

技術分享圖片 技術分享圖片
 1 --備份日誌尾部
 2 USE master
 3 GO
 4 DECLARE @strbackup NVARCHAR(100)
 5 --改為日期加時間的
 6 SET @strbackup = ‘C:\LogChainTest_log_tail_‘
 7     + REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120), ‘-‘, ‘‘), ‘ ‘,
 8                       ‘‘), ‘:‘, ‘‘) + ‘.bak‘
 9 BACKUP LOG LogChainTest TO DISK = @strbackup WITH INIT,NORECOVERY;
10 GO
技術分享圖片

這時候數據庫顯示正在還原

技術分享圖片

技術分享圖片
還原腳本

技術分享圖片 技術分享圖片
 1 -------------------------------------------------------------
 2 --還原
 3 USE master
 4 GO
 5 --還原第一個完整備份
 6 RESTORE DATABASE LogChainTest FROM DISK=‘C:\LogChainTest_full1_20131207145154.bak‘ 
 7 WITH REPLACE ,CHECKSUM, NORECOVERY
 8 GO
 9 
10 --還原第一個日誌備份
11 RESTORE LOG LogChainTest FROM DISK=‘c:\LogChainTest_log1_20131207145157.bak‘ 
12 WITH  NORECOVERY
13 GO 
14 
15 --還原第二個日誌備份
16 RESTORE LOG LogChainTest FROM DISK=‘c:\LogChainTest_log2_20131207145158.bak‘ 
17 WITH  NORECOVERY
18 GO 
19 
20 --還原日誌尾部
21 RESTORE DATABASE LogChainTest FROM DISK=‘c:\LogChainTest_log_tail_20131207145333.bak‘ 
22 WITH RECOVERY
23 GO
技術分享圖片

數據沒有丟失,可以查出最後一條插入到tt表的記錄3
技術分享圖片

回答開頭的問題:尾日誌指的是哪個地方? 為什麼要進行尾日誌備份?

其實備份日誌尾部,大家可以把他作為普通的事務日誌備份

技術分享圖片

如果遇到錯誤還可以加上CONTINUE_AFTER_ERROR 的backup option

技術分享圖片 技術分享圖片
 1 --備份日誌尾部
 2 USE master
 3 GO
 4 DECLARE @strbackup NVARCHAR(100)
 5 --改為日期加時間的
 6 SET @strbackup = ‘C:\LogChainTest_log_tail_‘
 7     + REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120), ‘-‘, ‘‘), ‘ ‘,
 8                       ‘‘), ‘:‘, ‘‘) + ‘.bak‘
 9 BACKUP LOG LogChainTest TO DISK = @strbackup WITH CONTINUE_AFTER_ERROR,NORECOVERY;
10 GO
技術分享圖片


備份記錄

實際上這個[msdb].[dbo].[backupset]表的作用只是給你看做了哪些備份

1 SELECT * FROM [msdb].[dbo].[backupset]

技術分享圖片

使用GUI的時候,我發現了一個問題

當我用上面的備份策略 完備1-》差備-》日備1-》完備2-》日備2

當我完成日備1的時候,還原界面和backupset表的界面如下

技術分享圖片

技術分享圖片

當我再進行完備2和日備2的時候,還原界面變成了下面的樣子

技術分享圖片

backupset表依然能顯示出備份記錄

技術分享圖片

很多人就認為備份鏈斷裂了,日誌鏈斷裂,備份日誌鏈斷裂,日誌備份鏈斷裂

這個表的記錄是刪除不了的

技術分享圖片
1 USE [msdb]
2 GO
3 DELETE FROM [msdb].[dbo].[backupset]
4 TRUNCATE TABLE [msdb].[dbo].[backupset]
1 消息 547,級別 16,狀態 0,第 1 行
2 DELETE 語句與 REFERENCE 約束"FK__backupfil__backu__473C8FC7"沖突。該沖突發生於數據庫"msdb",表"dbo.backupfilegroup", column ‘backup_set_id‘。
3 語句已終止。
4 消息 4712,級別 16,狀態 1,第 2 行
5 無法截斷表 ‘msdb.dbo.backupset‘,因為該表正由 FOREIGN KEY 約束引用。

這個表記錄了在備份的時候的lsn號
技術分享圖片

可以根據paul的文章做一些實驗

Debunking a couple of myths around full database backups

技術分享圖片

我們做一個實驗,先做一個完整備份

技術分享圖片 View Code

backupset表就會產生一條記錄
技術分享圖片

我們將bak文件刪除

技術分享圖片

用GUI來還原數據庫

技術分享圖片

技術分享圖片

結果:

技術分享圖片

他們的關系

技術分享圖片
1 USE [msdb]
2 GO
3 SELECT * FROM [dbo].[backupfile]
4 SELECT * FROM [dbo].[backupfilegroup]
5 SELECT * FROM [dbo].[backupset]
6 SELECT * FROM [sys].[backup_devices]
7 SELECT * FROM [dbo].[backupmediafamily]
8 SELECT * FROM [dbo].[backupmediaset]
技術分享圖片

技術分享圖片

每次備份的記錄都記錄在這些表裏面,還原的時候SSMS讀取這些表的記錄,讓你勾上幾個選項就可以還原數據庫了(非常傻瓜)

大家不要以為SQLSERVER在還原數據庫的時候依靠[msdb].[dbo].[backupset]表的lsn去對比備份順序

大家可以試想一下:

你的數據庫備份了3次,有3個備份記錄保存在backupset表

那麽當你把數據庫分離附加到別的sql實例的時候,你也可以還原你之前的備份

為什麼呢??

因為還原的時候只去數據庫的事務日誌去對比last lsn,是不依靠外部的其他的數據的而且也不需要依靠

如果還不明白的話,大家再看一下我上面貼出來的圖片吧o(∩_∩)o


總結

一直以來本人對SQLSERVER的備份還原機制都不是很熟悉,通過跟宋沄劍的討論讓本人重新認識SQLSERVER的備份、還原

失眠了兩晚,今晚可以吃一個好的水餃了

相關內容:

http://social.technet.microsoft.com/Forums/zh-CN/7e531652-1f00-441b-ae20-871b3e9573c8/sql-server-2005?forum=sqlserverzhchs

http://www.sqlskills.com/blogs/paul/misconceptions-around-the-log-and-log-backups-how-to-convince-yourself/

http://www.sqlskills.com/blogs/paul/more-on-how-much-transaction-log-a-full-backup-includes/

http://www.sqlskills.com/blogs/paul/debunking-a-couple-of-myths-around-full-database-backups/

淺談SQL Server中的事務日誌(一)----事務日誌的物理和邏輯構架

淺談SQL Server中的事務日誌(五)----日誌在高可用和災難恢復中的作用

上面的結論都經過我測試,希望大家可以指出本人的錯處o(∩_∩)o

您們也可以動手測試一下我說的是不是真的o(∩_∩)o

如有不對的地方,歡迎大家拍磚o(∩_∩)o

2013-12-7 補充:

大家不要誤解了,數據庫事務日誌截斷的意思不是說把不活動日誌部分刪除了,而是把這些日誌清空了

等待重用,除非你收縮事務日誌,不然這些日誌空間(VLF)只會等待重用

2013-12-8 補充:

還原日誌備份的時候使用restore log 或restore database都是一樣的

而還原差異備份的時候使用restore log就會報錯

技術分享圖片 技術分享圖片
 1 USE master
 2 GO
 3 --創建數據庫
 4 CREATE DATABASE LogChainTest;
 5 GO
 6 --改為完整恢復模式
 7 ALTER DATABASE LogChainTest SET RECOVERY FULL;
 8 GO
 9 
10 
11 --------------------------------------------------------------------
12 --備份
13 --第一個完整備份
14 USE master
15 GO
16 DECLARE @strbackup NVARCHAR(100)
17 --改為日期加時間的
18 SET @strbackup = ‘C:\LogChainTest_full1_‘
19     + REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120), ‘-‘, ‘‘), ‘ ‘,
20                       ‘‘), ‘:‘, ‘‘) + ‘.bak‘
21 BACKUP DATABASE LogChainTest TO DISK = @strbackup WITH INIT;
22 GO
23 
24 
25 
26 --第一個日誌備份
27 USE LogChainTest
28 GO
29 CREATE TABLE tt(id INT)
30 INSERT INTO tt
31 SELECT 1
32 DECLARE @strbackup NVARCHAR(100)
33 --改為日期加時間的
34 SET @strbackup = ‘C:\LogChainTest_log1_‘
35     + REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120), ‘-‘, ‘‘), ‘ ‘,
36                       ‘‘), ‘:‘, ‘‘) + ‘.bak‘
37 BACKUP LOG LogChainTest TO DISK = @strbackup WITH INIT;
38 GO
39 
40 
41 
42 --第一個差異備份
43 USE LogChainTest
44 GO
45 INSERT INTO tt
46 SELECT 2
47 DECLARE @strbackup NVARCHAR(100)
48 --改為日期加時間的
49 SET @strbackup = ‘C:\LogChainTest_diff1_‘
50     + REPLACE(REPLACE(REPLACE(CONVERT(VARCHAR, GETDATE(), 120), ‘-‘, ‘‘), ‘ ‘,
51                       ‘‘), ‘:‘, ‘‘) + ‘.bak‘
52 BACKUP DATABASE LogChainTest TO DISK = @strbackup WITH INIT, DIFFERENTIAL;
53 GO
54 ------------------------------------------------------------------------
55 
56 
57 --------------------------------------------------------------------------
58 --還原
59 
60 
61 
62 USE master
63 GO
64 --只有完備備份還原才可以移動數據庫文件
65 RESTORE DATABASE LogChainTest FROM DISK=‘C:\LogChainTest_full1_20131208100145.bak‘ 
66    WITH MOVE ‘LogChainTest‘ TO ‘E:\LogChainTest.mdf‘, 
67    MOVE ‘LogChainTest_log‘ TO ‘E:\LogChainTest_log.ldf‘,
68   NORECOVERY ,REPLACE
69 GO
70 
71 
72 RESTORE LOG LogChainTest FROM DISK=‘c:\LogChainTest_log1_20131208100151.bak‘ 
73    WITH MOVE ‘LogChainTest‘ TO ‘E:\LogChainTest.mdf‘, 
74    MOVE ‘LogChainTest_log‘ TO ‘E:\LogChainTest_log.ldf‘,
75   NORECOVERY
76 GO 
77 -------------------------------------------------
78 RESTORE DATABASE LogChainTest FROM DISK=‘c:\LogChainTest_log1_20131208100151.bak‘ 
79    WITH MOVE ‘LogChainTest‘ TO ‘E:\LogChainTest.mdf‘, 
80    MOVE ‘LogChainTest_log‘ TO ‘E:\LogChainTest_log.ldf‘,
81   NORECOVERY
82 GO 
83 
84 
85 RESTORE LOG LogChainTest FROM DISK=‘c:\LogChainTest_diff1_20131208100251.bak‘ 
86    WITH MOVE ‘LogChainTest‘ TO ‘E:\LogChainTest.mdf‘, 
87    MOVE ‘LogChainTest_log‘ TO ‘E:\LogChainTest_log.ldf‘,
88   RECOVERY
89 GO
90 ----------------------------------------------------------
91 RESTORE DATABASE LogChainTest FROM DISK=‘c:\LogChainTest_diff1_20131208100251.bak‘ 
92    WITH MOVE ‘LogChainTest‘ TO ‘E:\LogChainTest.mdf‘, 
93    MOVE ‘LogChainTest_log‘ TO ‘E:\LogChainTest_log.ldf‘,
94   RECOVERY
95 GO
96 
97 USE [LogChainTest]
98 GO
99 SELECT * FROM [dbo].[tt]
技術分享圖片

2016-8-2 補充:

MinLSN是當前所有活動事務的開始LSN和checkpoint的開始LSN中的較小者

MinLSN的作用是記錄當前數據庫需要恢復時,可能回滾的上限

實例恢復和介質恢復

技術分享圖片

技術分享圖片

實例恢復和fn_dblog從minlsn開始顯示

技術分享圖片

bootpage-》數據庫最後一個checkpoint的lsn-》ldf裏面定位到數據庫最後一個checkpoint開始的那條日誌記錄-》讀取minlsn

技術分享圖片

頁頭最後一次修改LSN(m_lsn)和dbi_checkptLSN進行對比

《SQL Server2008數據庫技術內幕》

技術分享圖片

(4.19)深入理解SQLSERVER的日誌鏈