SQL Server 2000 数据结构剖析(1): 日志文件结构

SQL Server 2000是一个可以自增长的可复写的循环文件,最小增长大小为248K(256K-8K),而一个数据库的多个日志文件,是被轮流选择写入日志的,也就是同时循环使用,所以日志文件如果要增长,也是轮流依次增长。

每一个事务日志文件都被分成若干个小的部分,称为虚拟日志文件(VLF),它是SQL Server日志的最小单元,当整个VLF中的所有记录不在包含活动事务(未提交事务)的信息的时候,比如存在这个VLF的事务刚好提交了,那么这个VLF就处于不活动的状态,能够被SQL Server所复用。

日志文件刚创建时,被分割成如干个相同大小的VLF文件(最少2个,最多16个),创建时,日志文件如果大于1MB,VLF的大小总是64K的倍数,下表提供了日志文件刚创建时的分配情况:

日志创建时大小 VLF数量 每个VLF的大小
小于1MB 2-4个 248KB-334KB
1MB-64MB 4个 256K-16MB
64MB-1GB 8个 8MB-128MB
1GB以上 16个 64MB或更大

日志文件的前8K用来存放日志头信息,后面依次存放VLF,所以,对于一个1MB大小的日志文件,先存放一个8K的日志头以后,显然是放不下4个256K的VLF的,其实SQL Server是存了3个256K的VLF和1个256K-8K=248K的VLF。

日志头信息

这8K的文件头也并非填满了信息,其实只有前42个字节是有用的信息,从42-8192这部分字节都是补0的。下面介绍着42个字节究竟存放了哪些信息。

数据类型 名称 大小 描述
UCHAR lfh_fixedValue 1 用于保护以下区域的重复映射.在SQL Server 2000中,被设为0xAB
UCHAR lfh_parity 1 在0x40和0x80之间切换,以防止这段VLF被重用.在做位填充操作时,在每512字节的小边界处会填充这个值,以便在恢复时判断日志的结尾
USHORT lfh_versionNo 2 日志文件的格式版本,SQL Server 7时,此值为1,SQL Server 2000时,此值为2
ULONG lfh_fSeqNo 4 VLF的序列号,如果是不活动的,则为0
ULONG lfh_status 4 活动的是0x02,否则为0
ULONG lfh_writeSeqNo 4 每次更新时的VLF序列号自增长计数器
ULONGLONG lfh_fileSize 8 VLF的大小
ULONGLONG lfh_startOffset 8 物理文件开始到这个头文件的位移
LSN lfh_createLSN 10 此时需要创建的LSN

日志块结构

每个VLF会包含若干个日志块(log block),而每个日志块依次存放以下信息:

日志块头信息,日志记录(用一个4字节的边界隔开),位置槽数组,一段空白区(用于伸缩填充,以确保后面剩余512个字节作为边界),512字节的边界

image

日志块总是512字节的倍数(NTFS的sector的大小),最大不会超过60K,一个日志块是一次日志I/O的基本单元. 通常情况下,VLF中的日志块是被填的满满的,但也有时候,VLF中的最后一个日志块是空的,如果VLF剩余的空间装不下这个记录的时候,会保留这个空日志块,而直接去填充下一个VLF。

日志块头信息

名称 大小 偏移量 描述
lbk_version 2 0x0 日志块的格式版本,SQL Server 7是1,SQL Server 2000是2
lbk_noOfRecords 2 0x2 日志块中日志记录的个数
lbk_offsetSlotArray 2 0x4 位置槽数组的偏移量
lbk_totalSize 2 0x6 日志块的总大小
lbk_status 2 0x8 被填充为0x01,非填充为0
lbk_prevBlkSize 2 0xA 上一个日志块的起始位置的偏移量,这样可以快速地定位前一个日志块的位置,因此SQL Server是可以反向扫描日志块的
lbk_startLSN 10 0xC 块中第一条记录的LSN
lbk_pad 2 0x16 用于补齐头信息为四字节的整数倍

位置槽数组

位置槽数组是一串2字节偏移量值的数组,反向记录每条记录的偏移量.如下图

image

字节填充

由于日志块是日志I/O的最小单元,所以日志块占用的磁盘段要么全部写入磁盘,要么全都不写。未能在读取日志块的时候能够检查这种状态,采用一种在这些sector的开头标识一个相同的小标记,这个标记在一个VLF中公用同一个,就是lfh_parity的值,每次重用的时候,在0x40和0x80之间切换,即上次使用了0x40这标记,这次就采用0x80(是不是日志块只有一部分被写到了磁盘中,因为日志块是512字节的倍数,它包换若干个sector,而磁盘的写入是以sector为单位的。)

当然,光这么标记也不行,更重要的是记录这个块的开头和结尾,于是在开头sector多加0X10,在结尾sector多加0x08。在NTFS中,读取一个被重映射的sector,可能会读到一段被0xFE填充的字节,但并不返回错误报告,虽然0xFE的0x40和0x80位都是1,并且0x10和0x08位也都是1,但是显然它是个有问题的,怎么办呢,我们通过0x20位来进行检验(我们之前无论怎么标记,0x20位一定是0,但是如果是0xFE是1,那就说明有问题了)

具体规则如下,假设sector开头的那个小标记叫B

if(B AND 0x02==1),说明这个字节很可能是0xFE,可能文件系统经历过磁盘硬件故障
if(B AND 0x10==1),日志段的第一个sector
if(B AND 0x08==1),日志段的最后一个sector
if(B AND lfh_parity == 0),旧的日志块

字节填充技术也用于在启动时检测日志的真实结尾,找到最后一个VLF的最后一个日志块,由于日志文件头并没有提供这个VLF中有多少个日志块,所以需要另外的表示方法来确定当前读取的日志块是否属于当前的VLF。

逻辑日志文件(LLF)

VLF以逻辑日志文件LLF的形式放在日志中,LLF是用于表示一个VLF或存放在备份设备中的临时对象,下表显示了LLF与VLF的关系

日志文件 日志备份
VLF 11 LLF1
VLF 12 LLF2
VLF 13 LLF3
VLF 14 LLF4
VLF 9 LLF5
VLF 10 LLF6
  LLF7
  LLF8

日志备份存放了原来的VLF1-VLF8,其实一个LLF就是一个VLF,他们的结构是相同的,只不过LLF会存放在日志备份中,而VLF只存放在日志文件中

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页