2017年1月23日 星期一

SQL SERVER頁面還原的疑問

SQL SERVER在執行頁面還原後(restore database ....page....with norecovery),通常還需執行 restore log。許多人會有這樣一個疑問,就是我們只是把某個頁面還原回來,但接著還原LOG時,並沒有(也不能)指定頁面,而是必需還原整個LOG。而LOG中包含了許多「非我們想還原的頁面」,這樣會不會造成LOG中的操作被重覆執行?

每個人可能都會說:「當然是不會...」,


接下來,我想就個人的理解,解釋一下「為什麼...

SQL SERVER頁面header的資訊中,有個m_lsn,這個代表了這個頁面最後被異動的LSN號碼。每當這個頁面的資料有異動時,頁面的m_lsn即會被更新。


Page Headerm_lsn,對應到Transaction LogLSN。當restore log時,如果一個logrestore,當它要去異動某個頁面時,它會用Transaction LogLSN跟頁面上的m_lsn去比對,當m_lsn小於 LSN 時,這個異動才會restore到這個頁面上。
這樣的機制,主要是用在「頁面還原」時,當我們執行
RESTORE DATABASE PAGE='1:57, 1:202, 1:916, 1:1016' 
   FROM   
   WITH NORECOVERY; 
RESTORE LOG FROM   
   WITH NORECOVERY; 
RESTORE LOG FROM   
   WITH NORECOVERY;  
某頁面損壞時,先從備份檔把某頁面還原。接著再還原後續的交易紀錄。
由於是頁面還原,正常的頁面還是保留在最新狀態。我們原本的疑問是,這時再還原LOG,那麼會不會造成一些重覆性的操作。


答案是不會,因為restore log時,必需是頁面的m_lsn小於LSN 才會被異動。因此也保證了頁面還原的可行性。


以下做個簡單的測試:
create database PHIL
go
use PHIL
go
create table test0120(c1 int identity,c2 char(500))
go
insert into test0120 values('abcd')
go 5
上面,建立一個資料庫,並塞入幾筆資料

dbcc ind(PHIL,test0120,1)
go
以下做個簡單的測試:
create database PHIL
go
use PHIL
go
create table test0120(c1 int identity,c2 char(500))
go
insert into test0120 values('abcd')
go 5
上面,建立一個資料庫,並塞入幾筆資料

dbcc ind(PHIL,test0120,1)
go

dbcc traceon(3604)
dbcc page(PHIL,1,119,2)
go














我們利用工具將頁面119m_lsn改成34:94:2

接著執行資料庫備份,此時在備份檔中的頁面是我改過了m_lsn
backup database PHIL to disk='d:\backup\phil.bak' with init

刪除某筆資料
delete test0120 where c1=3

再備份交易紀錄
backup log PHIL to disk='d:\backup\phil.trn' with init


上面是一個典型的資料庫備份過程,照理說,我們如果將資料庫復原,再將log復原,test0120這個tablec1=3的資料,應該會不在。
但由於在資料庫備份檔中的m_lsn被我們改成較大的值,依前面的說明,restore log時,這個頁面的lsn較新,因為這筆交易將不會復原。

接著我們測試看看
首先還原資料庫,並置於norecovery狀態
restore database PHIL from disk='d:\backup\phil.bak' with replace,norecovery
再來還原交易紀錄
restore log PHIL from disk='d:\backup\phil.trn'

檢查c1=3的紀錄,正常的情況下,這筆紀錄應該是不在

select * from phil..test0120












但我們可以看到上圖,c1=3的這筆紀錄依然存在,表示那筆交易紀錄並沒有apply

無論是還原交易紀錄或者是還原頁面,其實都是以頁面的LSN為「比較點」,當頁面的m_lsn < Log LSN,這筆交易(或者頁面)才會被還原。