SQL Server事务日志及其组织

事务日志又称为重做日志,Oracle与SQL Server中的事务日志功能是类似的。与Oracle不同的是,对数据库添加重做日志文件时,可以如同SQL Server数据库的数据文件一样指定初始化大小及增长率、最大大小属性等属性。Oracle数据库添加事务日志文件时,只能指定初始大小,不能指定增长率、最大大小属性等属性

事务日志支持的操作

SQL Server中靠日志来维护一致性(当然,日志的作用非常多,但一致性是日志的基本功能,其他功能可以看作是额外的功能)。
  事务日志支持以下操作:

  • 恢复个别的事务
    如果应用程序发出 ROLLBACK 语句,或者数据库引擎检测到错误(例如失去与客户端的通信),使用日志记录回退未完成的事务所做的修改。
  • 在 SQL Server 启动时恢复所有未完成的事务
    运行 SQL Server 的服务器发生故障时,数据库可能处于这样的状态:还没有将某些修改从缓存写入数据文件,在数据文件内有未完成的事务所做的修改。 启动 SQL Server 实例时,它将对每个数据库执行恢复操作,在事务日志中找到的每个未完成的事务并进行回滚,以确保数据库的完整性。这种恢复称为实例恢复
  • 将还原的数据库、文件、文件组或页前滚至故障点
    在硬件丢失或磁盘故障影响到数据库文件后,用户用过去的数据库备份来恢复数据库。而过去的数据库备份数据显然是当初备份时的状态,不会包含从备份完成到数据库崩溃时刻这段时间内产生的数据,因为重做日志文件中记录了所有数据的修改,SQL Server会把事务日志中的操作记录应用到恢复的数据文件,从而可以使数据库恢复到数据库存储介质发生故障的时刻,这种恢复称为介质恢复
  • 支持事务复制
    事务复制的原理是先将发布服务器数据库中的初始快照发送到各订阅服务器,然后监控发布服务器数据库中数据发生的变化,捕获个别数据变化的事务并将变化的数据发送到订阅服务器。日志读取器代理程序监视已为事务复制配置的每个数据库的事务日志,并将已设复制标记的事务从事务日志复制到分发数据库中。只有已提交的事务才能发送到分发数据库中。
  • 支持高可用性和灾难恢复解决方案
    备用服务器解决方案、AlwaysOn 可用性组、数据库镜像和日志传送极大程度上依赖于事务日志。

事务日志文件的组织

1. 事务日志物理体系结构

事务日志仅仅是记录与其对应数据库上的事务行为和对数据库修改的日志文件。在你新建数据库时,伴随着数据库文件,会有一个默认以ldf为扩展名的事务日志文件。当然,一个数据库也可以配有多个日志文件。
  SQL Server把一个物理日志文件从逻辑上划分为多个虚拟日志文件(Virtual Log File,VLF)。用个类比方法来看,日志文件(ldf)好比一趟火车,每一节车厢都是一个虚拟日志文件(VLF)。


  那为什么SQL Server要把日志文件划分出多个VLF呢?因为SQL Server通过这种方式使得存储引擎管理事务日志更加有效。物理日志以虚拟日志(VLF)为最小单位进行增长、收缩和使用,维护日志的时候也只需维护少量的VLF,这样对于日志空间的重复利用也会更加高效。
  SQL Server把所有物理日志文件当成一个连续的文件看待,顺序写入日志记录,用完第一个,再用下一个。即第一个日志文件的当前空间,如果没有可分配的VLF时,就会使用下一个日志文件的VLF,直到最后一个日志文件也没有可分配的VLF时,会重新回到第一个日志开始增长。多个日志文件之间并不存在镜像关系,也没有重做日志组的概念。VLF的使用如下图:

  VLF的数量以及每个VLF的大小由SQL Server根据日志文件的大小及增长率自动确定,即VLF没有固定大小,且日志文件所包含的VLF数不固定。在日志文件增长时,SQL Server也会重新规划VLFS的数量。
  SQL Server创建数据库时,根据日志文件(ldf)的大小,生成VLF的数量公式如下:

  从上面的公式图看到如果每次日志文件一点一点增长,比如1M1M地增长,那么到64M的时候,就会生成64x4个VLF;但是如果日志文件直接增长64M,最终生成的VLF数量只有8个。如果这些日志文件由于许多微小增量而增长到很大,则它们将具有很多VLF,也就是日志文件碎片, 这会降低数据库启动以及日志备份和还原操作的速度。
  所以,当我们在创建数据库的时候需要设置合适的文件的大小,使得文件的大小起码可以应付一段时间的增长。同时,也不要一下子就去创建一个很大的日志文件,因为里面可能只包含很少的VLF,最后却发挥不了太大作用,反而导致磁盘空间不足的错误发生。
  一个VLF可以以下面4种状态之一存在:

  • active:包含活动的事务,活动的事务指未结束的事务。
  • recoverable:不包含活动事务,但数据库此时处于维护一个完整日志序列的状态,而这些VLF还未进行备份,所以这时不能转变为 reusable状态使得其被重用,如果被重用覆盖,一个完整的日志序列就不连续了。
  • reusable:完全恢复模式下已经备份,或者简单恢复模式下,未包含活动事务。
  • unused:这个VLF从未被用到。
2. 事务日志逻辑体系结构

当针对数据库对象所做的任何修改保存到数据库之前,相应的数据库逻辑操作的记录首先会被记录到日志文件。这个记录会被按照先后顺序记录到日志文件的逻辑末尾,并分配一个全局唯一的日志序列号(log sequence number,简称LSN),这个序列号完全是按照顺序来的,如果日志中两个序列号LSN2>LSN1,则说明LSN2所在LSN1之后发生的。


  数据库中的事务日志映射在一个或多个物理文件上。 从概念上讲,SQL Server 事务日志按逻辑运行,就好像事务日志是一串日志记录一样。 从物理上讲,日志记录序列被有效地存储在实现事务日志的物理文件集中。
   日志记录按创建时的串行序列存储。** 每条日志记录都包含其所属事务的 ID。** 对于每个事务,与事务相关联的所有日志记录通过使用可提高事务回滚速度的向后指针挨个链接在一个链中。

SQL Server用日志记录来保证事务的基本属性,及数据库恢复。

活动日志
  MinLSN 是成功进行数据库范围内回滚所需的最早日志记录的日志序列号。 日志文件中从 MinLSN 到最后写入的日志记录这一部分称为日志的活动部分,或者称为活动日志。 这是进行数据库完整恢复所需的日志部分。 永远不能截断活动日志的任何部分。 所有的日志记录都必须从 MinLSN 之前的日志部分截断。


  下图显示了具有两个活动事务的结束事务日志的简化版本。 检查点记录已压缩成单个记录。

LSN 148 是事务日志中的最后一条记录。 在处理 LSN 147 处记录的检查点时,Tran 1 已经提交,而 Tran 2 是唯一的活动事务。 这就使 Tran 2 的第一条日志记录成为执行最后一个检查点时处于活动状态的事务(处于活动状态即还未commit,只有未commit的事务才能rollback)的最旧日志记录。 这使 LSN 142(Tran 2 的开始事务记录)成为 MinLSN。
  活动日志必须包括所有未提交事务的每一部分。 如果应用程序开始执行一个事务但未提交或回滚,将会阻止数据库引擎推进 MinLSN。

日志截断

物理日志的回绕
  事务日志是一种回绕的文件。 例如,假设有一个数据库,它包含一个分成四个虚拟日志文件的物理日志文件。 当创建数据库时,逻辑日志文件(具有日志记录的部分的VLF)物理日志文件(包含所有的VLF)的始端开始。 新日志记录被添加到逻辑日志的末端,然后向物理日志的末端扩张。日志截断将释放记录全部在最小恢复日志序列号 (MinLSN) 之前出现的所有虚拟日志,被截断的日志部分标记为可重用。


当逻辑日志的末端到达物理日志文件的末端时,新的日志记录将回绕到物理日志文件的始端。

这个循环不断重复,只要逻辑日志的末端不到达逻辑日志的始端。
  如果经常截断旧的日志记录,始终为到下一个检查点前创建的所有新日志记录保留足够的空间,则日志永远不会填满。

日志截断
  日志截断主要用于阻止日志填充。日志截断把数据库日志文件中不包含活动事务(未结束的事务)的VLF状态修改为reusable,释放逻辑日志中的空间以便物理事务日志重用这些空间。 如果事务日志从不截断,它最终将填满分配给物理日志文件的所有磁盘空间。 但是,在截断日志前,必须执行检查点操作,将当前内存中的脏页和事务日志信息从内存写入磁盘。
  下列各图显示了截断前后的事务日志。 第一个图显示了从未截断的事务日志。 当前,逻辑日志使用四个虚拟日志文件。 逻辑日志开始于第一个逻辑日志文件的前面,并结束于虚拟日志 4。 MinLSN 记录位于虚拟日志 3 中。 虚拟日志 1 和虚拟日志 2 仅包含不活动的日志记录。 这些记录可以截断。 虚拟日志 5 仍未使用,不属于当前逻辑日志。


第二个图显示了日志截断后的情形。 已释放虚拟日志 1 和虚拟日志 2 以供重新使用。 现在,逻辑日志开始于虚拟日志 3 的开头。 虚拟日志 5 仍未使用,它不属于当前逻辑日志。

  除非由于某些原因导致延迟,否则将在以下事件后自动发生日志截断:

  • 简单恢复模式下,在检查点之后发生。
  • 完整恢复模式或大容量日志恢复模式下,在日志备份之后发生(如果自上次备份后出现检查点)。

如何查看事务日志记录

大家知道在完整恢复模式下,SQLSERVER会记录每个事务所做的操作,这些记录会存储在事务日志里,那么事务日志记录怎么查看,里面都记录了些什么?
  事务日志记录里很多东西可以看的,里面记录了非常详细的数据库活动信息。打开可以利用下面SQL语句来查看所在数据库的事务日志记录:

USE [GPOSDB] --要查看事务日志记录的数据库
GO
SELECT * FROM [sys].[fn_dblog](NULL,NULL)

在SSMS中执行查询日志操作之后可以看到所有的日志记录,我截取了部分的结果,图中有几列,下面说明一下其中几列的意思:

  • CurrentLSN:当前LSN号,事务日志中的每个记录都由一个唯一的日志序列号 (LSN) 标识。LSN 是这样排序的:如果 LSN2 大于 LSN1,则 LSN2 所标识的日志记录描述的更改发生在日志记录 LSN1 描述的更改之后。
  • Operation列中记录了对应的LSN所做的操作。下面列出Operation几种比较常见而重要的值:
  • LOP_BEGIN_XACT 事务的开始
  • LOP_LOCK_XACT 获取锁
  • LOP_MODIFY_ROW 修改行(具体修改的对象可以查看AllocUnitName)
  • LOP_COMMIT_XACT 提交事务
  • LOP_DELETE_ROWS 删除数据
  • LOP_INSERT_ROWS 插入数据
  • Context:操作的上下文。
  • Transaction Name显示了创建的数据库的名称。
  • TransactoinID:事务ID号。
  • Log Record Fixed Length:LSN记录的所占虚拟日志文件的固定长度。
  • Previous LSN:前一个LSN号。
  • AllocUnitID:修改的那条数据所属分配单元ID
  • AllocUnitName:修改了数据的表名。
  • Slot ID:数据所在数据页面的第几条记录
  • PartitionID:数据所在数据页面的所在分区ID

推荐阅读更多精彩内容