2018年10月31日 星期三

SQLSERVER 進階資料庫修復-結尾紀錄備份


資料庫損壞時,最佳的建議是透過備份檔還原,將資料庫回復到正常的狀態,當然,前提是備份檔是正常的。
不過,使用資料庫備份還原,最大的問題是可能會有一小段時間的資料遺失(RPO > 0)

舉例來說,通常的備份策略是一天或一週做一次全備份,而在兩個全備份的期間,則定時做差異或交易紀錄備份。
因此,當資料庫損壞時,可能會有一小段時間是沒有備份的,這一小段時間的交易紀錄,稱為「結尾紀錄 tail-log」。
   

  
早期初學時,遇到像圖中這種狀況,因對DB不夠了解只能自認倒楣,然後用已經備份的檔案還原,並且通知使用者資料庫壞了,而且可能會有一小段時間的資料損失,接著就是一連串的檢討及補key資料。

後來為了避免這種狀況,就開始學習建HA,主要目的就是為了DB災害發生時不再有資料損失,並且降低DB停止服務的時間,也就是大家常講的RTORPO

隨著對DB逐漸的熟悉,就想到如果在DB損壞的情況下還能把最後這段結尾紀錄備份出來,那麼就算沒有HA,光靠備份與還原也能做到「資料零損失」,只是回復的時間會比較長。

經過斷斷續續的測試及google大神的幫助,我發現,只要是交易紀錄檔(ldf)沒壞掉,即使是DB壞的無法online了,都有很大的可能將結尾紀錄備份出來,再配合之前的定期備份檔進行還原,達到「資料零損失」的目地。

因此,當資料庫損壞時,如果你的DB沒有HA,你應在第一時間,嘗試備份「結尾紀錄 tail-log」,以最大程度的減少資料的損失。


資料庫損壞,但仍在線上可用
第一種狀況是資料庫損壞,但DB仍然ONLINE中可以使用。
這時候,應先將DB設為單一使用者模式,並首先嘗試以一般指令備份交易紀錄,如果成功,那麼就可以使用之前的備份加上結尾紀錄備份,將資料庫還原至最近的時間點。
如果正常備份交易紀錄失敗,則可試著在指令後加上 with no_truncate,例如:

backup log MyDB to disk='c:\test\MyDB.trn' with noinit
go
/*
備份失敗
訊息 945,層級 14,狀態 2,行 1
檔案無法存取、記憶體或磁碟空間不足,因此無法開啟資料庫 'MyDB'。詳細資訊請參閱 SQL Server 錯誤記錄檔。
訊息 3013,層級 16,狀態 1,行 1
BACKUP LOG 正在異常結束。
*/

資料庫損壞,無法執行結尾紀錄備份,我們在backup log指令後,使用no_truncate
backup log MyDB to disk='c:\test\MyDB.trn' with noinit,no_truncate 
go --OK,備份成功

接著就可以使用restore指令,將資料庫還原至最後的時間點。

以下節錄微軟官網對於no_truncate的解釋
NO_TRUNCATE
指定不截斷記錄,並使 Database Engine 嘗試進行備份,而不論資料庫狀態為何。 因此,利用 NO_TRUNCATE 建立的備份可能會有不完整的中繼資料。 在資料庫已損毀的情況下,您可以利用這個選項來進行記錄的備份。
BACKUP LOG NO_TRUNCATE 選項相當於同時指定 COPY_ONLY CONTINUE_AFTER_ERROR
未使用 NO_TRUNCATE 選項時,資料庫必須處於 ONLINE 狀態。 如果資料庫處於 SUSPENDED 狀態,您就能透過指定 NO_TRUNCATE 來建立備份。 但是,如果資料庫處於 OFFLINE EMERGENCY 狀態,即使設定了 NO_TRUNCATE,也不允許 BACKUP



資料庫損壞,且己不可用
第二種狀況是資料庫損壞,而且已經不可用。
例如DB損壞SQL SERVER自動將DB離線,或者DB被置於SuspectOFF-LINE,或者用no_truncate也無法備份等。這些情況下DB基本上已無法存取,也無法備份交易紀錄。

在這種情況下,有可能是mdf損壞,但ldf是好的。我們可以試著用「hack-attach」的方法,將結尾紀錄備份出來。

Hack-Attach
在另一台相同版本的Server
一、建立一個同名的資料庫(dummy DB),包含mdfldf的名稱都相同。
二、將dummy DB離線。ALTER DATABASE dummyDB SET OFFLINE;
三、刪除dummy DBmdfldf等所有資料檔。
四、將損壞的DBldf檔,複製到dummy DBldf檔所在目錄,取代新建的ldf
五、ALTER DATABASE dummyDB SET ONLINE; --會發生錯誤,忽略它
六、BACKUP LOG dummyDB TO DISK = '......'  WITH NO_TRUNCATE

經過上述步驟後,應可將結尾紀錄備份出來,就可以用之前的備份檔加上結尾紀錄備份,將資料庫還原至最近的時間點。

其實這個方法就是做一個假的資料庫,在OFF-LINE後將要做結尾紀錄備份LDF覆蓋掉新建的LDF,接著將資料庫ONLINE,最後將交易紀錄備份出來。這有點像是欺騙SQL SERVER,故稱為hack-attach.

如果你的DB沒有HA,而僅是靠著備份檔在做備援,那麼你更應該要熟悉或知道這些方法,當災害發生時儘量減少資料的損失。





2018年10月22日 星期一

SQLSERVER 交易紀錄碎片的解決方法與交易紀錄的收縮(shrink)



上篇討論了關於何謂交易紀碎片以及它對於的效能影響。

當你發現你的DB有交易紀錄碎片的問題時,首先要做的就是「降低碎片」,依照上篇所講的觀念,所謂的「降低碎片」,說白了就是「減少VLF的數量」。

VLF一旦被建立後,無法改變它的大小。所以「減少VLF的數量」意味著我們要刪除一些VLF

而刪除VLF,主要是透過dbcc shrinkfile來達成,當然,如果你的DB容許暫停服務的話,你也可以直接把log砍掉重練,這裡我們不多做討論,本篇是使用dbcc shrinkfile

要想更深入的理解dbcc shrinkfile,要先對VLF有些基本概念,如果你只想知道如何做,可以直接跳至「VLF碎片的解決」

VLF基本觀念
我們可以使用dbcc loginfo來查看VLF的資訊,以下圖為例,該資料庫的交易紀錄檔LDF,共被劃分為10VLF來管理。


dbcc loginfo顯示的訊息中係依StartOffset排序,StartOffset代表了該VLFLDF檔中的偏移量,也就是VLF的實體存放位址,也代表著VLF的順序。

FseqNo則代表了每個VLF的編號(0表示從未使用過)。此編號並不是固定的,它可以視為是一個會遞增的編號。

VLF是循環使用的,資料庫的交易紀錄從第一個FseqNo開始寫,當這個VLF寫滿後,即跳到下一個VLF寫入,並給予下一個VLF一個新的FseqNo編號(+1)。直到所有的VLF用完後,若之前的VLF已經可以重用,就會再循環使用。如果沒有VLF可用了,就會觸發自動成長。如果自動成長關閉或者因磁碟空間不足無法自動成長時,即會跳出交易紀錄檔已滿的錯誤訊息。

  
我們測試建立一個table如下
create table testVLF(c1 int identity,c2 char(1024))
接著新增約2000筆資料後,再執行dbcc loginfo
由上圖可以看到,目前交易紀錄的總量,佔用了3VLF

SQLSERVER對每筆交易紀錄,都會賦予一個唯一的編號(Log Sequence Number 簡稱LSN)。LSN 是經過排序的,因此如果 LSN2 大於 LSN1,表示 LSN2 所參考記錄中描述的變更,發生在記錄 LSN1 所描述的變更之後。
一、      在每個DB的交易紀錄中,LSN是唯一的。
二、      LSN是有順序性的,它是隨著交易的先後順序遞增,值越小的表示順序越前。
三、      LSN可以進行比較或排序,以方便建構還原順序。

我們用fn_dblog()去查transaction Log時所到看的LSN,如下圖
圖中可以看到真實的LSN是一個「3段數值組成的字串」,3個數值中間以「:」隔開。LSN三段式的意義如下:
n  VLF序號(file sequential number
n  VLF中的偏移量(offset)(以512bytes為單位度量)
n  Slot 編號

以上圖的最後一個LSN為例
000009b6:000016d9:0004 
在此篇中,我們有興趣的是第一部份「000009b6」,這是16進位的表示法,換算成10進位,即為「2486」。
各位可以再對照之前dbcc loginfo的結果圖,目前寫到的VLF,其FSeqNo的值即為2486。這表示了這筆交易紀錄是存在放FSeqNo=2486這個VLF之中。
哈哈,這裡有點講過頭了,LSN的概念在一般的DBA工作上,可能永遠也用不到,但你如果對交易紀錄的結構有興趣,這則是必備的常識

每個VLF視其中存放的交易紀錄的情況,在dbcc loginfo的結果中,基本上會有02兩種狀態(status
status 0Reusable or Unused à代表可使用的空間
status 2Active or Recoverable à代表不可使用的空間

它的意義在官網的說如下
Active (簡單的說就是:交易使用中,不能shrink)
The active portion of the log begins at the minimum LSN representing an active transaction. The active portion of the log ends at the last LSN written. Any VLFs that contain any part of the active log are considered active VLFs.
Recoverable 交易已結束,但因為有其它的:如mirror,replication,還沒完成,所以不能shrink

The portion of the log preceding the oldest active transaction is needed only to maintain a sequence of log backups for restoring the database to a former state.
Reusable 原本有在用,但資料已經備走了或者不再需要,可以shrink
If transaction log backups aren’t being maintained or you’ve already backed up the log, VLFs before the oldest active transaction aren’t needed and can be reused. Truncating or backing up the transaction log changes recoverable VLFs into reusable VLFs.
Unused閒置中,從沒用過的,可以shrink
One or more VLFs at the physical end of the log files might not have been used yet if not enough logged activity has taken place.


VLF 碎片的解決
要解決VLF的碎片,簡單的說,就是刪除過多的VLF,並且設定適當的自動成長。
底下列出要執行dbcc shrinkfile來解決VLF碎片的步驟:
1. 如果DB復原模式是fullbulk logged,請先執行backup log,目的是將交易紀錄檔的資料清空。
2. 如果DB復原模式是simple,執行checkpoint,目的同樣是為了將交易紀錄檔的資料清空,但也可以不用做,因為系統自己會做。
3. 使用dbcc loginfo觀察最大的active VLFstatus<>0max FSeqNo),最好位於log file的前面開始處。
4. 執行dbcc shrinkfile(‘邏輯檔名’,收縮目標大小),收縮log file到一個適當的大小,至於何謂適當的大小,這完全看你的系統狀況而定了,當然最佳的選擇是收縮到MinimumSize後,再重新建立新的VLF
5. 修改並設定適當的log file大小(alter database),請注意,設定時請參考上篇文章自行計算,一個VLF不要大於500MB
6. 若有設定自動成長,應視實際環境,給予固定的大小,不要給太小,以免日後又造成VLF Fragemention

VLF的數量及大小多少才適合,有幾個重要的參考指標:
一、每個VLF size不超過500MB
二、VLF的數量上限不要超過1000個。
三、VLF數量的甜蜜點(sweet spot)為100個。


dbcc shrinkfile的概念
使用dbcc shrinkfile 的指令去縮小LDF檔時,其實非常的單純,就是以StartOffSet為順序,從尾部開始,將 status 為 VLF收回,但在所有VLF中至少需有一個可用(status =0 )的VLF

SQL SERVER會試著去完成你所要求的收縮目標大小,當它發現VLF尾部的空間即使全放掉,也達不到你要求的收縮目標大小時,會自動在log中塞入一些NO-OP log,也就是一些無意義的log,這個目的是為了讓active VLF往下跳一個,以便backup log或checkpoint執行時能清除卡關的那個VLF。
如果你的DB的復原模式是simple mode,它會自動再做一次checkpoint,去試著清除LOG,這些動作的目地都只是為了讓shrink後的大小能符合你收縮目標大小的設定。

因此有時候明明資料沒多少但卻不能shrink,主要是目前的active VLF正好在LDF檔案的尾端。
以上圖這種情況,如果DB是full mode,執行交易紀錄的shrinkfile,將無法釋放任何的空間,因為最後一個VLF的狀態為2
這時候你可以等待或手動產生一些交易行為,然後執行backup log(full mode)或checkpoing(simple mode)並觀察「最高的activeVLF,最佳的位置是在LOG File的開頭處。然後再執行shrink

至於 AGs log shrink,由於shrink是寫入的動作,所以只能在主要節點進行shrink

dbcc shrinkfile(‘邏輯檔名’,收縮目標大小),如果目標大小給0的話,表示shrink 到 MinimumSize (初始化大小)。
mdf/ndf檔的 MinimumSize,可以從 dbcc page(xxx,1,0,3) 中的 MiniSize 看出來,但log file則無法使用dbcc page去看。
要查詢一個資料檔或交易紀錄檔的MinimumSize,其實可以簡單的借用 dbcc shrinkfile(‘邏輯檔名’, 99999) ,意思是給一個大值,確保它不會真的去壓縮,而只是想看它回傳的訊息。
圖中MinimumSize(8K Page)即是


所以,shrinkfile如果大小給0,它只會 shrink 到 MinimumSize,請注意。
資料行名稱
Description
DbId
Database Engine 試圖壓縮之檔案的資料庫識別碼。
FileId
Database Engine 試圖壓縮之檔案的檔案識別碼。
CurrentSize
檔案目前所佔的 8 KB 頁數。
MinimumSize
檔案所能佔用的 8 KB 頁數最小值。 這對應於檔案的大小下限或最初建立的大小。
UsedPages
檔案目前所用的 8 KB 頁數。
EstimatedPages
Database Engine 估計檔案可以壓縮成 8 KB 頁面的數目。


常常在shrinkfile時發現明明交易紀錄檔的「閒置空間」很大,但就是shrink不下來,後來經過一番研究才逐漸了解它背後的運作方式,此時才知道有個東西叫VLF,接著才又知道原來VLF也有碎片.,然後又再潛入交易紀錄檔的解析….等。
當逐漸了解交易紀錄檔的本質及運作原理後,在處理一些跟交易紀錄有關的問題時,心態就穩定許多,不容易再被一些問題的表象迷惑。




2018年8月30日 星期四

交易紀錄檔 VLF的數量對效能影響


SQLSERVER中,不論DB在哪種復原模式下,所有的異動都會寫入交易紀錄檔。差別在於紀錄的方式以及清除的方式不同。因此,如果交易紀錄檔的效能不好,其影響是全面性的。

SQLSERVER會將交易紀錄檔(transaction log file / ldf)劃分成多個「虚擬紀錄檔(Virtual Log File - VLF)來管理。
SQL2014以前的版本(不含2014),當建立、手動或自動成長LDF時,系統會將其劃分成數個VLF,如下表所示:

增量的大小
VLF
< 64MB
4
>=64MB and < 1GB
8
>=1GB
16

SQL2014,則有不同的演算法:
如果成長的量小於現行log file size1/8,那麼就只增加一個VLF
如果大於現行log file size1/8,那麼則依照舊有的規則。

我們可以使用dbcc loginfo來查看VLF的資訊,這也是目前唯一的方法。
以下圖為例,該資料庫的交易紀錄檔(*.ldf),共被劃分為10VLF來管理。


所以,假設你建立資料庫時,LDF檔初始大小設為10MB,那麼資料庫建立後,LDF檔會被切割為4VLF。如果有設定自動成長,每次成長10MB,那麼每成長一次,就會再多出4VLF
在一個繁忙的系統中,交易紀錄檔的初始大小如果不夠大,在系統運作的過程中,則會經常觸發自動成長。每次成長,系統就會將該空間依前述的規則切成數個VLF,因此VLF的數量也會增加。
自動成長所分配的空間,加入到原本的ldf檔的尾部。在邏輯上它會是連續的。但從實體上看,則整個ldf檔會是由許多不連續的空間所組成。因此,如果你的VLF的數量越多,表示你的交易紀錄檔(ldf)破碎的越嚴重。通常這被稱為 Transaction Log Fragmentation - 交易紀錄碎片,或者 VLF Fragmentation

我們知道,一般資料檔的碎片,會影響效能,所以我們通常會定期的檢查並重整。

~~~對於交易紀錄檔的碎片呢?

或許大家也跟我初次聽到時一樣~~~「啥….交易紀錄也有碎片!!!」

交易紀錄碎片的定義是比較粗糙的,一般是使用交易紀錄檔中VLF的數量來評估。也就是VLF的數量越多,表示交易紀錄的碎片越嚴重。
至於VLF的數量超過多少個才應該進行一些處理,這並沒有一個確切的標準可供遵循。一般的建議是VLF不要超過1000個。

我們可以使用下列指令,來取得所有DBVLF的計數:

DECLARE @query varchar(1000),
  @dbname varchar(1000),
  @count int
SET NOCOUNT ON
DECLARE csr CURSOR FAST_FORWARD READ_ONLY
FOR
SELECT name
FROM sys.databases

CREATE TABLE ##loginfo
(
  dbname varchar(100),
  num_of_rows int)
OPEN csr
FETCH NEXT FROM csr INTO @dbname
WHILE (@@fetch_status <> -1)
BEGIN

CREATE TABLE #log_info
(
  RecoveryUnitId tinyint,
  fileid tinyint,
  file_size bigint,
  start_offset bigint,
  FSeqNo int,
[status] tinyint,
  parity tinyint,
  create_lsn numeric(25,0)
)

SET @query = 'DBCC loginfo (' + '''' + @dbname + ''') '
INSERT INTO #log_info
EXEC (@query)
SET @count = @@rowcount
DROP TABLE #log_info
INSERT ##loginfo
VALUES(@dbname, @count)
FETCH NEXT FROM csr INTO @dbname

END

CLOSE csr
DEALLOCATE csr

SELECT dbname,
  num_of_rows
FROM ##loginfo
--WHERE num_of_rows >= 50
ORDER BY dbname

DROP TABLE ##loginfo

接下來,我們實際測試一下,交易紀錄的碎片真的對效能有影響嗎?

首先我建立兩個DB
VLF_LARGE模擬有許多的VLF,它的LOG的初始值10MB,然後用alter database的指令不斷的加LOG空間,以模擬自動成長,目地是增加VLF的數量。
VLF_SMALL模擬有較少的VLF,它的LOG的初始值直接給4GB

 
VLF_SMALL
VLF_LARGE
資料檔大小
4GB
4GB
資料檔路徑
H:\SQLData
H:\SQLData
LOG檔大小
4GB
4GB
LOG檔路徑
G:\SQLLog
G:\SQLLog
復原模式
Full
Full
VLF數量
16
4,008

接著我在兩個DB各建立一個空的TABLE,然後測試從另一個DBTABLE(300萬筆),將資料一次性新增進來。並且再測試updatedelete

經過四輪的測試,結果如下表
DML
VLF_SMALL
VLF_LARGE
1
2
3
4
平均
1
2
3
4
平均
INSERT
24
18
18
17
19.25
16
24
23
25
22
UPDATE
5
4
7
5
5.25
9
8
10
14
10.25
DELETE
7
5
11
7
7.5
17
10
21
15
15.75



從結果來看,VLF Fragmentation確實對這種大資料量的操作效能有所影響,我這個案例是測試300萬筆,是一個較大IO的操作。

由於交易紀錄是循序性的寫入,本文所測試的這種大資料量的操作,可能一次的操作所產生的交易紀錄量,就會跨許多不同的VLF才能儲存,因此若VLF碎片過高,會影響寫入的效能。

如果是小資料量的操作(如OLTP),由於交易紀錄量小(一次交易量不超過1VLF),則不受VLF碎片的影響。

上述的測試,其DB後端storage是傳統的旋轉式硬碟。

有趣的是,後來我改用本機的SSD硬碟進行上述測試,則不論VLF_LARGEVLF_SMALL,兩者對於DML的效能幾乎完全一致,也就是說如果DB的儲存媒體是SSD,則DML的效能不受VLF Fragementation的影響。這跟索引碎片在SSD的結果類似,碎片之所以在傳統旋轉式硬碟上會影響效能,主要是磁頭的物理移動頻繁所致,而SSD由於其存取的方式不同,所以沒有這個問題。




VLF數量對recovery效能的影響
VLF數量的多寡,對於recovery的影響則是巨大的,如果你的資料庫在啟動時需要很久才會online,且狀態處於recovery pending,那麼這篇文章會對你有所幫助。

我們做一個簡單的測試,有興趣的朋友也可以跟著試試看。

CREATE DATABASE [VLF_SMALL]
 ON  PRIMARY
( NAME = N'VLF_SMALL', FILENAME = N'H:\SQLData\VLF_SMALL.mdf' , SIZE = 2GB , FILEGROWTH = 1024KB )
 LOG ON
( NAME = N'VLF_SMALL_log', FILENAME = N'G:\SQLLog\VLF_SMALL_log.ldf' , SIZE = 2GB , FILEGROWTH = 10%)
GO
CREATE DATABASE [VLF_LARGE]
 ON  PRIMARY
( NAME = N'VLF_LARGE', FILENAME = N'H:\SQLData\VLF_LARGE.mdf' , SIZE = 2GB , FILEGROWTH = 1024KB )
 LOG ON
( NAME = N'VLF_LARGE_log', FILENAME = N'G:\SQLLog\VLF_LARGE_log.ldf' , SIZE = 1MB , FILEGROWTH = 10%)
GO

use master
go
declare @x int
declare @ssql varchar(1024)
set @x=10
while (@x<=2048)
   begin
      set @x=@x+1
      set @ssql='alter database VLF_LARGE modify file ( name = ''VLF_LARGE_log'', size = '+convert(varchar,@x)+'MB )'
      exec (@ssql)
   end
go

dbcc loginfo('VLF_LARGE')   --查看VLF數量有8160
go
dbcc loginfo('VLF_SMALL')   --查看VLF數量有16
go

資料庫相關指標如下
 
VLF_SMALL
VLF_LARGE
資料檔大小
2GB
2GB
資料檔路徑
H:\SQLData
H:\SQLData
LOG檔大小
2GB
2GB
LOG檔路徑
G:\SQLLog
G:\SQLLog
復原模式
Full
Full
VLF數量
16
8,160


這兩個 DB都是空的,剛剛建立,其中沒有任何的user tables。接著我們將它offline後再online,並計算online所需的時間,指令如下:
declare @x datetime
alter database VLF_SMALL set offline

set @x=getdate()
alter database VLF_SMALL set online

select datediff(ss,@x,getdate())





由上圖可見,VLF_SAMLLrecovery僅需1秒鐘。而VLF_LARGErecovery49秒,兩者recovery的速度差異非常的大。

SQL SERVER在執行recovery時,一次掃瞄一個VLF,當VLF數目多,會有較頻繁的VLF切換,故導致recovery所需時間的增加。
當你的SQL SERVER啟動時DB StartUp需要太長的時間,請檢查DBVLF數量是否太多。

VLF的數量及大小多少才適合,有幾個重要的參考指標:
一、每個VLF size不超過500MB
二、在SQL2012VLF數量超過1000個時,當SQL startrestoreattach時會有警告。
三、VLF的數量上限不要超過1000個
四、VLF數量的甜蜜點(sweet spot)為100個。

當超過1萬個VLF時,如果DBrecovery的情況時,則系統會發出警告,但警告的內容會告訴你說DB超過1仟個VLF時可能導致long startup and backup效能問題。但其實只要有recovery的動作,如offline/onlinerestore都會跳訊息。訊息如下:
Database dbName has more than n virtual log files which is excessive. Too many virtual log files can cause long startup and backup times. Consider shrinking the log and using a different growth increment to reduce the number of virtual log

VLF的數量及大小,也是影響SQL SERVER效能的一個重要環節,就像是我們日常重整索引的碎片,VLF碎片也需要定期的關注。

下一篇文章,我想接著談談VLF碎片的解決方法以及交易紀錄檔的壓縮(Shrink)