一個簡單的更新查詢
現在應該知道只讀取數據的查詢生命周期,下一步來認定當你需要更新數據時會發生什么。這個部分通過看一個簡單的UPDATE查詢,修改剛才例子里讀取的數據,來回答。
慶幸的是,直到存取方法(Access Methods)前,更新操作和剛才SELECT語句流程是一模一樣的。
這次存取方法(Access Methods)需要修改數據,因此在I/O請求傳遞前,修改的細節要存放於硬盤。這個就是事務管理器(Transaction Manager)的工作。
事務管理器(Transaction Manager)
事務管理器(Transaction Manger)這里有2個有趣的組件:鎖管理器(Lock Manager)和日志管理器(Log Manager)。鎖管理器(Lock Manager)為數據提供並發性負責,它通過使用鎖傳遞配置的隔離級別。
備注:
在剛才提到的SELECT查詢生命周期里,鎖管理器(Lock Manager)也有用到,這里繼續談的話會岔開話題,這里它被提到因為它是事務管理器(Transaction Manager)的一部分。
這里真正有趣的東西是日志管理器(Log Manager),存取方法(Access Methods)代碼里想要做出改變的請求被記錄,日志管理器(Log Manager)把這些改變寫到事務日志(transaction log),這個就是預寫式日志(Write-Ahead Logging:WAL)。
寫入事務日志(transaction log)是數據修改事務的一部分,它總是需要物理寫入硬盤,因為即使在系統崩潰的時候,SQL Server可以靠它來重讀那些改變(在接下來的還原章節你會學到這個更多)。
在事務日志(transaction log)里實際存放的並不是修改語句清單,而是修改語句的結果造成頁面變更的細節。這是SQL Server為了可以撤銷修改,這也讓事務日志(transaction log)內容很難讀懂,當然你可以借助第三方工具來幫忙。
回到UPDATE查詢生命周期,更新操作已經被寫到日志。當事務日志已經確認物理寫入后,實際的數據才會修改。這也是為什么事務日志(transaction log)操作重要。
一旦存取方法(Access Methods)收到確認,它把修改請求發給緩沖區管理器(Buffer Manager)來完成。
事務管理器(Transaction Manager),存取方法(Access Methods),記錄我們更新的事務日志(transaction log),完成數據修改請求的緩沖區管理器(Buffer Manager)。
緩存區管理器(Buffer Manager)
需要修改的頁已經在緩存里了,緩存區管理器(Buffer Manager)要做的只是修改需要的頁, 這個更新請求由存取方法(Access Methods)發起。在緩存中的頁被修改后,確認會發回給存取方法(Access Methods),最后發回給客戶端。
這里的關鍵點(也是最大的)是UPDATE語句只改變數據緩存里的數據,並不是在磁盤上的實際數據庫文件。這樣做是基於性能的原因,現在這個也被稱為所謂的臟頁(Dirty Page),因為它和硬盤上對應頁是不一樣的。
這與ACID屬性里定義的修改耐久性(durability of the modification)並不違背,因為你可以使用事務日志重建改變。舉例來說,如果你服務器突然斷電,物理內存(例如數據緩存區)里就啥都沒有了。臟頁是如何並在什么時候寫回數據庫文件在下一章節會介紹。
更新操作的生命周期如上圖所示。緩沖區管理器(Buffer Manager)改變緩存中頁的內容,並發送確認給存取方法(Access Methods)。可以看到,在此期間,數據文件一直沒被訪問。
還原(Recovery)
在上一章節,你讀到了UPDATE查詢的生命周期,里面談到了SQL Server使用預寫式日志(Write-Ahead Logging:WAL)方法來保持任何更改的耐久性(durability of any changes)。
變更首先寫入事務日志(transaction log),然后只停留在內存里。這樣做是基於性能原因並使你需要撤銷的話可以從事務日志(transaction log)里還原。在還原章節會介紹更多的相關新概念和流程(new concepts and terminology)。
臟頁(Dirty Pages)
從磁盤讀回內存的頁別標識為干凈頁(clean page)因為它和它的副本是一樣的。同樣,一旦在內存里的頁被修改會被標識為臟頁(Dirty Page)。
使用清空緩存(DBCC DROPCLEANBUFFERS)可以從緩存里清掉干凈頁(Clean pages)(注:從緩沖池中刪除所有緩沖區。),當你對開發和測試環境進行故障排除時非常方便,因為它強制從磁盤后續讀取來實現,不是緩存,不接觸任何臟頁。
臟頁(dirty page)就是自硬盤加載到內存有改變且現在和磁盤上不一樣。用下面的動態視圖可以看每個數據庫有多少臟頁。
1 SELECT db_name(database_id) AS 'Database',count(page_id) AS 'Dirty Pages' 2 FROM sys.dm_os_buffer_descriptors 3 WHERE is_modified =1 4 GROUP BY db_name(database_id) 5 ORDER BY count(page_id) DESC
Database Dirty Pages
People 2524
Tempdb 61
Master 1
如上表示在People數據庫有20M的臟頁(2524 * 8 / 1024).
每當緩存空閑不足或檢查點(checkpoint)發生時,這些臟頁(dirty page)會被定期寫回到數據庫。為了更快的分配頁,SQL Server總是在緩存里保持一定數量的可用空頁,這些可用空頁在可用緩存列表里被跟蹤。
當一個工作線程(worker thread)發起一個讀請求時,它在緩存拿到64頁的列表並檢查這個緩存列表是否低於特定的閾值(threshold)。如果是的話,會把列表里的頁標記為過期(age-out),這會引起把任何臟頁(dirty page)寫回硬盤。另外一個稱為惰性寫入器(Lazy Writer)的線程也是基於空閑緩存列表(free buffer list)不足。
惰性寫入器(Lazy Writer)
惰性寫入器(Lazy Writer)會定期檢查空閑緩存列表(free buffer list)的大小。當值低的時候,它會掃描整個數據緩存把有段時間沒用過的頁標記為過期(age-out),在內存里標記它們為空閑前會寫回硬盤。
惰性寫入器(Lazy Writer)也會在服務器上監控可用物理內存,在內存非常不足的情況下會把空閑緩存列表(free buffer list)的內存釋放回給系統。當SQL Server 很忙的時候,在還有可用物理內存和沒到服務器最大內存配置閾值時,它會增大空閑緩存列表(free buffer list)的大小來滿足(緩沖池(Buffer Pool)的)要求。
檢查點過程(Checkpoint Process)
檢查點(Check Point)是個SQL Server創建的時間點,用來保證任何提交的事務已經將它們的變更寫回硬盤。檢查點(Check point)成為數據庫可以開始的還原點。
檢查點過程(Check Point Process)用來保證已提交的事務相關的所有臟頁(dirty page)已經寫回硬盤。為了有效使用寫入器,它也會把未提交的臟頁(dirty page)也寫回硬盤,不像惰性寫入器(Lazy Writer),檢查點(Check Point)不會從緩存中移除頁;它只把臟頁(dirty page)寫回硬盤並在緩存頁的頁頭將緩存里頁標記為干凈。
默認情況下,在一個忙碌的服務器里,SQL Server會在每分鍾發起一次檢查點(Check Point),這會標記在事務日志里。如果SQL Server實例或數據庫重啟了,還原過程會讀取日志來獲知,自上一個檢查點后的日志里不需要進行任何操作。
日志序列號(Log Sequence Number:LSN)
日志序列號(Log Sequence Number:LSN)在事務日志里標識記錄,它被排序的,因此SQL Server可以知道事件發生的順序。
像進行前滾或后滾的還原前,最小的LSN號會被拿到。考慮這個不僅是檢查點(Check Point)日志序列號(Log Sequence Number:LSN),還有其他更重要的。這就是說還原還需要擔心在檢查點(Check point)前,是否有臟頁(dirty page)還沒寫回硬盤。這個在有大量數目臟頁(dirty page)的大系統里會發生。
因此檢查點(Check Point)之間的時間內,代表着大量的工作需要處理來,在上一個檢查點(Check Point)發生后,前滾任何提交的事務,或后滾任何沒有提交的事務。通過每分鍾的檢查點(Check Point),SQL Server嘗試保證還原時間自一個數據庫開始少於1分鍾,在此期間它不會自動執行檢查點(Check Point),除非有10MB的日志寫入。
檢查點(Check Point)可以通過CHECK POINT的T-SQL命令人為執行,也可以由SQL Server里的其它事件來觸發。例如,你發起一個備份命令時,檢查點(Check Point)會首先執行。
跟蹤號(trace flag)3502是檢查點(Check Point)開始和結束的錯誤號。例如,在自啟動后添加剛才的跟蹤號,在執行一系列的大量寫入后,我們在錯誤日志里可以看到如下的條目,可以看到檢查點(Check Point)在30-40秒之間執行一次。
跟蹤號(trace flag)提供改變SQL Server行為的一種途徑,通常幫助我們進行故障排除或者出於測試目的啟用或停用特定的功能。有幾百個跟蹤號(trace flag)存在但官方只公開部分;點擊查看它的公開列表還有如何使用它:
恢復間歇(Recovery Interval)
恢復間歇(Recovery Interval)是個服務器配置選項,可以用來調整檢查點(Check Point)間的時間差,因此可以設置自開始多少時間內的數據庫可以還原,恢復間歇(Recovery Interval)。
默認情況下,恢復間歇(Recovery Interval)設置為0;這會啟用SQL Server選擇一個合適的間歇(Interval),通常是接近於1分鍾自動執行一次檢查點(Check Point)。
改變這個值為大於0時,代表你希望在檢查點(Check Point)的之間的間歇時間大小。大多數情況下不沒必要修改,與還原時間比,你更在乎檢查點(Check Point)過程,你來決定是否設置。
恢復間歇(Recovery Interval)只在測試和實驗環境下才配置,為了有效的停止自動檢查點(Check Point),出於監控東西的目的或獲取更好的性能,可以配置出奇很高值。除非你為了SQL Server趕上世界記錄速度,你不應該在真實生產環境里修改這個值。
為了停止對磁盤子系統的太多影響,SQL Server甚至會抑制檢查點的I/O,因此它很會進行自我管理。如果你在服務器上曾看到SLEEP_BPOOL_FLUSH的等待類型,這是因為SQL Server為了保持全局系統的性能進行了檢查點的I/O抑制。
還原方式(Recovery Models)
SQL Server有3種數據庫還原方式(Recovery Models):完整(Full),批日志(Bulk-Logged)和簡單(Simple)。你選擇的方式會影響到你事務日志的使用方式,它增長到多大,你的備份策略,還有你的還原選項。
完整(Full)
使用完整(Full)還原方式,會要求所有操作已經在事務日志里完整寫入,在備份策略里要求包含完整(Full)備份和事務日志(transaction log)備份。
自SQL Server 2005開始,完整(Full)備份不清空(truncate)事務日志(transaction log)。這樣做的話事務日志備份的序列不會被損壞,在你完整備份損壞的情況提供一個額外的還原選項。
SQL Server數據庫如果需要更高級別的還原性,你應該使用完整(Full)還原方式。
批日志(Bulk-Logged)
這是個很特殊的還原方式,想這樣做的話,是通過最小量的日志寫入,來提高特定批量操作的性能。其他操作會和完整(FULL)還原方式一樣完全寫入日志。因為只有回滾事務需要的信息被寫入日志,這個方式可以提高性能。重做信息沒有寫入日志,這也表示你失去了基於時間點的還原(point-in-time-recovery)。
這些批日志包括:
- 批量插入(BULK INSERT)
- 使用可執行的BCP(Using the bcp executable)
- SELECT INTO
- 創建索引(CREATE INDEX)
- 修改索引重建(ALTER INDEX REBUILD)
- 刪除索引(DROP INDEX)
批日志(Bulk-Logged)和事務日志(Transaction Log)備份
使用批日志(Bulk-Logged)模式是想讓你的批日志(Bulk-Logged)操作更快完成。它不會為你的事務日志(transaction log)備份減少磁盤空間需求。
簡單(Simple)
如果在數據庫上設置了簡單(Simple)還原,,每次檢查點(Check Point)里發生的事務日志里,所有提交的事務都會被清空(truncate)。這是為了保證日志大小保持在最小,並且不需要事務日志(transaction log)備份(如果沒有的話),是好是壞看你對一個數據庫的還原級別要求。
如果自上次的完整(FULL)或差異(differential)備份起潛在丟失的所有變更,仍滿足你的業務需要的話,你可以選擇簡單(Simple)還原。