很少扎實的實作SQL Server Database Engine內部的行為,最近發現某個客戶的效能瓶頸可能在Transaction log的I/O,同時另外一家客戶正在導入儲存廠商異地備援(DR site)的解決方案(不打算用AlwaysOn傳送到異地),由於保護的是磁碟資源,我們需要確保磁碟上的mdf、ndf與ldf的一致性。
來筆記Buffer Flush To Disk讀書心得以及SQL2012/2014/SQL2016相關的新古與全新功能。
維持ACID的完整性(Atomicity)及持久性(Durability)
為了維持關聯式資料庫A.C.I.D的重要使命,SQL Server採用交易紀錄(Transaction log)來實現完整性Atomicity及持久性(Durability),這邊先簡單解釋完整性Atomicity及持久性(Durability):
- 完整性Atomicity: 交易必須全部完成執行,要不就是全部不執行。
- 持久性(Durability):交易完成之後,其作用便永遠存在於系統之中。
為了能擁有飛快的I/O效能,更新資料庫資料時,SQL Server會先將資料檔及紀錄檔更新在Buffer Pool(也稱Buffer cache),也就是記憶體緩存區內,而為了確保完整性及持久性,一個交易的Commit完成,則是將寫入Buffer Pool中的Log Flush到Disk上作為確認,也就是write-ahead log (WAL),先確保log儲存磁碟正確(這與AlwaysOn的同步認可很類似)。
因為資料分頁(Data Page)還在Buffer Pool上,此時若SQL Server所在的伺服器發生記憶體異常,只要Transaction log的磁碟檔案(.ldf)正確,DataBase Engine還是可以使用Transaction Log完成ReDo及UnDo將Disk上的mdf/ndf檔案追回或復原正確。
*如果直接同步mdf、ndf與ldf到異地機房(DR Site),可能會碰上log(ldf)是新的,但data file(mdf ndf)還是舊的情境。
從Buffer Pool寫入Log到磁碟的時間點
前面文章理解到Transaction Log寫入磁碟的其中一個時間點是交易Commit的Ending pose,整理幾個寫入磁碟的時間點:
- 交易commit
- log Buffer到達60K的容量
- checkpoint 檢查點
這邊我們先簡單測試交易commit前後,DataBase Engine在磁碟上的活動。
1.建立測試資料庫、測試資料表及測試資料
CREATE DATABASE [FlushDiskDb]
CONTAINMENT = NONE
ON PRIMARY
( NAME = N'FlushDiskDb', FILENAME = N'C:\temp\db\FlushDiskDb.mdf' , SIZE = 5120KB , FILEGROWTH = 1024KB )
LOG ON
( NAME = N'FlushDiskDb_log', FILENAME = N'C:\temp\db\FlushDiskDb_log.ldf' , SIZE = 2048KB , FILEGROWTH = 10%)
GO
USE [master]
GO
ALTER DATABASE [FlushDiskDb] SET RECOVERY FULL WITH NO_WAIT
GO
create table t1
(
c1 int identity,
c2 varchar(30)
)
insert t1 VALUES('Stanley')
insert t1 VALUES('Taiwan')
2.打開process monitor
3.設定Filter條件
Ctrl + L 熱鍵跳出Filer選項
設定Filter條件: Path > Contaons > database file路徑
4.建立一個交易,然後先不要Commit
use FlushDiskDb
BEGIN tran tran_one
update t1
set c2 = 'France'
where c1 = 2
5.果然在process monitor中: 空空如也!
6.但如果此時在交易的最後面下了commit
process monitor 出現了寫入.ldf的磁碟活動! 而且只有ldf沒有mdf磁碟活動喔。
write-ahead log (WAL),先保證log寫入磁碟來確保ACID的AD!
從Buffer Pool寫入資料分頁到磁碟的時間點
前面的簡單練習可以理解到Log寫入磁碟的時間點,也許聰明的工程師一定很快想進一步知道,資料分頁寫入磁碟的時間點? 從Technet的文章很快找到答案:
- checkpoint:檢查點發生
- Lazy writer:懶惰寫入器工作了(因為很懶惰,觸發的機會相較checkpoint少很多)
- Eager writing: 熱切的寫入器被選擇,然後大量載入交易發生了(nonlogged操作的一種,這邊暫時不討論)
簡單把Data Page和log buffer一起劃圖來表示磁碟寫入流程:
檢查點(CheckPoint)觸發時機
這邊我們先study最常發生的CheckPoint,中文是檢查點,CheckPoint會將目前記憶體中已修改的頁面 (中途分頁或稱Dirty Page)和交易記錄資訊從記憶體緩存(Buffer Cache)中寫入磁碟上。
Database Engine 支援幾種類型的檢查點:
- 自動:SQL DataBase Engine決定,可在執行個體層級設定(recovery interval)。
- 間接:SQL Server 2012中的新功能,在資料庫層級設定固定週期的檢查點時間(TARGET_RECOVERY_TIME)。
- 手動: 人為的方式執行checkpoint指令
- 系統內部: 執行資料庫備份、快照、停止 SQL Server (MSSQLSERVER) 服務、SQL Server 容錯移轉叢集執行個體 (FCI) 離線、使用 ALTER DATABASE 加入或移除資料庫檔案..
我們來試試剛剛的案例,新增查詢視窗,手動輸入checkpoint指令!
Process monitor多了寫入.mdf的磁碟活動
Checkpoint vs Lazy writer
Checkpoint和Lazy writer很類似,但Checkpoint的設計出發點是為了縮短資料庫的復原時間,但Lazy writer則是為了解決記憶體壓力,兩種執行後都會將Buffer Cache的資料分頁寫入磁碟,但只有Lazy Writer會釋放出資源。
如果也希望checkpoint釋放資源,舉例來說,我們需要手動再執行DBCC DROPCLEANBUFFERS指令:
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS;
GO
查詢目前Buffer Pool中的資料表
我們可以透過sys.dm_os_buffer_descriptors 這個dm查詢到!
USE FlushDiskDb
SELECT
DB_NAME(buf.database_id) AS DbName
,o.name
,buf.*
FROM sys.dm_os_buffer_descriptors buf
LEFT JOIN sys.allocation_units AS au WITH (NOLOCK)
ON buf.allocation_unit_id = au.allocation_unit_id
LEFT JOIN sys.partitions AS pt WITH (NOLOCK)
ON au.container_id = pt.hobt_id
LEFT JOIN sys.objects AS o WITH (NOLOCK)
ON pt.object_id = o.object_id
LEFT JOIN sys.indexes AS idx WITH (NOLOCK)
ON o.object_id = idx.object_id
AND pt.index_id = idx.index_id
WHERE DB_NAME(buf.database_id) = db_name()
and o.type_desc = 'USER_TABLE'
ORDER by name
執行結果:
1.Checkpoint執行前: Dirty Page
2.Checkpoint執行後
3.DBCC DROPCLEANBUFFERS執行後:
今天先大致理解Buffer Pool寫入磁碟的架構及時間點,周末繼續筆記未完成的SQL2012/2014/2016新古和全新功能。
參考:
Writing Pages
https://technet.microsoft.com/en-us/library/aa337560(v=sql.105).aspx
資料庫檢查點 (SQL Server)
https://msdn.microsoft.com/zh-tw/library/ms189573(v=sql.120).aspx