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'
,'')

使用 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: (:)

BUFFER:

BUF @0x000000027D15AC80

bpage = 0x000000026B6BA000          bhash = 0x0000000000000000          bpageno = (:)
bdbid = breferences = bcputicks =
bsampleCount = bUse1 = bstat = 0x10b
blog = 0x1215accc bnext = 0x0000000000000000 PAGE HEADER: Page @0x000000026B6BA000 m_pageId = (:) m_headerVersion = m_type =
m_typeFlagBits = 0x0 m_level = m_flagBits = 0x8000
m_objId (AllocUnitId.idObj) = m_indexId (AllocUnitId.idInd) =
Metadata: AllocUnitId =
Metadata: PartitionId = Metadata: IndexId =
Metadata: ObjectId = m_prevPage = (:) m_nextPage = (:)
pminlen = m_slotCnt = m_freeCnt =
m_freeData = m_reservedCnt = m_lsn = (::)
m_xactReserved = m_xdesId = (:) m_ghostRecCnt =
m_tornBits = DB Frag ID = Allocation Status GAM (:) = ALLOCATED SGAM (:) = ALLOCATED
PFS (:) = 0x61 MIXED_EXT ALLOCATED 50_PCT_FULL DIFF (:) = CHANGED
ML (:) = NOT MIN_LOGGED Slot Offset 0x60 Length Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size =
Memory Dump @0x000000000BE5A060 : 30000c00 002b0049 ................+.I
: 0065006e 006e0069 .Y.D.e.n.n.i.s. .G.a
: 006f0042 006a0069 006e0067 .o.B.e.i.j.i.n.g. .H
000000000000003C: 0061006e .a.i.d.i.a.n.8.8.8.8
: .8.8.8.8. Slot Column Offset 0x4 Length Length (physical) Id = Slot Column Offset 0x17 Length Length (physical) Name = Dennis Gao Address = [BLOB Inline Data] Slot Column Offset 0x2b Length Length (physical) BE4FC70: 69006a00 69006e00 B.e.i.j.i.n.g. .H.a.
000000000BE4FC84: 6e00 i.d.i.a.n. Slot Column Offset 0x49 Length Length (physical) Phone =

可以看出上面的 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 发表自博客园个人技术博客,未经作者本人同意禁止任何形式的转载,任何自动或人为的爬虫转载或抄袭行为均为耍流氓。

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

  1. SQL Server 事务日志传输

    概述 可以使用日志传送将事务日志不间断地从一个数据库(主数据库)发送到另一个数据库(辅助数据库).不间断地备份主数据库中的事务日志,然后将它们复制并还原到辅助数据库,这将使辅助数据库与主数据库基本保持 ...

  2. SQL Server事务日志介绍

    SQL Server中的数据库都是由一或多个数据文件以及一或多个事务日志文件组成的. 顾名思意,数据文件主要存储数据库的数据,包括数据库内容结构,数据页,索引页等等.那么事务日志到底是干什么的呢?它主 ...

  3. SQL SERVER 事务日志 解析

    1 基本介绍 每个数据库都具有事务日志,用于记录所有事物以及每个事物对数据库所作的操作. 日志的记录形式需要根据数据库的恢复模式来确定,数据库恢复模式有三种: 完整模式,完全记录事物日志,需要定期进行 ...

  4. SQL Server 事务日志截断、回绕与收缩(转载)

    每个 SQL Server 数据库都具有事务日志,用于记录所有事务以及每个事务对数据库所做的修改. 必须定期截断事务日志以避免它被填满. 但是,一些因素可能延迟日志截断,因此监视日志大小很重要. 某些 ...

  5. SQL Server 事务日志文件已满,收缩日志文件(9002)

    错误如下图: 1.数据库 → 属性 → 选项 → 恢复模式 → 选择‘简单’:如下图: 2.任务 → 收缩 → 文件类型‘文件’ → 收缩模式‘在释放未使用的空间前重新组织页’,将文件收缩到K,大小填 ...

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

    当在 SQL Server 数据库中创建一张表时,会在多张系统基础表中插入所创建表的信息,用于管理该表.通过目录视图 sys.tables, sys.columns, sys.indexes 可以查看 ...

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

    SQL Server 数据库安装后会包含 4 个默认系统数据库:master, model, msdb, tempdb. SELECT [name] ,database_id ,suser_sname ...

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

    SQL Server 的内存管理是一个庞大的主题,涉及特别多的概念和技术,例如常见的 Plan Cache.Buffer Pool.Memory Clerks 等.本文仅是管中窥豹,描述常见的内存管理 ...

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

    在 SQL Server 中,当数据库启动后,SQL Server 会为每个物理 CPU(包括 Physical CPU 和 Hyperthreaded)创建一个对应的任务调度器(Scheduler) ...

随机推荐

  1. 一个未解决的samba问题

    话说,现在的打复印扫描一体机的扫描功能十分丰富,扫描后的文件可以通过邮件发送,可以发到windows的共享.一直用着windows共享的方式,但是windows系统占用的内存还是略大,想把这个共享放到 ...

  2. Android NDK, No rule to make target

    这种问题一般是android.mk里面没有找到对应的源文件 http://stackoverflow.com/questions/11570167/android-ndk-no-rule-to-mak ...

  3. linux开机启动程序

    一./etc/rc.local这是一个最简单的方法,编辑“/etc/rc.local”,把启动程序的shell命令输入进去即可(要输入命令的全路径),类似于windows下的“启动”. 使用命令 vi ...

  4. [[UIScreen mainScreen] bounds]获取尺寸不对

    http://www.cocoachina.com/bbs/read.php?tid-264373.html [[UIScreen mainScreen] bounds]获取尺寸不对       我在 ...

  5. C#实现:给定任意要给字符串,输出所有可能的回文的子字符串集合。

    class Program { static void Main(string[] args) { string testStr = "sdfadfdsfadfdsfsdf"; i ...

  6. JSP内置对象有哪些呢?

    内置对象 request request 对象是 javax.servlet.httpServletRequest类型的对象. 该对象代表了客户端的请求信息,主要用于接受通过HTTP协议传送到服务器的 ...

  7. java四种内部类

    内部类有成员内部类,局部内部类,匿名内部类,静态内部类. 1,成员内部类package innerClass; public class InnerClassTest { String s1=&quo ...

  8. iOS 应用中有页面加载gif动画,从后台进入前台时就消失了

    解决办法: 在Appdelegate.m 里面有一个从后台进入前台所响应的方法,可以在该方法里post 一个通知,在加载动画里的页面接受通知,响应一定的方法即可 #pragma -mark 当程序进入 ...

  9. 如何用70行Java代码实现深度神经网络算法(转)

    对于现在流行的深度学习,保持学习精神是必要的——程序员尤其是架构师永远都要对核心技术和关键算法保持关注和敏感,必要时要动手写一写掌握下来,先不用关心什么时候用到——用不用是政治问题,会不会写是技术问题 ...

  10. 阿里云安装Tomcat

    1.Apache官方网站下载Tomcat http://mirrors.hust.edu.cn/apache/tomcat/tomcat-8/v8.0.35/bin/apache-tomcat-8.0 ...