简介
SQL Server中的事务日志无疑是SQL Server中最重要的部分之一。因为SQL SERVER利用事务日志来确保持久性(Durability)和事务回滚(Rollback)。从而还部分确保了事务的ACID属性.在SQL Server崩溃时,DBA还可以通过事务日志将数据恢复到指定的时间点。当SQL Server运转良好时,多了解一些事务日志的原理和概念显得并不是那么重要。但是,一旦SQL SERVER发生崩溃时,了解事务日志的原理和概念对于快速做出正确的决策来恢复数据显得尤为重要.本系列文章将会从事务日志的概念,原理,SQL Server如何使用日志来确保持久性属性等方面来谈SQL Server的事务日志.
事务日志的物理组织构架
事务日志仅仅是记录与其对应数据库上的事务行为和对数据库修改的日志文件.在你新建数据库时,伴随着数据库文件,会有一个默认以ldf为扩展名的事务日志文件. 当然,一个数据库也可以配有多个日志文件,但是在逻辑上,他们可以看成一个.
在SQL Server对于日志文件的管理,是将逻辑上一个ldf文件划分成多个逻辑上的虚拟日志文件(virtual log files,简称VLFs).以便于管理。用个类比方法来看,日志文件(ldf)好比一趟火车,每一节车厢都是一个虚拟日志文件(VLFs):
那为什么SQL Server要把日志文件划分出多个VLFS呢?因为SQL Server通过这种方式使得存储引擎管理事务日志更加有效.并且对于日志空间的重复利用也会更加高效。使用VLF作为收缩数据库的最小单位比使用ldf文件作为最小单位无疑是更加高效的.
VLFS的个数和大小无法通过配置进行设定,而是由SQL Server进行管理.当Create或Alter数据库时,SQL Server通过ldf文件的大小来决定VLFS的大小和数量。在日志文件增长时,SQL Server也会重新规划VLFS的数量.
注意:根据这个原理不难看书,如果设置日志文件的增量过小,则会产生过多的VLFS,也就是日志文件碎片,过多的日志文件碎片会拖累SQL Server性能.
SQL Server创建数据库时,根据日志文件(ldf)的大小,生成VLF的数量公式如下:
ldf文件的大小 |
VLF的数量 |
1M到64M |
4 |
64M到1GB |
8 |
大于1GB |
16 |
下面我们来看一个例子:
创建数据库,指定日志大小为65M
通过DBCC,我们可以看到,对应的有8个VLFs:
再次创建数据库,指定日志初始大小为28M:
可以看到,对应的,VLF的数量变为4:
而对于日志文件的增长,SQL Server使用了和创建数据库时相同的公式,也就是每次增长比如为2M,则按照公式每次增长4个VLFs.
我们创建一个TestGrow数据库,指定日志文件为2M,此时有4个VLFS:
当我们增长2M时,这个2M则是按照公式,再次分配4个VLFs:
此时,这时能看到的VLFs数量应该为4+4=8个:
由此可以看出,指定合适的日志文件初始大小和增长,是减少日志碎片最关键的部分.
事务日志的逻辑组织构架
当针对数据库对象所做的任何修改保存到数据库之前,相应的日志首先会被记录到日志文件。这个记录会被按照先后顺序记录到日志文件的逻辑末尾,并分配一个全局唯一的日志序列号(log sequence number,简称LSN),这个序列号完全是按照顺序来的,如果日志中两个序列号LSN2>LSN1,则说明LSN2所在LSN1之后发生的.
由此可以看出,将日志文件分为多个文件除了磁盘空间的考虑之外。完全不会像数据那样可以并行访问,所以将日志文件分为多个完全不会有性能上的提升.
LSN号可以看作是将日志文件和其记录数据之间的纽带.每一条日志不仅有LSN号,还有其对应事务的事务日志:
一个简单的图片示例如下:
许多类型的操作都记录在事务日志中。这些操作包括:
-
每个事务的开始和结束。
-
每次数据修改(插入、更新或删除)。这包括系统存储过程或数据定义语言 (DDL) 语句对包括系统表在内的任何表所做的更改。
-
每次分配或释放区和页。
-
创建或删除表或索引。
对于LSN如何在ROLLBACK或者是ROLL FORWARD中以及在备份恢复过程中起作用,会在后续文章中提到
总结
本篇文章从事务日志的逻辑和物理构架简单介绍了事务日志的构成.这是理解SQL Server如何利用日志保证持久性和数据备份恢复的基础。下一篇文章将会介绍SQL Server在操作中会如何使用到日志文件
简介
每一个SQL Server的数据库都会按照其修改数据(insert,update,delete)的顺序将对应的日志记录到日志文件.SQL Server使用了Write-Ahead logging技术来保证了事务日志的原子性和持久性.而这项技术不仅仅保证了ACID中的原子性(A)和持久性(D),还大大减少了IO操作,把对数据的修改提交到磁盘的工作交给lazy-writer和checkpoint.本文主要讲述了SQL Server修改数据时的过程以及相关的技术。
预写式日志(Write-Ahead Logging (WAL))
SQL Server使用了WAL来确保了事务的原子性和持久性.实际上,不光是SQL Server,基本上主流的关系数据库包括oracle,mysql,db2都使用了WAL技术.
WAL的核心思想是:在数据写入到数据库之前,先写入到日志.
因为对于数据的每笔修改都记录在日志中,所以将对于数据的修改实时写入到磁盘并没有太大意义,即使当SQL Server发生意外崩溃时,在恢复(recovery)过程中那些不该写入已经写入到磁盘的数据会被回滚(RollBack),而那些应该写入磁盘却没有写入的数据会被重做(Redo)。从而保证了持久性(Durability)
但WAL不仅仅是保证了原子性和持久性。还会提高性能.
硬盘是通过旋转来读取数据,通过WAL技术,每次提交的修改数据的事务并不会马上反映到数据库中,而是先记录到日志.在随后的CheckPoint和lazy Writer中一并提交,如果没有WAL技术则需要每次提交数据时写入数据库:
而使用WAL合并写入,会大大减少磁盘IO:
也许你会有疑问,那每次对于修改的数据还是会写入日志文件.同样消耗磁盘IO。上篇文章讲过,每一笔写入日志的记录都是按照先后顺序,给定顺序编号的LSN进行写入的,日志只会写入到日志文件的逻辑末端。而不像数据那样,可能会写到磁盘的各个地方.所以,写入日志的开销会比写入数据的开销小很多。
SQL Server修改数据的步骤
SQL Server对于数据的修改,会分为以下几个步骤顺序执行:
1.在SQL Server的缓冲区的日志中写入”Begin Tran”记录
2.在SQL Server的缓冲区的日志页写入要修改的信息
3.在SQL Server的缓冲区将要修改的数据写入数据页
4.在SQL Server的缓冲区的日志中写入”Commit”记录
5.将缓冲区的日志写入日志文件
6.发送确认信息(ACK)到客户端(SMSS,ODBC等)
可以看到,事务日志并不是一步步写入磁盘.而是首先写入缓冲区后,一次性写入日志到磁盘.这样既能在日志写入磁盘这块减少IO,还能保证日志LSN的顺序.
上面的步骤可以看出,即使事务已经到了Commit阶段,也仅仅只是把缓冲区的日志页写入日志,并没有把数据写入数据库.那将要修改的数据页写入数据库是在何时发生的呢?
Lazy Writer和CheckPoint
上面提到,SQL Server修改数据的步骤中并没有包含将数据实际写入到磁盘的过程.实际上,将缓冲区内的页写入到磁盘是通过两个过程中的一个实现:
这两个过程分别为:
1.CheckPoint
2.Lazy Writer
任何在缓冲区被修改的页都会被标记为“脏”页。将这个脏页写入到数据磁盘就是CheckPoint或者Lazy Writer的工作.
当事务遇到Commit时,仅仅是将缓冲区的所有日志页写入磁盘中的日志文件:
而直到Lazy Writer或CheckPoint时,才真正将缓冲区的数据页写入磁盘文件:
前面说过,日志文件中的LSN号是可以比较的,如果LSN2>LSN1,则说明LSN2的发生时间晚于LSN1的发生时间。CheckPoint或Lazy Writer通过将日志文件末尾的LSN号和缓冲区中数据文件的LSN进行对比,只有缓冲区内LSN号小于日志文件末尾的LSN号的数据才会被写入到磁盘中的数据库。因此确保了WAL(在数据写入到数据库之前,先写入日志)。
Lazy Writer和CheckPoint的区别
Lazy Writer和CheckPoint往往容易混淆。因为Lazy Writer和CheckPoint都是将缓冲区内的“脏”页写入到磁盘文件当中。但这也仅仅是他们唯一的相同点了。
Lazy Writer存在的目的是对缓冲区进行管理。当缓冲区达到某一临界值时,Lazy Writer会将缓冲区内的脏页存入磁盘文件中,而将未修改的页释放并回收资源。
而CheckPoint存在的意义是减少服务器的恢复时间(Recovery Time).CheckPoint就像他的名字指示的那样,是一个存档点.CheckPoint会定期发生.来将缓冲区内的“脏”页写入磁盘。但不像Lazy Writer,Checkpoint对SQL Server的内存管理毫无兴趣。所以CheckPoint也就意味着在这个点之前的所有修改都已经保存到了磁盘.这里要注意的是:CheckPoint会将所有缓冲区的脏页写入磁盘,不管脏页中的数据是否已经Commit。这意味着有可能已经写入磁盘的“脏页”会在之后回滚(RollBack).不过不用担心,如果数据回滚,SQL Server会将缓冲区内的页再次修改,并写入磁盘。
通过CheckPoint的运作机制可以看出,CheckPoint的间歇(Recovery Interval)长短有可能会对性能产生影响。这个CheckPoint的间歇是一个服务器级别的参数。可以通过sp_config进行配置,也可以在SSMS中进行配置:
恢复间歇的默认参数是0,意味着由SQL Server来管理这个回复间隔。而自己设置恢复间隔也是需要根据具体情况来进行界定。更短的恢复间歇意味这更短的恢复时间和更多的磁盘IO,而更长的恢复间歇则带来更少的磁盘IO占用和更长的恢复时间.
除了自动CheckPoint之外,CheckPoint还会发生在Alter DataBase以及关闭SQL Server服务器时。sysadmin和db_backupoperator组的成员以及db_owner也可以使用CheckPoint指令来手动保存CheckPoint:
通过指定CheckPoint后的参数,SQL Server会按照这个时间来完成CheckPoint过程,如果时间指定的短,则SQL Server会使用更多的资源优先完成CheckPoint过程。
通常情况下,将“脏”页写入磁盘的工作,Lazy Writer要做的比CheckPoint会多出许多。
总结
本文简单介绍了WAL的概念和修改数据库对象时,日志所扮演的角色。还分别介绍了CheckPoint和Lazy Writer,对于这些概念的理解是理解SQL Server DBA工作的基础。下篇文章将会讲述在简单恢复模式下日志的机制
简介
在简单恢复模式下,日志文件的作用仅仅是保证了SQL Server事务的ACID属性。并不承担具体的恢复数据的角色。正如”简单”这个词的字面意思一样,数据的备份和恢复仅仅是依赖于手动备份和恢复.在开始文章之前,首先要了解SQL Server提供的几种不同备份类型。
SQL Server提供的几种备份类型
SQL Server所提供的几种备份类型基本可以分为以下三种(文件和文件组备份以及部分备份不在本文讨论之列):
1.完整(Full)备份:直接将所备份的数据的所有区(Extent)进行复制。这里值得注意的有2点:
- 完整备份并不像其名字“完整”那样备份所有部分,而是仅备份数据库本身,而不备份日志(虽然仅仅备份少量日志用于同步)
- 完整备份在备份期间,数据库是可用的。完整备份会记录开始备份时的LSN号,结束备份时的LSN号,以便在备份结束时将这期间的改动应用到备份,所以完整备份后数据的时间点是备份结束的时间。
2.差异(Differential)备份:只备份上次完整备份后,做修改的部分。备份单位是区(Extent)。意味着某个区内即使只有一页做了变动,则在差异备份里会被体现.差异备份依靠一个BitMap进行维护,一个Bit对应一个区,自上次完整备份后,被修改的区会被置为1,而BitMap中被置为1对应的区会被差异备份所备份。而到下一次完整备份后,BitMap中所有的Bit都会被重置为0。
3.日志(Log)备份:仅仅备份自上次完整备份或日志备份之后的记录。在简单模式下,日志备份毫无意义(SQL Server不允许在简单恢复模式下备份日志),下文会说明在简单恢复模式下,为什么日志备份没有意义。
简单恢复模式(Simple Recovery Mode)
在简单恢复模式下,日志仅仅是为了保证SQL Server事务的ACID。并没有恢复数据的功能.
比如,我们有一个备份计划,如下:
我们在每周一0点做一次完整备份,在周三0点和周五0点分别做差异备份。在简单恢复模式下,如果周六数据库崩溃。我们的恢复计划只有根据周一0点的做的完整备份恢复后,再利用周五0点的差异备份进行恢复.而周五0点之后到服务器崩溃期间所有的数据将会丢失。
正如”简单”这个词所涵盖的意思,在简单恢复模式下,日志可以完全不用管理。而备份和恢复完全依赖于我们自己的完整和差异备份.
恢复模式是一个数据库级别的参数,可以通过在SSMS里或通过SQL语句进行配置:
简单恢复模式下日志的空间使用
在本系列文章的第一篇文章提到过,日志文件会划分成多个VLF进行管理,在逻辑上记录是线性的,给每个记录一个顺序的,唯一的LSN。
而在简单恢复模式下,为了保证事务的持久性,那些有可能回滚的数据会被写入日志。这些日志需要被暂时保存在日志以确保在特定条件下事务可以顺利回滚。这就涉及到了一个概念—最小恢复LSN(Minimum Recovery LSN(MinLSN) )
MinLsn是在还未结束的事务记录在日志中最小的LSN号,MinLSN是下列三者之一的最小值:
-
CheckPoint的开始LSN
-
还未结束的事务在日志的最小LSN
-
尚未传递给分发数据库的最早的复制事务起点的 LSN.
下图是一个日志的片段:
(图片摘自MSDN)
可以看到,最新的LSN是148,147是CheckPoint,在这个CheckPoint之前事务1已经完成,而事务2还未完成,所以对应的MinLSN应该是事务2的开始,也就是142.
而从MinLSN到日志的逻辑结尾处,则称为活动日志(Active Log)。
而活动日志分布在物理VLF上的关系可以用下图表示:
因此,VLF的状态是源自其上所含有的LSN的状态,可以分为两大类:活动VLF和不活动VLF
而更加细分可以将VLF的状态分为以下四类:
- 活动(Active) –在VLF 上存储的任意一条LSN是活动的时,则VLF则为活动状态,即使一个200M的VLF只包含了一条LSN,如上图的VLF3
- 可恢复(Recoverable) – VLF是不活动的,VLF上不包含活动LSN,但还未被截断(truncated)
- 可重用(Reusable) – VLF是不活动的,VLF上不包含活动LSN,已经被截断(truncated),可以重用
- 未使用(Unused) – VLF是不活动的,并且还未被使用过
概念如下图:
而所谓的截断(truncated)只是将可恢复状态的VLF转换到可重用状态。在简单恢复模式下,每一次CheckPoint会引发一次截断.而每一次CheckPoint都会将MinLSN向后推.所以当事务结束后并且过了CheckPoint点,其相关的日志将会被截断以便重复利用空间。
在日志达到日志文件(ldf文件)末尾时,也就是上图的VLF8时,会重新循环到VLF1开始,以便让空间进行重复利用.所以日志虽然可以从物理顺序上是从VLF1到VLF8,但逻辑顺序可以是从VLF6开始到VLF2结束:
因此可以看出,简单恢复模式下日志是不保存的(当事务结束后,相关的会被截断)。仅仅是用于保证事务回滚和崩溃恢复的用途.所以备份日志也就无从谈起,更不能利用日志来恢复数据库。
总结
本文介绍了简单恢复模式下日志的原理,并简单的引出了一些备份或者恢复数据的基础。而实际上,除了在开发或测试环境下。使用简单恢复模式的场景并不多,因为在现实生活中,在生产环境允许几个小时的数据丢失的场景几乎没有.下篇文章将会讲述在完整恢复模式下,日志的作用
简介
生产环境下的数据是如果可以写在资产负债表上的话,我想这个资产所占的数额一定不会小。而墨菲定律(事情如果有变坏的可能,无论这种可能性有多小,它总会发生)仿佛是给DBA量身定做的。在上篇文章介绍的简单恢复模式下,从最近一次备份到当前的数据都会存在丢失的风险。而完整备份模式使得数据丢失的风险大大减少。本文主要介绍在完整备份模式下概念原理和日志所处的角色。
完整(Full)恢复模式
完整恢复模式通过将对数据库的任何修改记录到日志来给予数据最大程度的保护。在完整恢复模式下,日志的作用不仅仅是保证了数据库事务的ACID。并且还可以使数据恢复到在日志范围内的任何时间点。
在上一篇文章中说过,在简单恢复模式下,日志几乎是不用进行管理的。每一次CheckPoint都有可能截断日志,从而来回收不活动的VLF以便重复利用空间。因此在简单恢复模式下,日志的空间使用几乎可以不去考虑。与之相反,在完整恢复模式下,日志作为恢复数据的重要组成部分,日志的管理和对日志空间使用的管理则需要重视。
在完整恢复模式下,CheckPoint不会截断日志。只有对日志的备份才会将MinLSN向后推并截断日志。因此在一个业务量稍大的系统中,日志的增长速度将会变得很快。
因此日志备份的目的分为以下两个:
- 减少活动日志的大小
- 减少日志损坏的风险
通过从MSDN中摘自的下图可以看到:
在DB_1处做了完整备份,并且接下来两次分别做了两次日志备份(Log_1和Log_2),在Log_2备份完不久服务器由于数据所在磁盘损坏。这时如果日志文件完好,则可以通过备份尾部日志(Tail of log)后,从DB_1开始恢复,依次恢复Log_1,Log_2,尾部日志来将数据库恢复到灾难发生时的时间点。理论上可以使数据的损失为0。
从日志恢复数据的原理是Redo,也就是将日志中记载的事务再重做一遍。这个开销和从完整或差异备份中恢复相比,要大很多。因此尽可能的减少利用日志的恢复量。而使用完整或者差异备份来恢复更多的数据。
大容量日志(Bulk-logged)恢复模式
大容量恢复模式在很多地方和完整恢复模式相同。但由于在完整恢复模式下,对数据库的每一项操作都会记录在日志中。而对于某些大量数据的导入导出操作,无疑会在日志中留下大量记录。很多情况下,我们并不需要将这些信息记录在日志中。
而大容量日志恢复模式作为完整恢复模式的备选方案。微软推荐的最佳实践是在进行大量数据操作时(比如索引的创建和rebuilt,select into操作等),暂时由完整恢复模式切换到大容量恢复模式来节省日志。这个转换并不会破坏日志链。
本文不会深入探讨这个模式,仅仅是对这个概念做简单解释。假设我要插入一批数据,则完整恢复模式和大容量日志恢复模式在日志中所记录的信息如下:
由此可以看出,在日志中,大容量恢复模式将这类操作变为一个原子。所以在大容量日志恢复模式下,不能redo大容量日志中的这类操作(select into之类的)
日志链(Log Chain)
连续的日志备份被称之为日志链。表示日志是连续的.这个概念可以用下图表示:
假设上面两个日志备份可以简单抽象成如上2个备份,则日志备份1的末尾LSN必须大于等于日志备份二的第一个LSN(通常情况下是第一个末尾LSN等于第二个日志备份的第一个LSN,但由于存在“只备份日志”选项只备份日志,并不截断日志,所以有可能重叠)。则这两个备份的日志链是连续的。
下图是一个生产环境下,在SSMS中查看日志链连续的例子:
可以看出,第一次完整备份后,备份多次事务日志,每一个事务日志的开始LSN都等于上一个事务日志的结束LSN。因此可以从第一次完整备份开始,恢复到最后一个日志备份期间的任何时间点。
完整的日志链以第一次完整备份或由简单恢复模式转为完整或大容量日志模式开始,到当前的时间点结束。
而从日志恢复数据要求从最近一次完整或差异备份到所恢复的时间点之间的日志链是连续的。
恢复次序
从备份恢复数据需要经历如下几步骤:
1.复制数据阶段:从完整备份和差异备份中将数据,索引页和日志复制到被恢复数据库文件。
2.Redo(roll forward)阶段:将记录在日志中的事务应用到从备份中复制过来的数据。使数据Roll Forward到指定的时间点.这个阶段完成后,数据库还处于不可使用阶段:
如图:
上面两部就是Restore
3.Undo(Roll Back)阶段:这也是传说中的Recovery,将任何未提交的事务回滚。这个阶段过后,数据库处于可用状态。任何后续备份将不能接着应用到当前数据库。
这个概念比如:
在连续两个日志链的日志备份,在第一个事务日志备份中定义的事务,在第二个事务日志备份中Commit.如果在第一个事务日志还原后使用了Recovery选项.也就是经历了Undo阶段。则事务1在Undo阶段会被回滚:
可见,日志备份1中的T1被回滚,在日志备份2中的Commit也就毫无意义。这也就是为什么经历过Undo阶段后不允许再恢复后续备份。因此,微软推荐的最佳实践是使用NoRecovery选项不进行Undo阶段。而在所有备份恢复后单独进行Undo阶段,这个操作可以通过还原日志尾部时,指定Recovery选项进行。
总结
本文简单介绍了在完整恢复模式下,日志的作用以及对数据恢复的一些概念。理解完整恢复模式的概念对于减少数据丢失的风险是无可替代的。