收缩SQL Server日志不是那么简单的(翻译)
原文地址:http://rusanu.com/2012/07/27/how-to-shrink-the-sql-server-log/
说明:本文为了更好的说明收缩的过程,在原文翻译的基础上增加了一些个人的理解,省略了部分内容,建议大家在阅读本文时参考原文。
一、问题场景 |
我的数据库日志文件已经增大到200G了,我也尝试去收缩数据库,但大小没有改变,请问该如何减小日志文件的大小?这个问题实际上就是说执行DBCC SHRINKFILE没有减小日志文件的大小,到底是什么原因导致的呢?
二、准备知识 |
1、LSN |
LSN用来标识特定日志在日志文件中位置(详情请见什么是LSN:日志序列号),它由两部分组成:一部分用来标识VLF(虚拟日志文件)的序列号,剩下的用来标识该日志在VLF中的具体的位置。
根据LSN不同,日志一般分为两类:首日志(最新的活动日志序号)和尾日志(保留时间最长的活动日志序号)。随着数据库的操作不断增加(如数据库中的update操作),首日志LSN序号不断变化。尾日志的序号只有在日志备份后才会变化。
(图一)日志文件结构图
2、VLF |
你可以通过DCC LOGINFO去分析数据库LDF中VLF(虚拟日志文件),LDF、VLF、日志的关系是:LDF包括多个VLF,每个VLF中包括多个日志记录。在VLF中,当事物日志增加时,日志的头部(首日志)不断向前移动,日志将占用越来越多的剩余空间,当这个VLF被占满后,新的日志写入到其他未被使用的VLF中,这个时候LDF并不会增大。当LDF中没有可用的VLF时,数据库会创建一个新的VLF。从而使得LDF文件物理增大,占用更多的磁盘空间。
(图二)日志增长
三、解决方法详细阐述 |
|
1、日志的截断 |
上图演示了首日志向前移动的场景,结合图一和图二可以看到,当VLF2的空间被日志填满后,数据库扩充LDF文件(向操作系统申请更多的磁盘空间),并在扩充后的LDF中新建一个VLF3用来填充新的日志记录。尽管VLF1中存在剩余空间,但因为VLF1中存在活动日志(哪怕只有一条),所以数据库无法利用这个VLF的剩余空间,(详细原因可以参考这篇文章什么是LSN:日志序列号)。
这个时候做日志备份就会发生日志截断的现象。一般会将截断理解为"删除"一些日志记录(非活动),实际上它只是意味着尾日志的向前移动:尾日志序号会被刷新成最小的活动日志序号,而从原来尾日志的位置到新位置之间的空间被标记为"可重新利用"。这个过程并不会减少LDF已占用的磁盘空间。如下图,整个VLF1的和部分VLF2上的日志(非活动)被截断了。
日志截断示意图(图三)
随着事务日志不断增加,VLF3中日志头部所在的位置将不断向前移动,当VLF3的空间被占满后,数据库会重新利用VLF1的空间,这种写入、截断、再写入的方式形成一个写日志的循环。在此期间LDF并不会物理上增大。
日志循环使用示意图(图四)
2、为什么日志不能收缩 |
现在我们再来看一个日志无法收缩的场景:
图四中,VLF1中的日志不断增加,直到VLF1的所有空间都被填满(如图五),此时因为没有发生截断,尾日志都在VLF2上,且VLF2和VLF3都被标记为不可重新利用,数据库只能扩充LDF、新建一个VLF4用来记录新的日志,首日志的位置将出现在VLF4中,整个写日志的(从图一到图四)顺序为VLF2——>VLF3——>VLF1——>VLF4。这个过程会导致数据库的日志文件在物理上增大。
日志增长示意图(图五)
这时我们再来截断事物日志,如上文所说,尾日志的会被更新,最后可能出现尾日志和首日志在同一个VLF上的场景。从日志文件记录的架构上来看,我们可以将这个过程简单地理解为:截断的顺序会按照首日志移动的顺序移动,从VLF2——>VLF3——>VLF1——>VLF4,最终尾日志和首日志出现在同一个VLF上。
日志截断示意图二(图六)
如上图,这个LDF文件包括3个空的和1个只有小部分活动日志的VLF文件,首日志和尾日志在同一个VLF中,这种情况下,试图通过DBCC SHRINKFILE是不会减小LDF文件的大小的。
日志文件能被收缩的原因是该文件尾部的数据被清除了,使得该部分空间被释放,而不是逃过尾部去删除文件首部或者中间部分的内容。这点与MDF文件不同,MDF文件中的数据是不能被删除的,只能将文件尾部的数据迁移到其他区域的剩余空间上,然后释放尾部占用的空间。
在LDF中 ,日志是不能被迁移的,而且也没有迁移的必要,因为当事物被提交后,日志变为不活动状态,通过事物日志备份即可将其截断(特殊情况下日志备份不一定能截断,如发布订阅的环境)。
综上所述,日志文件能被收缩的前提是:日志文件的最后一个VLF必须是free状态,从后向前推,只要是free状态的VLF都会被收缩,据此可以估算一个日志文件可以释放的空间大小。
如下我们看一个实际的例子:
USE DBname
DBCC loginfo
VLF状态示意图(图七)
从上图可以看到,这个数据库的日志文件共有13个VLF,其中有前12个处于free状态,最后1个处于活动状态,因此,我们可以推断首日志和尾日志的位置都在这个VLF上。这个时候执行文件收缩将看不到文件减小的效果。
3、如何解决这个问题 |
那么碰到这种情况,该怎么去收缩日志呢:尽可能多的执行一些能够产生大量日志的操作,这些日志将导致数据库重新利用startoffset靠前的非活动状态的VLF,将首日志的位置定位到这个startoffset,然后做一次事务日志备份,将尾日志也迁移到startoffset靠前的非活动状态的VLF中,如下图,最后再执行DBCC SHRINKFILE即可收缩日志文件。
日志截断示意图三(图六)
四、重要说明 |
前文中一直在说通过日志备份即可解决日志截断的问题,其实这只是最简单的场景。在实际环境中可能有很多因素会影响日志的截断,如:
- 活动的事物日志
日志备份只能截断非活动的日志,如果一个事物长时间运行,此时备份事物日志将不会引起截断发生。
- 事物日志分发
事物日志分发中,只有当日志读取器代理已经读取完待分发的日志后,日志才能变得非活动状态。(之前我处理过一个类似问题,大家可以通过这个链接看看http://www.cnblogs.com/i6first/p/3281437.html。)
- 数据库镜像和AlwaysOn
这两种数据库技术都需要将日志传递到接受端,在传递还没有完成时,日志会一直保留,即使是备份日志也无法截断。
.NET 使用CouchBase 基础篇
2011年2月,CouchOne和memebase合并后,改名为Couchbase,官网地址(www.couchbase.com)。membase最后一个版本为1.7.2,可在Couchbase的官网下载(http://www.couchbase.com/downloads-all)。
这里不介绍couchbase的安装,只介绍.NET Client Librarye 使用。
-
获得CouchBase For Net的SDK有两种方式
- 通过nuget,Install-Package CouchbaseNetClient
- 通过官网下载http://www.couchbase.com/communities/net
-
开始CouchBase之旅
- 创建项目,这里需要提醒的是,在vs2010中我们创建类Console应用程序时项目默认使用.NET Framework Client Profile,我们需要手动切换至full .NET Framework。
- 在程序中配置CouchBase,CouchBase提供编码配置和配置文件配置,当然使用app.config是最灵活的,这里是我们的首选,添加以下信息至你的配置文件,
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="couchbase" type="Couchbase.Configuration.CouchbaseClientSection, Couchbase"/>
</configSections>
<couchbase>
<servers bucket="default" bucketPassword="">
<add uri="http://192.168.0.2:8091/pools"/>
<add uri="http://192.168.0.3:8091/pools"/>
</servers>
</couchbase>
</configuration>这里使用了集群配置的url列表,当然在你本地调试只需要一个地址,默认CouchBase在安装时会创建一个没有密码的default的缓存桶(bucket),你可以*修改这块的信息。(如果对bucket不太明白,请自行google)。
- 添加引用
using Couchbase; using Couchbase.Configuration; using Couchbase.Extensions; using Enyim.Caching; using Enyim.Caching.Configuration; using Enyim.Caching.Memcached; |
根据实际引用添加引用
- 创建实例及使用
var client = new CouchbaseClient(); // 创建实例 client.Store(StoreMode.Add, "somekey", "somevalue"); //存储数据 var someValue = client.Get("somekey") as string; //获取数据 var someValue = client.Get<string>("somekey"); //获取数据 |
以上是简单基本类型的使用,下面我们介绍一下复杂类型。先申明一个类
[Serializable] |
var key = Guid.NewGuid(); var beer = client.Get<Beer>("beer_" + key); |
在CouchBase2.0正式版就开始支持json,这一点让人激动人心。
存储json数据
public static bool StoreJson<T>(this CouchbaseClient client, StoreMode storeMode, string key, T value) where T : class { |
获取json数据
public static T GetJson<T>(this CouchbaseClient client, string key) where T : class { |
Client使用方法
var key = Guid.NewGuid(); var beer = client.GetJson<Beer>("beer_" + key); |
-
检查和操作结果
官方的说明
For check and set operations, the return values are wrapped in a CasResult instance. The success of the operation is still determined by a Boolean and detailed failures still require logging.
var result = client.GetWithCas("foo"); |
-
获取详细操作结果
如果你需要获取运行时的详细信息,你可以使用IoperationResult API方法,下面是官方给的API属性的说明。
Each of these methods shares its name with a method from the single-value return API, but prefixed with "Execute." For example, Get() becomes ExecuteGet() and Store() becomes ExecuteStore().
var getResult = client.ExecuteGet<Beer>("beer_heady_topper"); if (casResult.Success) { |
-
配置日志
CouchBase支持Log4Net和Nlog,你可以自己现在或者从CouchBase提供的SDK获取。
Log4Net 配置参考。
<?xml version="1.0" encoding="utf-8"?> |
更多Log4Net配置可参考:http://logging.apache.org/log4net/release/manual/configuration.html.
Nlog配置参考
<?xml version="1.0" encoding="utf-8"?> |
更多Nlog配置可参考:http://nlog-project.org/wiki/Configuration_file
总结 :以上信息来源官方的Getting Started,另附一份自己整理的Demo。(通过office word 发布的文档格式有些变形)