人人都是 DBA(VI)SQL Server 事务日志

原文:人人都是 DBA(VI)SQL Server 事务日志

SQL Server 的数据库引擎通过事务服务(Transaction Services)提供事务的 ACID 属性支持。ACID 属性包括:

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

事务日志(Transaction Log)

事务日志(Transaction Log)存储的是对数据库所做的更改信息,让 SQL Server 有机会恢复数据库。而恢复(Recovery)的过程就是使数据文件与日志保持一致的过程。任何在日志中指示已经提交的数据更改必须出现在数据文件中,任何未标记为提交的更改不能出现在数据文件中。

预写日志(Write-ahead Logging)功能确保在真正发生变化的数据页写入磁盘前,始终先在磁盘中写入日志记录,使得任务回滚成为可能。写入事务日志(Transaction Log)是同步的,即 SQL Server 必须等它完成。但写入数据页可以是异步的,所以可以在缓存中组织需要写入的数据页进行批量写入,以提高写入性能。

事务日志用于保证 SQL Server 在语句或系统出现故障时的可恢复性,并允许将备份的日志应用到数据库上。但事务日志并没有提供很好的可读性,实际上读取事务日志通常也不会获取到太多有用信息。更推荐的跟踪记录机制是使用 SQL Server Profiler 等工具,以筛选和捕获有用的信息。

比如,我们使用下面的 SQL 来创建一张简单的 Table,来尝试观察事务日志的变化。

CREATE TABLE [dbo].[Customer](
    [Id] [bigint] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](256) NOT NULL,
    [Address] [nvarchar](max) NULL,
    [Phone] [nvarchar](256) NULL
) ON [PRIMARY]

插入一条记录。

INSERT INTO [dbo].[Customer]
           ([Name]
           ,[Address]
           ,[Phone])
     VALUES
           ('Dennis Gao'
           ,'Beijing Haidian'
           ,'88888888')

使用 DBCC LOG 命名可以先观察产生的序列。

DBCC LOG([TEST])

使用系统提供的函数 sys.fn_dblog 来查看当前的事务日志记录,可以列出很多详细信息,这里只显示了几个常用的列。

SELECT [Current LSN]
    ,[Operation]
    ,[Context]
    ,[Transaction ID]
    ,[Log Record Length]
    ,[Previous LSN]
    ,[AllocUnitId]
    ,[AllocUnitName]
    ,[Page ID]
    ,[Slot ID]
    ,[Xact ID]
FROM sys.fn_dblog(NULL, NULL)

事务日志总是连续的并且是顺序的,按照 LSN(Log Sequence Number)的顺序排列。从查询的尾部可以查看 AllocUnitName 操作的数据表名称。

对应的 Operation 是 LOP_INSERT_ROWS,Context 是 LCX_HEAP,也就是插入数据到堆表。同时发现 Page ID 是 0001:00000078,也就是十进制的 120 号页面。

可以使用 DBCC PAGE 命令查看 Page 页信息。

dbcc page ( {'dbname' | dbid}, filenum, pagenum [, printopt={0|1|2|3} ])
DBCC TRACEON(3604, -1)
GO

DBCC PAGE([TEST], 1, 120, 3)
GO
PAGE: (1:120)

BUFFER:

BUF @0x000000027D15AC80

bpage = 0x000000026B6BA000          bhash = 0x0000000000000000          bpageno = (1:120)
bdbid = 7                           breferences = 0                     bcputicks = 0
bsampleCount = 0                    bUse1 = 8595                        bstat = 0x10b
blog = 0x1215accc                   bnext = 0x0000000000000000          

PAGE HEADER:

Page @0x000000026B6BA000

m_pageId = (1:120)                  m_headerVersion = 1                 m_type = 1
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x8000
m_objId (AllocUnitId.idObj) = 87    m_indexId (AllocUnitId.idInd) = 256
Metadata: AllocUnitId = 72057594043629568
Metadata: PartitionId = 72057594039107584                                Metadata: IndexId = 0
Metadata: ObjectId = 565577053      m_prevPage = (0:0)                  m_nextPage = (0:0)
pminlen = 12                        m_slotCnt = 1                       m_freeCnt = 8005
m_freeData = 185                    m_reservedCnt = 0                   m_lsn = (33:460:24)
m_xactReserved = 0                  m_xdesId = (0:0)                    m_ghostRecCnt = 0
m_tornBits = 0                      DB Frag ID = 1                      

Allocation Status

GAM (1:2) = ALLOCATED               SGAM (1:3) = ALLOCATED
PFS (1:1) = 0x61 MIXED_EXT ALLOCATED  50_PCT_FULL                        DIFF (1:6) = CHANGED
ML (1:7) = NOT MIN_LOGGED           

Slot 0 Offset 0x60 Length 89

Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP VARIABLE_COLUMNS
Record Size = 89
Memory Dump @0x000000000BE5A060

0000000000000000:   30000c00 01000000 00000000 04000003 002b0049  0................+.I
0000000000000014:   00590044 0065006e 006e0069 00730020 00470061  .Y.D.e.n.n.i.s. .G.a
0000000000000028:   006f0042 00650069 006a0069 006e0067 00200048  .o.B.e.i.j.i.n.g. .H
000000000000003C:   00610069 00640069 0061006e 00380038 00380038  .a.i.d.i.a.n.8.8.8.8
0000000000000050:   00380038 00380038 00                          .8.8.8.8.

Slot 0 Column 1 Offset 0x4 Length 8 Length (physical) 8

Id = 1                              

Slot 0 Column 2 Offset 0x17 Length 20 Length (physical) 20

Name = Dennis Gao                   

Address = [BLOB Inline Data] Slot 0 Column 3 Offset 0x2b Length 30 Length (physical) 30

000000000BE4FC70:   42006500 69006a00 69006e00 67002000 48006100  B.e.i.j.i.n.g. .H.a.
000000000BE4FC84:   69006400 69006100 6e00                        i.d.i.a.n.

Slot 0 Column 4 Offset 0x49 Length 16 Length (physical) 16

Phone = 88888888   

可以看出上面的 SQL 语句 Insert 了数据 Id = 1, Name = Dennis Gao, Phone = 88888888。

虚拟日志文件(VLF:Virtual Log File)

不管为事务日志定义多少个物理文件,SQL Server 总是把日志当成连续流(Contiguous Stream)来对待。当 DBCC SHRINKDATABASE 命令确认日志可以缩小多少时,它不是单独考虑每个日志文件,而是根据整个日志来确定可压缩大小。

SQL Server 数据库的事务日志是通过虚拟日志文件(VLF:Virtual Log File)来管理的,VLF 的大小由 SQL Server 根据日志的总大小和日志增量大小来决定,不能通过配置指定。如果 VLF 数量变多会导致数据库性能下降,所以需要指定合理的日志文件初始大小和增长步长,防止过多的 VLF 的产生。

SQL Server 会根据如下规则来判断 VLF 的数量:

 日志大小 
 VLF 数量 


Size <= 1MB 


 将日志文件大小除以最小 VLF 大小(31KB*8KB)确定个数 


1MB < Size <= 64MB


 4 个


 64MB < Size <= 1GB 


 8 个


Size > 1GB


 16 个

当日志持续增长时,会使用相同的方式确定新添加的 VLF 的数量。日志总是以整个 VLF 为单位增长,而且缩小也只能到 VLF 的边界为止。

VLF 可以处于以下 4 种状态之一。

  • Active:日志的活动部分,从未提交事务的最小 LSN 开始,结束于最后一个写入的 LSN。
  • Recoverable:在最早的活动事务之前的那部分日志。
  • Reusable:如果日志已经被备份,则不需要最早活动事务之前的 VLF,可重用这些空间。日志截断或备份会将 Recoverable VLF 转换成 Reusable VLF。
  • Unused:未使用的部分。

可以使用下面的 SQL 查询 VLF 的数量。

CREATE TABLE #VLFInfo (
    RecoveryUnitID INT
    ,FileID INT
    ,FileSize BIGINT
    ,StartOffset BIGINT
    ,FSeqNo BIGINT
    ,[Status] BIGINT
    ,Parity BIGINT
    ,CreateLSN NUMERIC(38)
    );

CREATE TABLE #VLFCountResults (
    DatabaseName SYSNAME
    ,VLFCount INT
    );

EXEC sp_MSforeachdb N'Use [?]; 

                INSERT INTO #VLFInfo
                EXEC sp_executesql N''DBCC LOGINFO([?])''; 

                INSERT INTO #VLFCountResults
                SELECT DB_NAME(), COUNT(*)
                FROM #VLFInfo; 

                TRUNCATE TABLE #VLFInfo;'

SELECT DatabaseName
    ,VLFCount
FROM #VLFCountResults
ORDER BY VLFCount DESC;

DROP TABLE #VLFInfo;

DROP TABLE #VLFCountResults;

可以使用 DBCC LOGINFO 命令进一步观察 VLF 的相关属相。

DBCC LOGINFO

SQL Server 可以配置多个物理日志文件当做一个序列流来对待。如果管理良好,定期备份或截断日志,可能永远都不会使用除第一个文件之外的其他日志文件。当需要新的 VLF 时,多个物理文件中都没有可用 VLF,则会以循环的方式把新的 VLF 添加到每个物理日志文件中。

自动截断模式(Auto Truncate Model)

如果 SQL Server 设置了如下情况,则认为没有维护日志备份:

  • 设置 SIMPLE 恢复模型,数据库会定期截断日志。
  • 从未进行过完全数据库备份。

以上任何一种情况下,SQL Server 会处于自动截断模式(Auto Truncate Model)中,当数据库事务日志满时就会进行截断。这里的 "满" 指的是日志记录的数量比在系统启动过程中、在合理的时间内能够重做的数量多。

判断数据库是否在自动截断模式的最简单的方法是查询 sys.database_recovery_status 目录视图,如果 last_log_backup_lsn 列为空,则数据库就是处于自动截断模式。

SELECT * FROM sys.database_recovery_status;

可以通过 DBCC SQLPERF 命名来查看日志文件大小。

DBCC SQLPERF('logspace')

当然,也可以通过系统提供的目录视图来查看。

SELECT instance_name AS [Database]
    ,cntr_value AS [LogFull(%)]
FROM sys.dm_os_performance_counters
WHERE counter_name LIKE 'Percent Log Used%'
    AND instance_name NOT IN (
        '_Total'
        ,'mssqlsystemresource'
        )
    AND cntr_value > 0
ORDER BY [LogFull(%)] DESC;

可以使用 DBCC 命令来压缩事务日志文件,下面是 DBCC SHRINKDATABASE 和 DBCC SHRINKFILE 的语法。

DBCC SHRINKDATABASE
( database_name | database_id | 0
     [ , target_percent ]
     [ , { NOTRUNCATE | TRUNCATEONLY } ]
)
[ WITH NO_INFOMSGS ]

DBCC SHRINKFILE
(
    { file_name | file_id }
    { [ , EMPTYFILE ]
    | [ [ , target_size ] [ , { NOTRUNCATE | TRUNCATEONLY } ] ]
    }
)
[ WITH NO_INFOMSGS ]

 

《人人都是 DBA》系列文章索引:

 序号 
 名称 


1


 人人都是 DBA(I)SQL Server 体系结构


2


 人人都是 DBA(II)SQL Server 元数据


3


 人人都是 DBA(III)SQL Server 调度器


4


 人人都是 DBA(IV)SQL Server 内存管理


5


 人人都是 DBA(V)SQL Server 数据库文件


6


 人人都是 DBA(VI)SQL Server 事务日志


7


 人人都是 DBA(VII)B 树和 B+ 树


8


 人人都是 DBA(VIII)SQL Server 页存储结构


9


 人人都是 DBA(IX)服务器信息收集脚本汇编


10


 人人都是 DBA(X)资源信息收集脚本汇编


11


 人人都是 DBA(XI)I/O 信息收集脚本汇编


12


 人人都是 DBA(XII)查询信息收集脚本汇编


13


 人人都是 DBA(XIII)索引信息收集脚本汇编


14


 人人都是 DBA(XIV)存储过程信息收集脚本汇编 


15


 人人都是 DBA(XV)锁信息收集脚本汇编

本系列文章《人人都是 DBA》由 Dennis Gao 发表自博客园个人技术博客,未经作者本人同意禁止任何形式的转载,任何自动或人为的爬虫转载或抄袭行为均为耍流氓。

时间: 2024-08-31 20:36:45

人人都是 DBA(VI)SQL Server 事务日志的相关文章

SQL Server事务日志的几个常用操作

我们知道,SQL Server事务日志主要是用来记录所有事务对数据库所做的修改,如果系统出现故障,它将成为最新数据的唯一来源.日志的操作常有以下几个应用: 一.事务日志文件LDF的丢失 当我们不小删除或者LDF文件丢失的时候,数据库只剩下MDF文件,此时直接通过附加MDF是无法恢复数据库的,那我们怎么样才能恢复数据库呢?我们可以把SQL Server的日志文件分为两种形式:一类是无活动事务的日志,另一类是有活动事务的日志,我们分别根据两种情况来进行数据库恢复. 1.无活动事务的日志恢复 当文件并

SQL Server事务日志被填满的原因是什么

SQL Server事务日志可能会被填满,这会阻止之后的数据库操作,包括UPDATE, DELETE, INSERT 和CHECKPOINT. 事务日志填满会导致1105错误: Can't allocate space for object syslogs in database dbname because the logsegment is full. If you ran out of space in syslogs, dump the transaction log. Otherwis

SQL SERVER 事务日志

  谈到事务日志就不得不谈下检查点 Checkpoint ,简称 CKP. 事务日志和检查点都是为了数据库的快速恢复而存在的.我们需要知道检查点是干什么的,它和事务日志有什么关系. CKP是干什么的? 总所周知,数据的更改并没有直接写入mdf/ndf 数据文件中,而是写入了buffer pool中; 当CKP被触发后,检查点将"全部"脏数据页从当前数据库的buffer pool刷新到数据文件上.所谓的脏数据,是那些被更改的 data page ,在内存中却没有写入磁盘.全部脏数据包括已

人人都是 DBA(V)SQL Server 数据库文件

原文:人人都是 DBA(V)SQL Server 数据库文件 SQL Server 数据库安装后会包含 4 个默认系统数据库:master, model, msdb, tempdb. SELECT [name] ,database_id ,suser_sname(owner_sid) AS [owner] ,create_date ,user_access_desc ,state_desc FROM sys.databases WHERE database_id <= 4; master mas

人人都是 DBA(IV)SQL Server 内存管理

原文:人人都是 DBA(IV)SQL Server 内存管理 SQL Server 的内存管理是一个庞大的主题,涉及特别多的概念和技术,例如常见的 Plan Cache.Buffer Pool.Memory Clerks 等.本文仅是管中窥豹,描述常见的内存管理相关概念. 在了解内存管理之前,通过 sys.dm_os_memory_clerks 视图可以查询内存的使用职责(Memory Clerks),也就是内存的消耗者. SELECT [type], SUM(pages_kb) AS tota

人人都是 DBA(VIII)SQL Server 页存储结构

原文:人人都是 DBA(VIII)SQL Server 页存储结构 当在 SQL Server 数据库中创建一张表时,会在多张系统基础表中插入所创建表的信息,用于管理该表.通过目录视图 sys.tables, sys.columns, sys.indexes 可以查看新建的表的元数据信息. 下面使用创建 Customer 表的过程作为示例. USE [TEST] GO DROP TABLE [dbo].[Customer] GO CREATE TABLE [dbo].[Customer]( [I

人人都是 DBA(X)资源信息收集脚本汇编

原文:人人都是 DBA(X)资源信息收集脚本汇编 什么?有个 SQL 执行了 8 秒! 哪里出了问题?臣妾不知道啊,得找 DBA 啊. DBA 人呢?离职了!!擦!!! 程序员在无处寻求帮助时,就得想办法自救,努力让自己变成 "伪 DBA". 索引 获取数据库的 CPU 使用率 过去一段时间里 CPU 利用率的历史情况 谁用 CPU 工作的时间最长 服务器上安装了多大的 Memory SQL Server 进程用了多少 Memory 是否申请新的 Memory 无法得到 SQL Ser

人人都是 DBA(XI)I/O 信息收集脚本汇编

原文:人人都是 DBA(XI)I/O 信息收集脚本汇编 什么?有个 SQL 执行了 8 秒! 哪里出了问题?臣妾不知道啊,得找 DBA 啊. DBA 人呢?离职了!!擦!!! 程序员在无处寻求帮助时,就得想办法自救,努力让自己变成 "伪 DBA". 索引 数据文件和日志文件位置和大小 查看指定数据库文件的大小和可用空间 服务器 Disk 容量和挂载信息 查看 Disk 剩余空间 查询数据库设置的 Recovery Model 查看最近的 Full Backup 信息 获取所有数据库的

人人都是 DBA(XV)锁信息收集脚本汇编

原文:人人都是 DBA(XV)锁信息收集脚本汇编 什么?有个 SQL 执行了 8 秒! 哪里出了问题?臣妾不知道啊,得找 DBA 啊. DBA 人呢?离职了!!擦!!! 程序员在无处寻求帮助时,就得想办法自救,努力让自己变成 "伪 DBA". 索引 查看 Session 对应的 Thread 和当前 Command 侦测 Deadlocking 或阻塞问题 查看 Task 执行中哪个 Wait Type 最慢 查看当前 Task 的运行情况 查看 Lock Waits 状态 查看 La