原文位址: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序号不斷變化。尾日志的序号隻有在日志備份後才會變化。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuUzN0YzNzQTOwQDM1EzNy8CXxADNxAjMvwlN2MDO1QzLcd2bsJ2Lc12bj5ycn9Gbi52YuAzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
(圖一)日志檔案結構圖
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是最靈活的,這裡是我們的首選,添加以下資訊至你的配置檔案,
這裡使用了叢集配置的url清單,當然在你本地調試隻需要一個位址,預設CouchBase在安裝時會建立一個沒有密碼的default的緩存桶(bucket),你可以自由修改這塊的資訊。(如果對bucket不太明白,請自行google)。<?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>
- 添加引用
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] public class Beer { public Guid Id { get; set; } public string Name { get; set; } public string Brewery { get; set; } } |
var key = Guid.NewGuid(); var beer = new Beer { Id = key, Name = "Old Yankee Ale", Brewery = "Cottrell Brewing Company" }; client.Store(StoreMode.Add, "beer_" + key, beer); 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 { var ms = new MemoryStream(); var serializer = new DataContractJsonSerializer(typeof(T)); serializer.WriteObject(ms, value); var json = Encoding.Default.GetString(ms.ToArray()); ms.Dispose(); return client.Store(storeMode, key, json); |
擷取json資料
public static T GetJson<T>(this CouchbaseClient client, string key) where T : class { var json = client.Get<string>(key); var ms = new MemoryStream(Encoding.Default.GetBytes(json)); var serializer = new DataContractJsonSerializer(typeof(T)); var obj = serializer.ReadObject(ms) as T; return obj; |
Client使用方法
var key = Guid.NewGuid(); var beer = new Beer { Id = key, Name = "American Ale", Brewery = "Thomas Hooker Brewing Company", Type = "beer" client.StoreJson<Beer>(StoreMode.Add, "beer_" + key, beer); 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"); var bar = "bar"; var result = client.Cas(StoreMode.Set, "foo", bar, result.Cas); if (result.Result) { Console.WriteLine("CAS operation was successful"); |
-
擷取詳細操作結果
如果你需要擷取運作時的詳細資訊,你可以使用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 (getResult.Success && getResult.HasValue) { var beer = getResult.Value; beer.Brewery = "The Alchemist"; var casResult = client.ExecuteCas(StoreMode.Set, "beer_heady_topper", beer, getResult.Cas); if (casResult.Success) { Console.WriteLine("CAS operation was successful"); } } else { Console.WriteLine("Get operation failed with message {0} and exception {1} ", getResult.Message, getResult.Exception); |
-
配置日志
CouchBase支援Log4Net和Nlog,你可以自己現在或者從CouchBase提供的SDK擷取。
Log4Net 配置參考。
<?xml version="1.0" encoding="utf-8"?> <configSections> <sectionGroup name="enyim.com"> <section name="log" type="Enyim.Caching.Configuration.LoggerSection, Enyim.Caching" /> </sectionGroup> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" /> </configSections> <enyim.com> <log factory="Enyim.Caching.Log4NetFactory, Enyim.Caching.Log4NetAdapter" /> </enyim.com> <log4net debug="false"> <appender name="LogFileAppender" type="log4net.Appender.FileAppender,log4net"> <param name="File" value="c:\\temp\\error-log.txt" /> <param name="AppendToFile" value="true" /> <layout type="log4net.Layout.PatternLayout,log4net"> <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n" /> </layout> </appender> <root> <priority value="ALL" /> <level value="DEBUG" /> <appender-ref ref="LogFileAppender" /> </root> </log4net> |
更多Log4Net配置可參考:http://logging.apache.org/log4net/release/manual/configuration.html.
Nlog配置參考
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" /> <log factory="Enyim.Caching.NLogFactory, Enyim.Caching.NLogAdapter" /> <nlog> <targets> <target name="logfile" type="File" fileName="c:\temp\error-log.txt" /> </targets> <rules> <logger name="*" minlevel="Info" writeTo="logfile" /> </rules> </nlog> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" /> </startup> |
更多Nlog配置可參考:http://nlog-project.org/wiki/Configuration_file
總結 :以上資訊來源官方的Getting Started,另附一份自己整理的Demo。(通過office word 釋出的文檔格式有些變形)
Demo源碼
着意耕耘,自有收獲
|