Sql Server 收缩日志文件原理及always on 下的实践

摘要:
这会使LDF文件在物理上更大,并占用更多磁盘空间。日志文件可以收缩的原因是,文件末尾的数据被清除,从而释放了这部分的空间,而不是脱离末尾以删除文件开头或中间的内容。综上所述,日志文件收缩的前提条件是,日志文件的最后一个VLF必须处于空闲状态,而处于空闲状态的VLF将从后面收缩到前面。基于此,我们可以估计日志文件可以释放的空间。此时,在执行文件收缩时,将看不到文件缩减的效果。

一、准备知识

  1、LSN

  LSN用来标识特定日志在日志文件中位置(详情请见什么是LSN:日志序列号),它由两部分组成:一部分用来标识VLF(虚拟日志文件)的序列号,剩下的用来标识该日志在VLF中的具体的位置。

  根据LSN不同,日志一般分为两类:首日志(最新的活动日志序号)和尾日志(保留时间最长的活动日志序号)。随着数据库的操作不断增加(如数据库中的update操作),首日志LSN序号不断变化。尾日志的序号只有在日志备份后才会变化。

     Sql Server 收缩日志文件原理及always on 下的实践第1张

                                                        (图一)日志文件结构图

2、VLF

  你可以通过DCC LOGINFO去分析数据库LDF中VLF(虚拟日志文件),LDF、VLF、日志的关系是:LDF包括多个VLF,每个VLF中包括多个日志记录。在VLF中,当事物日志增加时,日志的头部(首日志)不断向前移动,日志将占用越来越多的剩余空间,当这个VLF被占满后,新的日志写入到其他未被使用的VLF中,这个时候LDF并不会增大。当LDF中没有可用的VLF时,数据库会创建一个新的VLF。从而使得LDF文件物理增大,占用更多的磁盘空间。

     Sql Server 收缩日志文件原理及always on 下的实践第2张

                                                           (图二)日志增长

二、解决方法详细阐述

  1、日志的截断

  上图演示了首日志向前移动的场景,结合图一和图二可以看到,当VLF2的空间被日志填满后,数据库扩充LDF文件(申请更多的磁盘空间),并在扩充后的LDF中新建一个VLF3用来填充新的日志记录。尽管VLF1中存在剩余空间,但因为VLF1中存在活动日志(哪怕只有一条),所以数据库无法利用这个VLF的剩余空间。

  这个时候做日志备份就会发生日志截断的现象。一般会将截断理解为"删除"一些日志记录(非活动),实际上它只是意味着尾日志的向前移动:尾日志序号会被刷新成最小的活动日志序号,而从原来尾日志的位置到新位置之间的空间被标记为"可重新利用"。这个过程并不会减少LDF已占用的磁盘空间。如下图,整个VLF1的和部分VLF2上的日志(非活动)被截断了。

     Sql Server 收缩日志文件原理及always on 下的实践第3张

                                                      (图三)日志截断示意图

      随着事务日志不断增加,VLF3中日志头部所在的位置将不断向前移动,当VLF3的空间被占满后,数据库会重新利用VLF1的空间,这种写入、截断、再写入的方式形成一个写日志的循环。在此期间LDF并不会物理上增大。

     Sql Server 收缩日志文件原理及always on 下的实践第4张

                                                     (图四)日志循环使用示意图

2、为什么日志不能收缩

  现在我们再来看一个日志无法收缩的场景:

  图四中,VLF1中的日志不断增加,直到VLF1的所有空间都被填满(如图五),此时因为没有发生截断,尾日志都在VLF2上,且VLF2和VLF3都被标记为不可重新利用,数据库只能扩充LDF、新建一个VLF4用来记录新的日志,首日志的位置将出现在VLF4中,整个写日志的(从图一到图四)顺序为VLF2——>VLF3——>VLF1——>VLF4。这个过程会导致数据库的日志文件在物理上增大。

     Sql Server 收缩日志文件原理及always on 下的实践第5张

                                                           (图五)日志增长示意图

      这时我们再来截断事物日志,如上文所说,尾日志的会被更新,最后可能出现尾日志和首日志在同一个VLF上的场景。从日志文件记录的架构上来看,我们可以将这个过程简单地理解为:截断的顺序会按照首日志移动的顺序移动,从VLF2——>VLF3——>VLF1——>VLF4,最终尾日志和首日志出现在同一个VLF上。

      Sql Server 收缩日志文件原理及always on 下的实践第6张

                                                          (图六)日志截断示意图二

      如上图,这个LDF文件包括3个空的和1个只有小部分活动日志的VLF文件,首日志和尾日志在同一个VLF中,这种情况下,试图通过DBCC SHRINKFILE是不会减小LDF文件的大小的。

  日志文件能被收缩的原因是该文件尾部的数据被清除了,使得该部分空间被释放,而不是逃过尾部去删除文件首部或者中间部分的内容。这点与MDF文件不同,MDF文件中的数据是不能被删除的,只能将文件尾部的数据迁移到其他区域的剩余空间上,然后释放尾部占用的空间。

  在LDF中 ,日志是不能被迁移的,而且也没有迁移的必要,因为当事物被提交后,日志变为不活动状态,通过事物日志备份即可将其截断(特殊情况下日志备份不一定能截断,如发布订阅的环境)。

  综上所述,日志文件能被收缩的前提是:日志文件的最后一个VLF必须是free状态,从后向前推,只要是free状态的VLF都会被收缩,据此可以估算一个日志文件可以释放的空间大小。

  如下我们看一个实际的例子:

  USE DBname

  DBCC loginfo

      Sql Server 收缩日志文件原理及always on 下的实践第7张

                                             (图七)VLF状态示意图

      从上图可以看到,这个数据库的日志文件共有13个VLF,其中有前12个处于free状态,最后1个处于活动状态,因此,我们可以推断首日志和尾日志的位置都在这个VLF上。这个时候执行文件收缩将看不到文件减小的效果。

3、如何解决这个问题

  那么碰到这种情况,该怎么去收缩日志呢:尽可能多的执行一些能够产生大量日志的操作,这些日志将导致数据库重新利用startoffset靠前的非活动状态的VLF,将首日志的位置定位到这个startoffset,然后做一次事务日志备份,将尾日志也迁移到startoffset靠前的非活动状态的VLF中,如下图,最后再执行DBCC SHRINKFILE即可收缩日志文件。

     Sql Server 收缩日志文件原理及always on 下的实践第8张

                                                 (图八)日志截断示意图三

三、重要说明

  前文中一直在说通过日志备份即可解决日志截断的问题,其实这只是最简单的场景。在实际环境中可能有很多因素会影响日志的截断,如:

  活动的事物日志

  日志备份只能截断非活动的日志,如果一个事物长时间运行,此时备份事物日志将不会引起截断发生。

  事物日志分发

  事物日志分发中,只有当日志读取器代理已经读取完待分发的日志后,日志才能变得非活动状态。

  数据库镜像和AlwaysOn

  这两种数据库技术都需要将日志传递到接受端,在传递还没有完成时,日志会一直保留,即使是备份日志也无法截断。

四、Always on 环境下实践

  先对数据库进行完整备份:

      

EXEC sp_configure 'show advanced options', 1;

RECONFIGURE;

EXEC sp_configure 'xp_cmdshell', 1;

RECONFIGURE;

DECLARE @DbName NVARCHAR(1000);

DECLARE myCursor CURSOR LOCAL STATIC
FOR
SELECT [name]
FROM sysdatabases
WHERE [name] NOT IN ( 'master', 'model', 'msdb', 'tempdb' )
AND name NOT LIKE '%test%'
AND name NOT LIKE '%bak%'
AND name NOT LIKE '%demo%'
AND version IS NOT NULL
AND version <> 0
ORDER BY [name];
OPEN myCursor;
FETCH NEXT FROM myCursor INTO @DbName;
WHILE ( @@FETCH_STATUS = 0 )
BEGIN
DECLARE @strDate AS NVARCHAR(20),
@strDateBeforeSeven AS NVARCHAR(20),
@strFileName AS NVARCHAR(255),
@strFileNameBeforeSeven AS NVARCHAR(255),
@strCommand AS NVARCHAR(255)

SET @strDate = CONVERT(NVARCHAR(20),GETDATE(),112);
SET @strDateBeforeSeven = CONVERT(NVARCHAR(20),GETDATE()-3,112); 
SET @strFileName = 'E:daybak['+@DbName+']_bakup_'+@strDate; 
SET @strFileNameBeforeSeven = 'E:daybak['+@DbName+']_bakup_'+@strDateBeforeSeven; 

EXEC ('BACKUP DATABASE ['+@DbName+'] TO DISK = ''' + @strFileName + '.bak''')

SET @strCommand = 'DEL ' + @strFileNameBeforeSeven + '.bak'
EXEC master.dbo.xp_cmdshell @strCommand

FETCH NEXT FROM myCursor INTO @DbName;
END;
CLOSE myCursor;
DEALLOCATE myCursor;

  然后对数据库进行事务日志备份并收缩:

EXEC sp_configure 'show advanced options', 1;

RECONFIGURE;

EXEC sp_configure 'xp_cmdshell', 1;

RECONFIGURE;

DECLARE @DbName NVARCHAR(1000);

DECLARE myCursor CURSOR LOCAL STATIC
FOR
SELECT [name]
FROM sysdatabases
WHERE [name] NOT IN ( 'master', 'model', 'msdb', 'tempdb' )
AND name NOT LIKE '%test%'
AND name NOT LIKE '%bak%'
AND name NOT LIKE '%demo%'
AND version IS NOT NULL
AND version <> 0
ORDER BY [name];
OPEN myCursor;
FETCH NEXT FROM myCursor INTO @DbName;
WHILE ( @@FETCH_STATUS = 0 )
BEGIN
DECLARE @strDate AS NVARCHAR(20),
@strDateBeforeSeven AS NVARCHAR(20),
@strFileName AS NVARCHAR(255),
@strFileNameBeforeSeven AS NVARCHAR(255),
@strCommand AS NVARCHAR(255)

SET @strDate = CONVERT(NVARCHAR(20),GETDATE(),112);
SET @strDateBeforeSeven = CONVERT(NVARCHAR(20),GETDATE()-3,112); 
SET @strFileName = 'E:Log_daybak['+@DbName+']_bakup_'+@strDate; 
SET @strFileNameBeforeSeven = 'E:Log_daybak['+@DbName+']_bakup_'+@strDateBeforeSeven; 

EXEC ('BACKUP LOG ['+@DbName+'] TO DISK = ''' + @strFileName + '.log'';USE ['+@DbName+'];DBCC SHRINKFILE(2,100);')

SET @strCommand = 'DEL ' + @strFileNameBeforeSeven + '.log'
EXEC master.dbo.xp_cmdshell @strCommand

FETCH NEXT FROM myCursor INTO @DbName;
END;
CLOSE myCursor;
DEALLOCATE myCursor;

免责声明:文章转载自《Sql Server 收缩日志文件原理及always on 下的实践》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Vue:vue-router编程式导航CentOS搭建Git服务器及权限管理下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

MySQL笔记-高可用方案

MySQL笔记-高可用方案 目录 MySQL笔记-高可用方案 一、概述环境信息 二、主从复制(一主一从) 2.1、配置参数(my.cnf) 2.2、Master数据库,创建复制用户并授权2.3、Slave数据库,指定Master及启用slave 2.3、验证主从复制 主库创建测试库、表和数据 从库查看同步情况 三、双主配置 3....

go语言游戏服务端开发(一)——架构

五邑隐侠,本名关健昌,12年游戏生涯。 本教程以Go语言为例。 网络游戏程序分为客户端和服务端。客户端负责图形渲染、交互和一些简单校验处理,服务端负责业务逻辑处理、数据存储。 我们开发一个游戏demo,服务端程序可以是一个单线程的服务进程。它包含网络通信、业务逻辑处理、数据存储。服务端打开网络端口监听,客户端通过网络连接到服务端,服务端接入连接。客户端发包...

cloudera目录位置

http://www.aboutyun.com/thread-9189-1-1.html 这里来的嘿嘿。 1. 相关目录/var/log/cloudera-scm-installer : 安装日志目录。/var/log/* : 相关日志文件(相关服务的及CM的)。/usr/share/cmf/ : 程序安装目录。/usr/lib64/cmf/ : Age...

jdk1.8.0_40 +maven+tomcat7.0+mysql8.0详细安装教程

(一)jdk的安装 1.下载jdk推荐下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 2.配置JAVA_HOME, PATH找到:右键此电脑>属性>高级系统设置>高级>环境变量>系统变量> J...

SQL Server的链接服务器(MySQL、Oracle、Ms_sql、Access、SYBASE)

一、使用 Microsoft OLE DB Provider For ODBC 链接MySQL安装MySQL的ODBC驱动MyODBC1、为MySQL建立一个ODBC系统数据源,例如:选择数据库为test ,数据源名称为myDSN 2、建立链接数据库EXEC sp_addlinkedserver @server = 'MySQLTest', @srvpro...

MySQL分页时使用 limit+order by 会出现数据重复问题

1、问题描述 在MySQL中我们通常会采用limit来进行翻页查询,比如limit(0,10)表示列出第一页的10条数据,limit(10,10)表示列出第二页。但是,当limit遇到order by的时候,可能会出现翻到第二页的时候,竟然又出现了第一页的记录。 SELECT `post_title`, `post_date` FROM pos...