本文是对SQL Server事务日志的总结,文章有一些内容和知识来源于官方文档或一些技术博客,本文对引用部分的出处都有标注。html
事务日志介绍sql
在SQL Server中,事务日志是数据库的重要组件,若是系统出现故障,则可能须要使用事务日志将数据库恢复到一致状态。每一个SQL Server数据库都拥有本身的事务日志,用于记录全部事务以及每一个事务对数据库所作的修改。那么数据库的哪些操做会记录在事务日志中呢?具体一点的说,这些操做包括:数据库
· 每一个事务的开始和结束。缓存
· 每次数据修改(插入、更新或删除)。 这包括系统存储过程或数据定义语言 (DDL) 语句对包括系统表在内的任何表所作的更改。安全
· 每次分配或释放区和页。架构
· 建立或删除表或索引。app
另外,像SELECT这样的操做是不会记录在事务日志当中的。若是你想对事务日志记录信息有一个直观的认识,那么你能够在测试环境作一些SELECT、INSERT、UPDATE、DDL等操做,而后使用ApexSQL Log这款工具查看具体的事务日志记录信息。ide
USE YourSQLDba;工具
GO性能
CREATE TABLE dbo.TEST(ID INT);
GO
INSERT INTO dbo.TEST SELECT 100;
GO
SELECT * FROM dbo.TEST;
GO
UPDATE dbo.TEST SET ID=101;
GO
DELETE FROM dbo.TEST WHERE ID=101;
GO
如上所示,像DDL、DML操做都会记录在事务日志当中,可是SELECT是不会记录在事务日志当中(固然SELECT INTO除外,其实SELECT INTO在事务日志里面转化为了CREATE TABLE形式)。另外,须要注意: 事务日志并非审计跟踪。也就是说事务日志并不能彻底替代审计跟踪。它不提供对数据库作出改变的审计跟踪;它不保持对数据库已执行命令的记录,只有数据如何改变的结果。其实不少对事务日志了解不深刻的人都觉得事务日志能够代替审计跟踪(曾经有项目经理想让我从事务日志当中挖掘出谁误删了数据,其实事务日志只会记录那个帐号删除了记录,并不会记录客户端信息,因此不能定位到谁删除了数据)。以下所示,咱们多了一个DROP TABLE操做。你会看到跟上面不同的结果。
USE YourSQLDba;
GO
CREATE TABLE dbo.TEST(ID INT);
GO
INSERT INTO dbo.TEST SELECT 100;
GO
SELECT * FROM dbo.TEST;
GO
UPDATE dbo.TEST SET ID=101;
GO
DELETE FROM dbo.TEST WHERE ID=101;
GO
DROP TABLE dbo.Test;
GO
这篇博客transactionlog中有一张图,描述了一个更新操做的流程中,事务日志在这个流程中的位置以及做用。想必看过这张图后,你们在大脑中会对事务日志的功能做用有一个初步的形象认识。
其实这张图还包含了不少隐藏的重要信息,下面咱们一一来述说一下:
预写式日志(Write-Ahead Logging)
什么是预写式日志呢? 其实其核心思想就是在变化的数据写入到数据库以前,将相关日志记录信息先写入到日志. SQL Server的预写式日志(Write-Ahead Logging)机制保证修改的描述(例如日志记录)会在数据自己修改写入数据文件前写入,会写入磁盘上的事务日志文件。它是SQL Server保证事务持久性(Durability)的基本机制。一个日志记录会包含已提交事务或未提交事务的详细信息,在数据被事务修改的不一样状况下,可能已经写入数据文件或还没来得及写入数据文件,这取决于检查点是否已发生。
浅谈SQL Server中的事务日志(二)----事务日志在修改数据时的角色 这篇博客有深刻浅出的介绍(以下所示):
Write-Ahead Logging的核心思想是:在数据写入到数据库以前,先写入到日志.
由于对于数据的每笔修改都记录在日志中,因此将对于数据的修改实时写入到磁盘并无太大意义,即便当SQL Server发生意外崩溃时,在恢复(recovery)过程当中那些不应写入已经写入到磁盘的数据会被回滚(RollBack),而那些应该写入磁盘却没有写入的数据会被重作(Redo)。从而保证了持久性(Durability)。
但WAL不只仅是保证了原子性和持久性。还会提升性能.
硬盘是经过旋转来读取数据,经过WAL技术,每次提交的修改数据的事务并不会立刻反映到数据库中,而是先记录到日志.在随后的CheckPoint和Lazy Writer中一并提交,若是没有WAL技术则须要每次提交数据时写入数据库......
官方文档SQL Server 事务日志体系结构和管理指南介绍以下(我的对翻译作了一下调整,也增长了一点点内容):
要了解预写日志的工做方式,了解如何将修改的数据写入磁盘很重要。SQL Server维护一个缓冲区缓存(buffer cache),在必须检索数据时从其中读取数据页。 在缓冲区缓存中修改页后,不会将其当即写回磁盘;而是将其标记为“脏”数据。在将数据页物理写入磁盘以前,这些脏数据能够屡次被修改。 对于每次逻辑写入,都会在日志缓存(log cache)中插入一条事务日志记录记录这些修改。在将关联的脏页从缓冲区缓存中删除并写入磁盘以前,必须将这条些日志记录写入磁盘。检查点进程按期在缓冲区高速缓存中扫描包含来自指定数据库的页的缓冲区,而后将全部脏页写入磁盘。 CHECKPOINT 可建立一个检查点,在该点保证所有脏页都已写入磁盘,从而在之后的恢复过程当中节省时间。
将修改后的数据页从高速缓冲存储器写入磁盘的操做称为刷新页。 SQL Server具备一个逻辑,它能够在写入关联的日志记录前防止刷新脏页。 日志记录将在提交事务时写入磁盘。
检查点做用
检查点将脏数据页从当前数据库的缓冲区高速缓存刷新到磁盘上。这最大限度地减小了数据库完整恢复时必须处理的活动日志,减小的崩溃恢复须要的时间。其实CheckPoint是为了优化IO和减小Recovery时间 在完整恢复时,需执行下列操做:
§ 前滚系统中止以前还没有刷新到磁盘上的日志记录修改信息。
§ 回滚与未完成的事务(如没有 COMMIT 或 ROLLBACK 日志记录的事务)相关联的全部修改。
检查点操做
检查点在数据库中执行下列过程:
· 将记录写入日志文件,标记检查点的开始。
· 将为检查点记录的信息存储在检查点日志记录链内。
· 记录在检查点中的一条信息是第一条日志记录的日志序列号 (LSN),它必须存在才能成功进行数据库范围内的回滚。 该 LSN 称为“最小恢复 LSN”(“MinLSN”)。 MinLSN 是下列各项中的最小者:
o 检查点开始的 LSN。
o 最先的活动事务起点的 LSN。
o 还没有传递给分发数据库的最先的复制事务起点的 LSN。
o 检查点记录还包含全部已修改数据库的活动事务的列表。
· 若是数据库使用简单恢复模式,检查点则标记在 MinLSN 前重用的空间。
· 将全部脏日志和脏数据页写入磁盘。
· 将标记检查点结束的记录写入日志文件。
· 将这条链起点的 LSN 写入数据库引导页。
致使检查点的活动
下列情形下将出现检查点:
· 显式执行 CHECKPOINT 语句。 用于链接的当前数据库中出现检查点。
· 在数据库中执行了最小日志记录操做,例如,在使用大容量日志恢复模式的数据库中执行大容量复制操做。
· 已经使用 ALTER DATABASE 添加或删除了数据库文件。
· 经过 SHUTDOWN 语句或经过中止 SQL Server (MSSQLSERVER) 服务中止了 SQL Server 实例。 任一操做都会在 SQL Server 实例的每一个数据库中生成一个检查点。
· SQL Server 实例在每一个数据库内按期生成自动检查点,以减小实例恢复数据库所需的时间。
· 进行了数据库备份。
· 执行了须要关闭数据库的活动。 例如,AUTO_CLOSE 设置为 ON ,而且关闭了数据库的最后一个用户链接,或者执行了须要从新启动数据库的数据库选项更改。
事务日志物理结构
SQL Server数据库中的事务日志能够有一个或多个事务日志文件。当存在多个事务日志文件时,这些日志文件也只能顺序调用,并不能并行使用,所以使用多个日志文件并不会带来性能上的提高(后面内容会展开讨论这个)。其实,若是你对ORACLE当中联机重作日志体系结构很是熟悉的话,多个事务日志文件就至关于多个redo log file,不一样的是,ORACLE下面的redo log能够实现多路复用(日志组能够有一个或多个一样的日志成员redo log file,多个日志成员的缘由是防止日志文件组内某个日志文件损坏后及时提供备份,因此同一组的日志成员通常内容信息相同,可是存放位置不一样)。通常会将同一组的不一样日志成员文件放到不一样的磁盘或不一样的裸设备上。以提升安全性。SQL Server彷佛没有这个架构设计。另外,ORACLE的REDO 与UNDO在结构设计上是分开的。而SQL Server能够经过事务日志进行REDO和UNDO操做。
事务日志逻辑结构
从逻辑结构上看,SQL Server对于日志文件的管理,是将逻辑上一个ldf文件划分红多个逻辑上的虚拟日志文件(virtual log files,简称VLFs).以便于管理。SQL Server事务日志按逻辑运行,就好像事务日志是一串日志记录同样。每条日志记录由一个日志序列号 (LSN) 标识。 每条新日志记录均写入日志的逻辑结尾处,并使用一个比前面记录的 LSN 更高的 LSN。 日志记录按建立时的串行序列存储。 每条日志记录都包含其所属事务的 ID。对于每一个事务,与事务相关联的全部日志记录经过使用可提升事务回滚速度的向后指针挨个连接在一个链中。 虚拟日志文件没有固定大小,且物理日志文件所包含的虚拟日志文件数不固定。 数据库引擎在建立或扩展日志文件时动态选择虚拟日志文件的大小。 数据库引擎尝试维护少许的虚拟文件。 在扩展日志文件后,虚拟文件的大小是现有日志大小和新文件增量大小之和。 管理员不能配置或设置虚拟日志文件的大小或数量。可是若是设置日志文件的增量太小,则会产生过多的VLFS,也就是日志文件碎片,过多的日志文件碎片会拖累SQL Server性能.所以,指定合适的日志文件初始大小和增加,是减小日志碎片最关键的部分.
事务日志是一种回绕的文件。 例如,假设有一个数据库,它包含一个分红四个虚拟日志文件的物理日志文件。 当建立数据库时,逻辑日志文件从物理日志文件的始端开始。 新日志记录被添加到逻辑日志的末端,而后向物理日志的末端扩张。 日志截断将释放记录所有在最小恢复日志序列号 (MinLSN) 以前出现的全部虚拟日志。 MinLSN 是成功进行数据库范围内回滚所需的最先日志记录的日志序列号。 示例数据库中的事务日志的外观与下图所示类似。
当逻辑日志的末端到达物理日志文件的末端时,新的日志记录将回绕到物理日志文件的始端。
上面关于事务日志的虚拟日志循环覆盖使用是否有点眼熟的感受,这个跟ORACLE下REDO LOG的循环覆盖使用的理念是如出一辙的。只不过是不一样的概念和不一样的实现方式。
事务日志功能
事务日志有啥功能呢?关于事务日志的功能,详细具体内容能够参考官方文档事务日志 (SQL Server),里面已经详细介绍了事务日志的几个功能,在此不作展开。
事务日志支持如下操做:
· 恢复个别的事务。
· 在SQL Server启动时恢复全部未完成的事务。
· 将还原的数据库、文件、文件组或页前滚至故障点。
· 支持事务复制。
· 支持高可用性和灾难恢复解决方案: AlwaysOn 可用性组、数据库镜像和日志传送。
事务日志截断
什么是事务日志截断呢? 在介绍事务日志截断前,咱们必须先了解一下MinLSN、活动日志(Actvie Log)等概念。
最小恢复LSN(Minimum Recovery LSN(MinLSN))概念
MinLSN是在还未结束的事务记录在日志中最小的LSN号,MinLSN是下列三者之一的最小值:
· CheckPoint的开始LSN
· 还未结束的事务在日志的最小LSN
· 还没有传递给分发数据库的最先的复制事务起点的 LSN.
从MinLSN到日志的逻辑结尾处,则称为活动日志(Active Log)。日志文件中从 MinLSN 到最后写入的日志记录这一部分称为日志的活动部分,或者称为活动日志(Active log)。 这是进行数据库完整恢复所需的日志部分。 永远不能截断活动日志的任何部分。全部的日志记录都必须从 MinLSN 以前的日志部分截断。也就是说永远不能截断活动日志的任何部分。
下图显示了具备两个活动事务的结束事务日志的简化版本。 检查点记录已压缩成单个记录。
LSN 148 是事务日志中的最后一条记录。 在处理 LSN 147 处记录的检查点时,Tran 1 已经提交,而 Tran 2 是惟一的活动事务。 这就使 Tran 2 的第一条日志记录成为执行最后一个检查点时处于活动状态的事务的最旧日志记录。 这使 LSN 142(Tran 2 的开始事务记录)成为 MinLSN。
活动日志必须包括全部未提交事务的每一部分。若是应用程序开始执行一个事务但未提交或回滚,将会阻止数据库引擎推动 MinLSN。 这可能会致使两种问题:
若是系统在事务执行了许多未提交的修改后关闭,之后从新启动时,恢复阶段所用的时间将比“恢复间隔”选项指定的时间长得多。
由于不能截断 MinLSN 以后的日志部分,日志可能变得很大。 即便数据库使用的是简单恢复模式,这种状况也有可能出现,在简单恢复模式下,每次执行自动检查点操做时一般都会截断事务日志。
日志截断其实指从SQL Server数据库的逻辑事务日志中删除不活动的虚拟日志文件,释放逻辑日志中的空间以便物理事务日志重用这些空间。 若是事务日志从不截断,它最终将填满分配给物理日志文件的全部磁盘空间。 可是,在截断日志前,必须执行检查点操做。检查点将当前内存中已修改的页(称为“脏页”)和事务日志信息从内存写入磁盘。 执行检查点时,事务日志的不活动部分将标记为可重用。 此后,日志截断能够释放不活动的部分。有关检查点的详细信息,请参阅数据库检查点 (SQL Server)。
关于日志截断,必须按期截断事务日志,防止其占满分配给物理日志文件的磁盘空间。日志截断并不减少物理日志文件的大小。 若要减小物理日志文件的物理大小,则必须收缩日志文件。
日志截断会在下面事件后自动进行截断:
简单恢复模式下,在检查点以后发生。
在完整恢复模式或大容量日志恢复模式下,若是自上一次备份后生成检查点,则在日志备份后进行截断(除非是仅复制日志备份)。
CHECKPOINT only truncates the transaction log (marks the VLF for reuse) only in simple recovery model. In Full recovery, you have to take log backup.
实际上,日志截断会因为多种缘由发生延迟。 查询 sys.databases 目录视图的 log_reuse_wait 和 log_reuse_wait_desc 列,了解哪些因素(若是存在)阻止日志截断。 下表对这些列的值进行了说明:
Log_reuse_wait 值 |
Log_reuse_wait_desc 值 |
说明 |
0 |
NOTHING |
当前有一个或多个可重复使用的虚拟日志文件。 |
1 |
CHECKPOINT |
自上第二天志截断以后,还没有生成检查点,或者日志头还没有跨一个虚拟日志文件移动。 (全部恢复模式) |
2 |
LOG_BACKUP |
在截断事务日志前,须要进行日志备份。 (仅限完整恢复模式或大容量日志恢复模式) |
3 |
ACTIVE_BACKUP_OR_RESTORE |
数据备份或还原正在进行(全部恢复模式)。 |
4 |
ACTIVE_TRANSACTION |
事务处于活动状态(全部恢复模式): |
5 |
DATABASE_MIRRORING |
数据库镜像暂停,或者在高性能模式下,镜像数据库明显滞后于主体数据库。 (仅限完整恢复模式) |
6 |
REPLICATION |
在事务复制过程当中,与发布相关的事务仍未传递到分发数据库。 (仅限完整恢复模式) |
7 |
DATABASE_SNAPSHOT_CREATION |
正在建立数据库快照。 (全部恢复模式) |
8 |
LOG_SCAN |
发生日志扫描。 (全部恢复模式) |
9 |
AVAILABILITY_REPLICA |
可用性组的辅助副本正将此数据库的事务日志记录应用到相应的辅助数据库。 (完整恢复模式) |
10 |
— |
仅供内部使用 |
11 |
— |
仅供内部使用 |
12 |
— |
仅供内部使用 |
13 |
OLDEST_PAGE |
若是将数据库配置为使用间接检查点,数据库中最先的页可能比检查点 LSN 早。 在这种状况下,最先的页能够延迟日志截断。 (全部恢复模式) |
14 |
OTHER_TRANSIENT |
当前未使用此值。 |
事务日志收缩
有时候咱们监控告警会发现事务日志出现暴增的状况,那么此时就必须对是事务日志进行收缩,无论数据库处于那种恢复模式,简单、完整模式。均可以按下面流程进行收缩。
1:查看对应数据库事务日志的逻辑名称(name),后续操做须要用到。
SELECT database_id ,
name ,
type_desc ,
physical_name
FROM sys.master_files
WHERE database_id = DB_ID('YourSQLDba')
AND type_desc='LOG'
2: 使用DBCC SQLPERF查看事务日志空间使用状况统计信息:
DBCC SQLPERF (LOGSPACE)
若是对应数据库的Log Space Used(%)的值较小,那么就能够收缩事务日志。
3:执行相似下面的收缩事务日志文件语句。
USE YourSQLDba;
GO
DBCC SHRINKFILE('YourSQLDba_Log', 128);
若是Log Space Used(%)很小,而收缩效果又不佳,那么通常是由于日志截断延迟形成,通常能够经过下面脚本检查缘由,大部分状况是由于等待LOG_BACKUP缘故。因此你对事务日志作一次备份后,再进行收缩便可解决。
SELECT name ,
log_reuse_wait ,
log_reuse_wait_desc
FROM sys.databases
WHERE database_id = DB_ID('YourSQLDba');
backup log [YourSQLDba]
to disk = 'M:\DB_BACKUP\LOG_BACKUP\YourSQLDba_[2018-01-11_06h37m26_Thu]_logs.TRN'
with noInit, checksum, name = 'YourSQLDba:15h40: M:\DB_BACKUP\LOG_BACKUP\YourSQLDba_[2018-01-11_06h37m26_Thu]_logs.TRN'
增长事务日志文件
SQL Server数据库中的事务日志能够有一个或多个事务日志文件,可是即便有多个事务日志文件,也不能并行写入多个事务日志文件,数据库引擎仍是会串行使用多个事务日志文件。也就是说大多数场景,多个事务日志文件其实并无什意义,那么它存在的意义是什么呢?例如,当你当前磁盘告警,事务日志没法继续增加,你须要在其余磁盘新增一个事务日志文件,让数据库继续顺畅运行。我的以为多个事务日志文件确实是一个很鸡肋的东西。Paul S. Randal在“了解SQL Server的日志记录和恢复”中明确指出:不要建立多个的日志文件,由于它不会致使性能增益。
下面是如何增长一个事务日志文件的样例:
USE [master]
GO
ALTER DATABASE [YourSQLDba] ADD LOG FILE ( NAME = N'YourSQLDba_Log2', FILENAME = N'D:\SQL_LOG\YourSQLDba_Log1.LDF' , SIZE = 65536KB , MAXSIZE = 55296KB , FILEGROWTH = 10%)
GO
删除事务日志文件
既然能够增长事务日志文件,那么固然也能够删除事务日志文件,可是这个删除操做是有限制的。主日志文件(primary log)是不能删除的。若是你删除primary log就会报“不能从数据库中删除主数据文件或主日志文件。”,下面咱们来测试一下。
准备测试环境以下:
USE master;
GO
CREATE DATABASE [TEST]
CONTAINMENT = NONE
ON PRIMARY
( NAME = N'TEST', FILENAME = N'D:\SQL_DATA\TEST.mdf' , SIZE = 100MB , MAXSIZE = 40GB, FILEGROWTH = 64MB )
LOG ON
( NAME = N'TEST_log' , FILENAME = N'D:\SQL_LOG\TEST_LOG_1.ldf' , SIZE = 20MB , MAXSIZE = 40MB , FILEGROWTH = 10MB),
( NAME = N'TEST_log2', FILENAME = N'D:\SQL_LOG\TEST_LOG_2.ldf' , SIZE = 20MB , MAXSIZE = 20GB , FILEGROWTH = 10MB)
GO
BACKUP DATABASE [TEST] TO DISK = N'D:\DB_BACKUP\Test.bak'
WITH NOFORMAT, NOINIT,
NAME = N'TEST-Full Database Backup',
SKIP, NOREWIND, NOUNLOAD, STATS = 10;
GO
USE TEST;
GO
SELECT * INTO mytest FROM sys.objects;
GO
INSERT INTO mytest
SELECT * FROM mytest
GO 12
DBCC SQLPERF(LOGSPACE)
DBCC LOGINFO('TEST')
注意,此时DBCC LOGINFO显示FileId=3的日志文件对应的虚拟日志(VLF)的Status为2,此时删除事务日志文件会提示文件没法删除,由于Status=2意味着VLF不能被覆盖和重用。
Status = 2 means that VLF can't be reused (overwritten) at this time and it doesn't necessarily mean that VLF is still active and writing transactions to that VLF. As Jonathan already mentioned, it means that the VLF is waiting for backup/REPL/Mirroring etc..
USE master;
GO
ALTER DATABASE TEST REMOVE FILE TEST_log2
备份事务日志后,你会发现FileId=3的日志文件对应的虚拟日志(VLF)的Status变为了0,那么此时就能够移除事务日志文件了。
BACKUP LOG TEST TO DISK = 'D:\SQL_LOG\Test.Trn'
GO
DBCC LOGINFO('TEST')
GO
USE master;
GO
ALTER DATABASE TEST REMOVE FILE TEST_log2
若是是生产环境或者在上述备份事务日志后,对应日志文件的VLF的状态仍然为2,那么能够用收缩日志文件和备份事务日志循环处理,直至对应日志文件下全部的VLF状态所有为0,就能够删除事务日志文件。
USE TEST;
GO
DBCC SHRINKFILE(TEST_log2);
BACKUP LOG TEST TO DISK = 'D:\SQL_LOG\Test.Trn'
注意,主日志文件(primary log)是不能删除的,以下测试所示:
USE master;
GO
ALTER DATABASE TEST REMOVE FILE TEST_log
Msg 5020, Level 16, State 1, Line 35
The primary data or log file cannot be removed from a database.
可是当你须要规划存储路径、移动事务日志文件时,你可使用折中的方法将主事务日志文件(primary log)移动到其它目录。以下所示:
1: 将当前数据库脱机;
ALTER DATABASE TEST SET OFFLINE;
2: 修改数据库的事务日志位置
ALTER DATABASE TEST
MODIFY FILE
(
NAME = N'TEST_log'
, FILENAME = N'E:\SQL_LOG\TEST_LOG_1.ldf'
)
3: 手工将事务日志文件移动到上面位置
4:将数据库联机操做。
ALTER DATABASE TEST SET ONLINE;
另外,如何判断那个日志文件是主事务日志文件?目前来讲,我只能这样判断, sys.master_files当中,file_id最小值对应的日志文件为主事务日志文件。用脚本判断以下:
SELECT f.database_id AS database_id ,
DB_NAME(f.database_id) AS database_name,
MIN(f.file_id) AS primary_log_id ,
f.type_desc AS type_desc
FROM sys.master_files f
WHERE f.database_id= DB_ID('databasename') AND type = 1
GROUP BY f.database_id,f.type_desc;
另外,你也能够用下面脚本查出哪些数据库拥有两个或以上事务日志。
SELECT f.database_id AS database_id ,
d.name AS database_name,
f.type_desc AS type_desc ,
COUNT(*) AS log_count
FROM sys.master_files f
INNER JOIN sys.databases d ON f.database_id = d.database_id
WHERE type = 1
GROUP BY f.database_id ,
f.type_desc,
d.name
HAVING COUNT(*) >= 2;
参考资料:
https://docs.microsoft.com/zh-cn/sql/relational-databases/sql-server-transaction-log-architecture-and-management-guide#physical_arch
https://docs.microsoft.com/zh-cn/sql/relational-databases/logs/the-transaction-log-sql-server#FactorsThatDelayTruncation
https://docs.microsoft.com/zh-cn/sql/relational-databases/logs/database-checkpoints-sql-server
https://technet.microsoft.com/zh-cn/library/2009.02.logging.aspx
http://www.cnblogs.com/CareySon/archive/2012/02/13/2349751.html
http://www.cnblogs.com/CareySon/p/3315041.html
http://www.cnblogs.com/CareySon/archive/2012/02/17/2355200.html