zoukankan      html  css  js  c++  java
  • (4.19)深入理解SQLSERVER的日志链

    您真的理解了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	estfull.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	estlog1.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	estlog2.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	estfull.bak' WITH  FILE = 1, 
    MOVE N'test' TO N'D:MSSQL	est.mdf',  
    MOVE N'test_log' TO N'D:MSSQL	est_log.ldf',  
    NOUNLOAD,NORECOVERY , STATS = 5
    
    GO
    
    USE [master]
    RESTORE DATABASE [test] 
    FROM  DISK = N'D:DBBackup	estlog2.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数据库技术内幕》

  • 相关阅读:
    测试用例的设计
    测试经理的职责
    如果开发认为这不是bug,对QA来说应该怎么处理?
    我选择测试的过程
    API的知识点
    测试人员需要的技能
    时区转换的计算方式
    学习能力的必需
    制图工具
    JSON序列和反序列1
  • 原文地址:https://www.cnblogs.com/gered/p/9882367.html
Copyright © 2011-2022 走看看