天天看點

01. SQL Server 如何讀寫資料

一. 資料讀寫流程簡要

SQL Server作為一個關系型資料庫,自然也維持了事務的ACID特性,資料庫的讀寫沖突由事務隔離級别控制。無論有沒有顯示開啟事務,事務都是存在的。流程圖如下:

01. SQL Server 如何讀寫資料

資料讀寫流程圖

0. 事務開始

(1) 所有DML語句必然是基于事務的,如果沒有顯式開啟事務,即手動寫下BEGIN TRAN,SQL Server則把每條語句作為一個事務,并自動送出事務。

也就是說SQL SERVER 預設不開啟隐式事務,這點與ORACLE正好相反,ORACLE預設開啟了隐式事務,每條DML語句或者語句塊,都要手動commit才會送出。

SQL Server裡如要改變這個預設行為,可以在會話裡做如下設定,如果沒有打開隐式事務,SQL Server會自動送出目前的DML語句,而打開後,需要手動COMMIT才會送出。

(2) 如果手動開啟了一個事務(BEGIN TRAN),則和開啟隐式事務(SET IMPLICIT_TRANSACTIONS ON)一樣,需要手動送出事務(COMMIT);

1. 發起DML

(1) DML通常指的是:INSERT、DELETE、UPDATE;

(2) DDL語句最終是被轉化為對系統表的DML,在SQL SERVER中DDL語句也可以被復原,比如:CREATE/ALTER/DROP/TRUNCATE,在ORACLE裡是不可以的,另外SQL Server中的DCL語句:DENY,REVOKE,也可以被復原;

2. 資料是否在記憶體

(1) 在記憶體中使用HASH算法查找資料,如果找到資料那麼記為邏輯讀;

(2) 如果資料頁不在記憶體中,則需要從磁盤上的資料檔案中,讀取相應的資料頁到記憶體中,即實體讀,實體讀也會被記數為邏輯讀,也就是說無論記憶體中有沒有資料,邏輯讀是一定有的。

3. 修改資料

(1) 在SQL SERVER記憶體的資料緩沖區中将資料頁修改,此時資料頁稱為髒頁(DIRTY PAGE);

(2) 在SQL SERVER記憶體的日志緩沖區中記錄REDO LOG,姑且稱為髒日志;

4. 事務結束

(1) 送出(COMMIT),此時将目前事務的髒日志重新整理到資料庫的日志檔案中,并打上事務結束标記(COMMIT),髒頁有可能暫未被重新整理到資料檔案;

事務日志結構如下(可通過log explorer等類似工具檢視):

BEGIN TRAN

DML

COMMIT TRAN

(2) 復原(ROLLBACK),此時讀REDO LOG得到反向DML操作,反向修改髒頁,正向的DML+反向DML都會被記錄在資料庫的日志檔案中,并打上事務結束标記(ROLLBACK),同樣,髒頁有可能暫未被重新整理到資料檔案;

事務日志結構如下:

反向DML

ROLLBACK TRAN

不難發現,SQL SERVER的日志容易成為一個瓶頸(BOTTLENECK),因為在寫的同時引入了讀,即引入了競争,而ORACLE用UNDO SEGMENT很好地避免了這個問題,REDO LOG永遠隻是在被串行寫。

5. 重新整理資料頁

(1) SQL Server資料庫遵循預寫日志(WAL:Write-Ahead Logging)原則,因為關系型資料庫是基于事務的,而日志正是事務ACID屬性的保證,也是資料恢複的保證;

(2) 檢查點(CHECKPOINT),檢查點周期性地将髒頁重新整理到資料檔案中,最終在日志檔案打上檢查點标記(CHECKPOINT),至此上面事務中修改的資料被正式寫到磁盤上的資料檔案中。

二. 資料讀寫流程深入

試想:

(1) 日志是不是一定要在COMMIT後才寫到日志檔案?如果有個很長很大的事務,那麼送出日志時,日志從緩沖區被寫入磁盤,豈不是要等很久?

(2) 資料是不是一定要在日志送出後,發生了CHECKPOINT,才寫到資料檔案?如果日志一直沒送出,那麼資料緩沖區豈不是很擁擠?

考慮到這2點,SQL Server還會通過Log Writer/Lazy Writer不定時的重新整理日志/資料到磁盤,至于日志和資料的一緻性,在啟動或者資料庫還原時,SQL Server會去做檢查,也即是我們常說的前滾(REDO)和復原(UNDO)。

01. SQL Server 如何讀寫資料

資料讀寫體系結構圖

0. SQL SERVER MEMORY

(1) SQL SERVER占用伺服器記憶體的一部分,非SQL SERVER占用的記憶體,供作業系統及伺服器上其他應用程式使用;

(2) SQL SERVER記憶體對象可分為兩大類,圖中僅标出Buffer Pool中的資料及日志緩存;

1. 事務結束

(1) 事務結束的前提是日志緩存成功寫入到日志檔案中,也就是說用戶端收到COMMIT/ROLLBACK語句運作成功的消息時,日志已被成功寫入日志檔案(資料還不定是否被寫入資料檔案);

(2) 不過,日志緩存并不是一定要等到事務結束時才重新整理到日志檔案的;

2. LOG WRITER

(1) 當遇到長事務時,不必等到發出事務結束指令,LOG WRITER也會周期性地将髒日志重新整理到日志檔案,以保證使用者發出COMMIT時快速響應以結束事務;

(2) 微軟并沒有公布SQL SERVER 除去COMMIT外,LOG WRITER将髒日志重新整理到日志檔案的周期,這裡可以參考ORACLE的:每3秒,或者日志緩沖區1/3滿;或者已經包含1M的髒日志;

3. LAZY WRITER

(1) LAZY WRITER周期性掃描緩存(預設1s),維護自由頁面(free page)清單,根據LRU算法将已重新整理到磁盤的頁釋放;

(2) 如果是髒頁,則Lazy Writer将髒頁重新整理到磁盤(這時事務可能還未送出),以最終将記憶體頁釋放并加入自由頁面清單;

4. CHECKPOINT

(1) CHECKPOINT同LAZY WRITER一樣也會重新整理髒頁到資料檔案中(隻重新整理已送出的事務資料),但不會維護記憶體自由頁面清單;

(2) 可以設定SP_CONFIGURE ‘RECOVERY INTERVAL’選項來改變CHECKPOINT發生的頻率,預設為1分鐘一次。

小結:可以發現,資料和日志被寫入資料/日志檔案,并不是同步的。有可能寫入/送出了日志,資料沒有寫入磁盤;有可能寫入了資料,事務未被送出;

(1) 針對有完整事務日志,資料未被寫入磁盤的情況,啟動/還原資料庫時,SQL SERVER做前滾(REDO);

(2) 針對有資料寫入資料檔案,日志未完整送出的事務,啟動/還原資料庫時,SQL SERVER做復原(UNDO)。