以前我已經寫了一個關於SQL Server日誌的簡單系列文章。本篇文章會進一步挖掘日誌背後的一些概念,原理以及做用。若是您沒有看過我以前的文章,請參閱:html
淺談SQL Server中的事務日誌(一)----事務日誌的物理和邏輯構架數據庫
淺談SQL Server中的事務日誌(二)----事務日誌在修改數據時的角色安全
淺談SQL Server中的事務日誌(三)----在簡單恢復模式下日誌的角色架構
淺談SQL Server中的事務日誌(四)----在完整恢復模式下日誌的角色併發
淺談SQL Server中的事務日誌(五)----日誌在高可用和災難恢復中的做用數據庫設計
在關係數據庫系統中,咱們須要數據庫可靠,所謂的可靠就是當碰見以下兩種狀況之一時保證數據庫的一致性:性能
實際上,上述第二種狀況就是併發性所須要解決的問題,傳統關係數據庫中,咱們用鎖來解決這個問題,而對於內存數據庫或帶有樂觀併發控制的數據庫系統,經過多版本併發控制(MVCC)來解決這個問題。由於本篇文章的主旨是討論日誌而不是併發,所以對於上述第二種狀況不會詳細解釋。測試
咱們上面還屢次提到了一致性(Consistence),在開始瞭解日誌如何維持一致性以前,咱們首先要明白什麼是一致性。一致性在數據庫系統中所指的內容比較廣,一致性不只僅須要數據庫中的數據知足各類約束,好比說惟一約束,主鍵約束等,還須要知足數據庫設計者心中的隱式約束,簡單的業務約束好比說性別這列只容許男或女,這類隱式約束一般使用觸發器或約束來實現,或是在數據庫所服務的應用程序中進行約束。優化
下面咱們把一致性的範圍縮減到事務一致性,事務一致性的概念學術上的解釋爲:線程
若是事務執行期間沒有出現系統錯誤或其餘事務錯誤,而且數據庫在事務開始期間是數據一致的,那麼在該事務結束時,咱們認爲數據庫仍然保證了一致性。
所以,引伸出來事務必須知足原子性,也就是事務不容許部分執行。事務的部分執行等同於將數據庫置於不一致的境地之下。此外多事務併發執行也可能致使數據庫不一致,除非數據庫系統對併發進行控制。
關於上面的顯式約束,由數據庫系統來實現,好比說違反了一致性約束的語句會致使數據庫系統報錯並拒絕執行。但一些隱式的事務約束,好比說寫語句的開發人員對系統設計者所設計的規則並不瞭解,致使了違反業務規則的數據修改,這種狀況在數據庫端很難探查。可是這種問題一般能夠規則到權限控制的領域,咱們認爲授予某個用戶修改特定數據的權限,就認爲這個用戶應該瞭解數據庫中隱式和顯式的規則。
除去這些業務上的數據不一致以外,咱們須要在系統崩潰等狀況下保證數據的一致性,而可能致使這類數據不一致的狀況包括但不限於下面這些狀況:
SQLServer中的日誌
SQL Server中靠日誌來維護一致性(固然,日誌的做用很是多,但一致性是日誌的基本功能,其餘功能能夠看做是額外的功能)。一般咱們建立數據庫的時候,會附帶一個擴展名爲ldf的日誌文件。日誌文件其實本質上就是日誌記錄的集合。在SQL Server中,咱們能夠經過DBCC LOGINFO來看這個日誌的信息,如圖1所示。
圖1.DBCC LOGINFO
該命令能夠從VLF的角度從一個比較高的層級看日誌。其中值得注意的列是VLF大小,狀態(2表示使用,0表示從未使用過),偏移量。對於這些信息對咱們規劃VLF數量的時候頗有幫助,由於VLF過多可能引發嚴重的性能問題,尤爲是在複製等Scale-Out或HA環境下。
而後,事務對數據庫中每次修改都會分解成多個多個原子層級的條目被記錄到持久存儲中,這些條目就是所謂的日誌記錄(Log Record),咱們能夠經過fn_dblog來查看這些條目。如圖2所示。
圖2.Fn_dblog
每一個日誌記錄都會被背賦予一個惟一的順序編號,這個編號大小爲10字節,由三部分組成,分別爲:
所以,因爲VLF是不斷遞增的(同一個VLF被複用會致使編號改變),所以LSN序號也是不斷遞增的。所以,經過上面的LSN結構不難發現,若是比VLF更小的粒度並非直接對應LOG RECORD,而是LOG Block。Log Block是日誌寫入持久化存儲的最小單位,Log Block的大小從512字節到60K不等,這取決於事務的大小,那些在內存還未被寫入持久化存儲的Log Block也就是所謂的In-Flight日誌。如下兩個因素決定Log Block的大小:
所以當一個事務很大時(好比說大面積update),每60K就會成爲一個Log Block寫入持久化存儲。而對於不少小事務,提交或回滾就會稱爲一個Block寫入持久化存儲,所以根據事務的大小,LOG Block的大小也會不一樣。值得疑惑的是,由於磁盤上分配單元的大小是2的N次方,所以最接近LOG BLOCK的大小應該是64K,而SQL Server爲何不把Log Block設定爲64K呢。這樣能夠更優化IO。
VLF和Log Block和Log Record的關係如圖3所示。
圖3.三者之間的關係
從比較高的層級瞭解了日誌以後,咱們再仔細瞭解日誌中應該存儲的關鍵信息,每條Log Record中都包含下面一部分關鍵信息:
固然,這些僅僅是日誌的一小部份內容。經過Log Record所記錄的內容,就可以精確的記錄對數據庫所作的修改。
在瞭解爲了Undo,日誌所起的做用以前,咱們首先能夠了解一下爲何須要事務存在回滾:
所以,Log Record會爲這些列保存一些字節來執行數據庫回滾,最簡單的例子莫過於執行插入後Rollback事務,則日誌會產生一條所謂的Compensation Log Record來反操做前面已經插入的事務,如圖4所示。
圖4.Compensation Log示例
圖4執行的是一個簡單的Insert語句,而後回滾。咱們看到,SQL Server生成了一個Compensation Log Record來執行反向操做,也就是Delete操做。值得注意的是,爲了防止這些回滾操做,SQL Server會保留一些空間用於執行回滾,咱們看到LOP_INSERT_ROWS保留的74字節空間被下面的Compensation Log Record所消耗。Compensation Log record還有一個指向以前LSN的列,用於回滾,直至找到LOP_BEGIN_XACT的事務開始標記。另外,Compenstion Log Record只可以用於Redo,而不能用於Undo。
那假設咱們某一個事務中刪除了多條數據怎麼辦?好比說,某一個事務中一個Delete語句刪除了10行,則須要在Log Record對應10個LOP_DELETE_ROWS(引伸一下,由此咱們能夠看出某一個語句可能致使N個Log Record,這麼多Log Record在複製,鏡像時都須要在另外一端Redo,所以須要額外的開銷),若是咱們此時RollBack了該事務,則Redo的順序是什麼呢,如圖5所示。
圖5.回滾事務
圖5中,刪除3條數據後,進行回滾,首先從刪除3開始,生成對應的反向Compensation Log Record,並指向刪除2,再對應刪除2生成反向Compensation Log Record並指向刪除1,以此類推,最終回滾事務指回開始事務。
與Undo不一樣,在計算機存儲體系中,輔助存儲一般是帶有磁頭的磁盤。這類存儲系統的IOPS很是低,所以若是對於事務對數據庫執行的修改操做,咱們積累到必定量再寫入磁盤,無疑會提升IO的利用率。可是在數據在主存尚未持久化的輔助存儲的期間,若是遭遇系統故障,則這部分數據的丟失則可能致使數據庫的不一致狀態。
所以,使用日誌使得該問題獲得解決。與日誌Undo方面的不一樣之處在於:Undo用於解決事務未完成和事務回滾的狀況,而Redo則是爲了保證已經提交的事務所作的修改持久化到輔助存儲。
Redo則引伸出了WAL,即事務日誌會在COMMIT或COMMIT以前寫入持久化存儲中,而後事務對數據自己的修改才能生效。所以就可以保證在系統故障時能夠經過讀取日誌來Redo日誌的持久化操做。所以對於最終用戶能夠顯示事務已經提交而暫時不用將所修改的數據寫入持久化存儲。因爲數據在日誌未寫入持久化存儲以前沒法持久化,則須要更大的主存做爲BUFFER空間。
由於日誌既要用於Undo,又要用於Redo,所以爲了可以成功生成Compensation Log Record,須要日誌既記錄被修改前的數據,又記錄被修改後的數據,好比咱們在圖6中作一個簡單的更新。
圖6.記錄更新以前和以後的數據
值得注意的是,若是修改的值是彙集索引鍵,則因爲修改該數據會致使存儲的物理位置改變,因此SQL Server並不會像這樣作即席更新,而是刪除數據再插入數據,從而致使成本的增長,所以儘可能不要修改彙集索引鍵。
當SQL Server非正常緣由關閉時,也就是在沒有走CheckPoint(會在下面提到)時關閉了數據庫,此時數據庫中數據自己可能存在不一致的問題。所以在數據庫再次啓動的時候,會去掃描日誌,找出那些未提交卻寫入持久化存儲的數據,或已提交卻未寫入持久化存儲的數據,來進行Undo和Redo來保證事務的一致性。Undo/Redo Recovery遵循如下規則:
圖7中,咱們進行一個簡單測試,在啓動過程當中,首先禁用了CheckPoint以防止自動CheckPoint,而後咱們修改數據,不提交,並持久化到磁盤。另外一個線程修改數據並提交,但未持久化到磁盤。爲了簡單起見,我把兩個線程寫到一個窗口中。
圖7.須要Undo和Redo的兩個事務
此時咱們強制殺死SQL Server進程,致使數據自己不一致,此時在SQL Server的重啓過程當中,會自動的Redo和Undo上面的日誌,如圖8所示。
圖8.實現Redo和Undo
那麼,什麼是CheckPoint?
圖8給出的簡單例子足以說明Recovery機制。但例子過於簡單,假如一個很是繁忙的數據庫可能存在大量日誌,一個日誌若是所有須要在Recovery過程當中被掃描的話,那麼Recovery過程所致使的宕機時間將會成爲噩夢。所以,咱們引入一個叫CheckPoint的機制,就像其名稱那樣,CheckPoint就是一個存檔點,意味着咱們能夠從該點繼續開始。
在Undo/Redo機制的數據庫系統中,CheckPoint的機制以下:
1.將CheckPoint標記寫入日誌(標記中包含當前數據庫中活動的事務信息),並將Log Block寫入持久化存儲
2.將Buffer Pool中全部的髒頁寫入磁盤,全部的髒頁包含了未提交事務所修改的數據
3.將結束CKPT標記寫入日誌,並將Log Block寫入持久化存儲
咱們在日誌中能夠看到的CheckPoint標記如圖9所示。
圖9.CheckPoint標記
其中,這些Log Record會包含CheckPoint的開始時間,結束時間以及MinLSN,用於複製的LSN等。由圖9中咱們還能夠看到一個LOP_XACT_CKPT操做的Log Record,該操做符的上下文若是爲NULL的話,則意味着當前:
由CheckPoint的機制能夠看出,因爲內存中的數據每每比持久化存儲中的數據更新,而CheckPoint保證了這部分數據可以被持久化到磁盤,所以CheckPoint以前的數據必定不會再須要被Redo。而對於未提交的事物所修改的數據寫入持久化存儲,則能夠經過Undo來回滾事務(未提交的事物會致使CheckPoint沒法截斷日誌,所以這部分日誌能夠在Recovery的時候被讀取到,即便這部分日誌在CheckPoint以前)。
此時,咱們就能夠100%的保證,CheckPoint以前的日誌就能夠被安全刪除(簡單恢復模式)或歸檔了(完整恢復模式),在Recovery時,僅僅須要從CheckPoint開始掃描日誌,從而減小宕機時間。
本篇文章深刻挖掘了數據庫中日誌爲保護數據一致性的的做用、實現原理。日誌在這些功能以外,也是爲了用於實現高可用性,所以瞭解這些原理,能夠更好的幫助咱們在搭建高可用性拓撲以及設計備份計劃時避免一些誤區。