再談SQL Server中日誌的的做用

簡介

    以前我已經寫了一個關於SQL Server日誌的簡單系列文章。本篇文章會進一步挖掘日誌背後的一些概念,原理以及做用。若是您沒有看過我以前的文章,請參閱:html

    淺談SQL Server中的事務日誌(一)----事務日誌的物理和邏輯構架數據庫

    淺談SQL Server中的事務日誌(二)----事務日誌在修改數據時的角色安全

    淺談SQL Server中的事務日誌(三)----在簡單恢復模式下日誌的角色架構

    淺談SQL Server中的事務日誌(四)----在完整恢復模式下日誌的角色併發

    淺談SQL Server中的事務日誌(五)----日誌在高可用和災難恢復中的做用數據庫設計

 

數據庫的可靠性

    在關係數據庫系統中,咱們須要數據庫可靠,所謂的可靠就是當碰見以下兩種狀況之一時保證數據庫的一致性:性能

  • 在系統崩潰/故障等狀況下,保證數據庫的一致性
  • 數據不能在多個DML語句同時修改數據的狀況下,致使不一致或數據損壞

 

    實際上,上述第二種狀況就是併發性所須要解決的問題,傳統關係數據庫中,咱們用鎖來解決這個問題,而對於內存數據庫或帶有樂觀併發控制的數據庫系統,經過多版本併發控制(MVCC)來解決這個問題。由於本篇文章的主旨是討論日誌而不是併發,所以對於上述第二種狀況不會詳細解釋。測試

    咱們上面還屢次提到了一致性(Consistence),在開始瞭解日誌如何維持一致性以前,咱們首先要明白什麼是一致性。一致性在數據庫系統中所指的內容比較廣,一致性不只僅須要數據庫中的數據知足各類約束,好比說惟一約束,主鍵約束等,還須要知足數據庫設計者心中的隱式約束,簡單的業務約束好比說性別這列只容許男或女,這類隱式約束一般使用觸發器或約束來實現,或是在數據庫所服務的應用程序中進行約束。優化

    下面咱們把一致性的範圍縮減到事務一致性,事務一致性的概念學術上的解釋爲:線程

若是事務執行期間沒有出現系統錯誤或其餘事務錯誤,而且數據庫在事務開始期間是數據一致的,那麼在該事務結束時,咱們認爲數據庫仍然保證了一致性。

    所以,引伸出來事務必須知足原子性,也就是事務不容許部分執行。事務的部分執行等同於將數據庫置於不一致的境地之下。此外多事務併發執行也可能致使數據庫不一致,除非數據庫系統對併發進行控制。

    關於上面的顯式約束,由數據庫系統來實現,好比說違反了一致性約束的語句會致使數據庫系統報錯並拒絕執行。但一些隱式的事務約束,好比說寫語句的開發人員對系統設計者所設計的規則並不瞭解,致使了違反業務規則的數據修改,這種狀況在數據庫端很難探查。可是這種問題一般能夠規則到權限控制的領域,咱們認爲授予某個用戶修改特定數據的權限,就認爲這個用戶應該瞭解數據庫中隱式和顯式的規則。

    除去這些業務上的數據不一致以外,咱們須要在系統崩潰等狀況下保證數據的一致性,而可能致使這類數據不一致的狀況包括但不限於下面這些狀況:

  •     存儲系統損壞,好比說磁盤上字節級別的損壞,這類問題一般能夠經過磁盤上的奇偶校驗發現,另外還有一些大一些的問題,好比說整個存儲系統崩潰。這類問題的修復手段取決於前期工做,好比說備份策略,高可用性架構,SAN Replication等技術。
  •     機房總體損壞,這類問題比較極端,只有異地機房容災能夠解決。
  •     系統故障,修改數據的進程都須要事務做爲上下文,和其餘概念同樣,事務也是有狀態的。而事務狀態一般存儲在易丟失的主存中,所以,當出現系統故障、進程崩潰等系統失敗時,可能致使事務狀態的丟失,此時,咱們就沒法得知事務中的哪部分已經執行而哪部分還未執行,從新運行事務並不會解決這類問題,由於有可能致使事務中某部分的重複執行。所以解決這類問題的方式就是將事務的狀態以及對數據庫修改的詳細步驟與內存中的數據分開存放,並存儲於磁盤等穩定的介質中,當系統故障等狀況下,咱們能夠經過這些記錄來將系統恢復到一致性的狀態之下,咱們對這類存儲,稱之爲日誌。

 

SQLServer中的日誌

    SQL Server中靠日誌來維護一致性(固然,日誌的做用很是多,但一致性是日誌的基本功能,其餘功能能夠看做是額外的功能)。一般咱們建立數據庫的時候,會附帶一個擴展名爲ldf的日誌文件。日誌文件其實本質上就是日誌記錄的集合。在SQL Server中,咱們能夠經過DBCC LOGINFO來看這個日誌的信息,如圖1所示。

    1

    圖1.DBCC LOGINFO

 

    該命令能夠從VLF的角度從一個比較高的層級看日誌。其中值得注意的列是VLF大小,狀態(2表示使用,0表示從未使用過),偏移量。對於這些信息對咱們規劃VLF數量的時候頗有幫助,由於VLF過多可能引發嚴重的性能問題,尤爲是在複製等Scale-Out或HA環境下。

    而後,事務對數據庫中每次修改都會分解成多個多個原子層級的條目被記錄到持久存儲中,這些條目就是所謂的日誌記錄(Log Record),咱們能夠經過fn_dblog來查看這些條目。如圖2所示。

2

圖2.Fn_dblog

 

    每一個日誌記錄都會被背賦予一個惟一的順序編號,這個編號大小爲10字節,由三部分組成,分別爲:

  •     VLF順序號(4字節)
  •     Log Block順序號(4字節)
  •     Log Block內的順序編號(2字節)

 

    所以,因爲VLF是不斷遞增的(同一個VLF被複用會致使編號改變),所以LSN序號也是不斷遞增的。所以,經過上面的LSN結構不難發現,若是比VLF更小的粒度並非直接對應LOG RECORD,而是LOG Block。Log Block是日誌寫入持久化存儲的最小單位,Log Block的大小從512字節到60K不等,這取決於事務的大小,那些在內存還未被寫入持久化存儲的Log Block也就是所謂的In-Flight日誌。如下兩個因素決定Log Block的大小:

  • 事務提交或回滾
  • Log Block滿60K會強制Flush到持久化存儲,以保證WAL

    所以當一個事務很大時(好比說大面積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

    圖3.三者之間的關係

   

    從比較高的層級瞭解了日誌以後,咱們再仔細瞭解日誌中應該存儲的關鍵信息,每條Log Record中都包含下面一部分關鍵信息:

  • LSN
  • Log Record的Context
  • Log Record所屬的事務ID(全部的用戶事務都會存在事務ID)
  • Log Record所佔的字節
  • 同一個事務中上一條Log Record的LSN(用於Undo)
  • 爲Undo所保留的日誌空間

    固然,這些僅僅是日誌的一小部份內容。經過Log Record所記錄的內容,就可以精確的記錄對數據庫所作的修改。

 

日誌用於Undo

    在瞭解爲了Undo,日誌所起的做用以前,咱們首先能夠了解一下爲何須要事務存在回滾:

  • 由於事務可能失敗,或者死鎖等緣由,若是但願事務不違反原子性而形成數據庫不一致的話,則須要經過回滾將已經部分執行的事務回滾掉。
  • 根據業務需求,若是在某些關聯業務失敗等狀況下,回滾數據。

    所以,Log Record會爲這些列保存一些字節來執行數據庫回滾,最簡單的例子莫過於執行插入後Rollback事務,則日誌會產生一條所謂的Compensation Log Record來反操做前面已經插入的事務,如圖4所示。

    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.回滾事務

 

    圖5中,刪除3條數據後,進行回滾,首先從刪除3開始,生成對應的反向Compensation Log Record,並指向刪除2,再對應刪除2生成反向Compensation Log Record並指向刪除1,以此類推,最終回滾事務指回開始事務。

 

 

日誌用於Redo

    與Undo不一樣,在計算機存儲體系中,輔助存儲一般是帶有磁頭的磁盤。這類存儲系統的IOPS很是低,所以若是對於事務對數據庫執行的修改操做,咱們積累到必定量再寫入磁盤,無疑會提升IO的利用率。可是在數據在主存尚未持久化的輔助存儲的期間,若是遭遇系統故障,則這部分數據的丟失則可能致使數據庫的不一致狀態。

   所以,使用日誌使得該問題獲得解決。與日誌Undo方面的不一樣之處在於:Undo用於解決事務未完成和事務回滾的狀況,而Redo則是爲了保證已經提交的事務所作的修改持久化到輔助存儲。

   Redo則引伸出了WAL,即事務日誌會在COMMIT或COMMIT以前寫入持久化存儲中,而後事務對數據自己的修改才能生效。所以就可以保證在系統故障時能夠經過讀取日誌來Redo日誌的持久化操做。所以對於最終用戶能夠顯示事務已經提交而暫時不用將所修改的數據寫入持久化存儲。因爲數據在日誌未寫入持久化存儲以前沒法持久化,則須要更大的主存做爲BUFFER空間。

    由於日誌既要用於Undo,又要用於Redo,所以爲了可以成功生成Compensation Log Record,須要日誌既記錄被修改前的數據,又記錄被修改後的數據,好比咱們在圖6中作一個簡單的更新。

6

圖6.記錄更新以前和以後的數據

 

   值得注意的是,若是修改的值是彙集索引鍵,則因爲修改該數據會致使存儲的物理位置改變,因此SQL Server並不會像這樣作即席更新,而是刪除數據再插入數據,從而致使成本的增長,所以儘可能不要修改彙集索引鍵。

 

Undo/Redo Recovery

    當SQL Server非正常緣由關閉時,也就是在沒有走CheckPoint(會在下面提到)時關閉了數據庫,此時數據庫中數據自己可能存在不一致的問題。所以在數據庫再次啓動的時候,會去掃描日誌,找出那些未提交卻寫入持久化存儲的數據,或已提交卻未寫入持久化存儲的數據,來進行Undo和Redo來保證事務的一致性。Undo/Redo Recovery遵循如下規則:

  • 按照由早到晚的順序Redo該已提交卻未寫入持久化存儲的數據
  • 按照由晚到早的順序Undo未提交,卻寫入持久化存儲的數據

    圖7中,咱們進行一個簡單測試,在啓動過程當中,首先禁用了CheckPoint以防止自動CheckPoint,而後咱們修改數據,不提交,並持久化到磁盤。另外一個線程修改數據並提交,但未持久化到磁盤。爲了簡單起見,我把兩個線程寫到一個窗口中。

    7

    圖7.須要Undo和Redo的兩個事務

 

    此時咱們強制殺死SQL Server進程,致使數據自己不一致,此時在SQL Server的重啓過程當中,會自動的Redo和Undo上面的日誌,如圖8所示。

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

圖9.CheckPoint標記

 

   其中,這些Log Record會包含CheckPoint的開始時間,結束時間以及MinLSN,用於複製的LSN等。由圖9中咱們還能夠看到一個LOP_XACT_CKPT操做的Log Record,該操做符的上下文若是爲NULL的話,則意味着當前:

  • 包含未提交事務
  • 該Log Record記錄包含未提交事務的個數
  • 包含未提交的事務所涉及的LSN

   由CheckPoint的機制能夠看出,因爲內存中的數據每每比持久化存儲中的數據更新,而CheckPoint保證了這部分數據可以被持久化到磁盤,所以CheckPoint以前的數據必定不會再須要被Redo。而對於未提交的事物所修改的數據寫入持久化存儲,則能夠經過Undo來回滾事務(未提交的事物會致使CheckPoint沒法截斷日誌,所以這部分日誌能夠在Recovery的時候被讀取到,即便這部分日誌在CheckPoint以前)。

此時,咱們就能夠100%的保證,CheckPoint以前的日誌就能夠被安全刪除(簡單恢復模式)或歸檔了(完整恢復模式),在Recovery時,僅僅須要從CheckPoint開始掃描日誌,從而減小宕機時間。

 

小結

    本篇文章深刻挖掘了數據庫中日誌爲保護數據一致性的的做用、實現原理。日誌在這些功能以外,也是爲了用於實現高可用性,所以瞭解這些原理,能夠更好的幫助咱們在搭建高可用性拓撲以及設計備份計劃時避免一些誤區。

相關文章
相關標籤/搜索