SQL Server 交易記錄檔(Transaction Log)管理

從 WAL 原理到實務維運:VLF 結構、三種復原模式、截斷 vs 收縮的關鍵區別、log_reuse_wait_desc、CHECKPOINT 與記錄檔備份,以及記錄檔爆掉的排查 SOP


目錄


什麼是交易記錄檔

每個 SQL Server 資料庫都至少有兩種實體檔案:資料檔.mdf / .ndf,存放實際的資料頁)與交易記錄檔.ldf,記錄「每一筆對資料的修改」)。任何 INSERT / UPDATE / DELETE、甚至 DDL,都會先在記錄檔留下一筆 log record

核心機制是 WAL(Write-Ahead Logging,預寫式記錄)log 一定比資料頁先落地。(WAL 的通用原理、兩條規則與 ARIES 崩潰復原見 預寫日誌 WAL 完全指南,本篇聚焦 SQL Server 的具體實作與維運。)

交易要改一筆資料
  → ① 先把「這筆改動」寫成 log record,循序寫進記錄檔 .ldf(循序寫入,很快)
  → ② COMMIT 時,log record 確實 flush 到磁碟:此刻交易才算持久 (Durable)
  → ③ 被改動的資料頁(dirty page)還在記憶體,稍後才由 CHECKPOINT 批次寫回資料檔 .mdf

這樣設計換來幾件事:

用途 說明
當機復原 重啟時 redo 已 commit、undo 未 commit 的交易,保證資料庫回到一致狀態
交易回復 ROLLBACK 靠 log 把已改的東西還原回去
持久性 (Durability) commit 只需保證 log 落地,資料頁可稍後再寫,兼顧安全與效能
時間點還原 (PITR) 搭配記錄檔備份,可還原到任意時間點(需 FULL 模式)
下游同步 複寫(Replication)、CDC、鏡像、Always On 都靠讀 log 把變更送出去

正因為「commit 只要 log 落地」,記錄檔是循序寫入而非隨機寫入,這是 OLTP 高吞吐的關鍵之一。相對地,記錄檔一旦有問題(滿了、壞了),整個資料庫就寫不進去

補充:dirty page(髒頁)與 buffer pool

上面流程裡的 dirty page 是理解 log 與 checkpoint 的前提,補充三個相關名詞:

  • page(頁):SQL Server 儲存與 I/O 的最小單位,固定 8 KB;資料表與索引都以 page 為單位存放(8 個連續 page = 1 個 extent,64 KB)。
  • buffer pool(緩衝池):SQL Server 不會每次都直接讀寫磁碟,而是把 page 讀進記憶體的 buffer pool,之後的讀寫都在記憶體上進行。
  • dirty page(髒頁):buffer pool 裡已被修改、但還沒寫回磁碟資料檔(.mdf 的 page(記憶體版本比磁碟新)。相對地,記憶體與磁碟一致的叫 clean page

一次修改的完整路徑:

改一筆資料
  → 目標 page 不在記憶體,就先從 .mdf 讀進 buffer pool
  → 在記憶體裡改它:變成 dirty page(同時 log record 依 WAL 先寫進 .ldf)
  → dirty page 不立刻落地,稍後由 CHECKPOINT / lazy writer 批次寫回 .mdf
  → 寫回後變回 clean page

這樣設計:改資料只要「記憶體改 + 循序寫 log」,不必每次隨機寫資料檔,且同一 page 被改多次可合併成一次寫回;安全性則靠 WAL 兜底——就算 dirty page 還沒寫回就當機,重啟時能用 log redo 把已 commit 的變更重做回來。負責把 dirty page 寫回磁碟的角色:

角色 時機
CHECKPOINT 定期批次刷 dirty page,並在 log 記檢查點以縮短復原時間
Lazy writer 記憶體有壓力時,為騰出 buffer 空間而把 dirty page 寫回並淘汰

關鍵區分:dirty page 講的是「記憶體 vs 資料檔 .mdf」的落差,走隨機寫、延後批次刷;交易記錄檔 .ldf循序寫、commit 即落地。兩條路分工,正是 WAL 又快又安全的原因。可用 sys.dm_os_buffer_descriptorsis_modified = 1 即為 dirty)查目前的髒頁量。

為什麼「循序寫 log」是高吞吐的關鍵

重點在 循序 I/O 遠快於隨機 I/O,再加上 commit 只需等 log、不需等資料頁

  • 資料頁散落各處:一筆交易可能改到磁碟不同位置的多個 page,把它們寫回是隨機寫(每次都要 seek/旋轉定位),併發時磁頭在散落位置間來回抖動。
  • log 只往檔尾 append:所有 log record 循序寫在記錄檔結尾,連續寫入實體相鄰、幾乎免 seek。HDD 上差距是數量級;SSD 上也較快且寫入放大低。

如果沒有 WAL,要讓交易在 commit 當下就持久,得先把所有被改的資料頁(隨機位置)全部刷回磁碟——每次 commit 一堆隨機寫。有了 WAL,commit 只要把該交易的 log(檔尾、循序)flush 一次就算數,資料頁留在記憶體當 dirty page 稍後由 checkpoint 批次寫回。等於把「每筆交易多次隨機寫」換成「每筆交易一次循序 append」——這就是吞吐暴增的來源。

再疊上兩個放大效果:

機制 效果
群組提交(group commit) 高併發時多筆交易的 log 合併成一次 flush,每筆交易的 I/O 成本再被攤薄
寫入合併 同一資料頁被多筆交易改,checkpoint 只寫回一次,而非每筆交易寫一次

由於每個 committing 交易都得等自己的 log 落地(等待類型 WRITELOG),log 的寫入速度直接決定 commit 吞吐——它是提交路徑上的關鍵瓶頸,這也是實務上會把 .ldf 放在低延遲儲存的原因。同一招是通用原理:PostgreSQL 的 WAL、MySQL InnoDB 的 redo log 都靠它衝吞吐。


記錄檔的實體結構:VLF

先建立直覺:環形跑道

交易記錄檔邏輯上是一本**「一直往後寫、不回頭」的流水帳**(每筆修改 append 一筆 log record)。但實體檔案不可能無限長,所以 SQL Server 把 .ldf 當成環形跑道重複使用:寫到檔尾就繞回開頭,把那些「已經不需要的」舊記錄所佔的空間覆蓋掉。

為了判斷「哪段空間可以覆蓋了」,SQL Server 不會一筆一筆去追(太細),而是把檔案切成若干個大格子——VLF(Virtual Log File,虛擬記錄檔)以 VLF 為單位判斷可否回收。VLF 就是這條跑道上的「格子」。

寫入方向 →(填滿就繞回開頭)
┌───────┬───────┬───────┬───────┬───────┐
│ VLF 1 │ VLF 2 │ VLF 3 │ VLF 4 │ VLF 5 │
└───────┴───────┴───────┴───────┴───────┘

哪些空間「還需要」:active 與 MinLSN

一筆 log record 只要符合以下任一條件,就還「需要」、不能被覆蓋:

  • 它所屬的交易還沒結束(還可能要 rollback);
  • 還沒被 log 備份(FULL / BULK_LOGGED 模式);
  • 下游還沒讀走(複寫 / CDC 等)。

從「最舊那筆還被需要的記錄」(它的位置叫 MinLSN)到「最新一筆」之間的所有記錄,合稱 active(作用中) 區段,絕對不能覆蓋;比 MinLSN 更舊的都算 inactive(非作用中),可以回收。以上圖為例(假設 MinLSN 落在 VLF 3):

VLF 狀態 能不能覆蓋
VLF 1、VLF 2 inactive(都比 MinLSN 舊) ✅ 可重用
VLF 3、VLF 4 active(含 MinLSN 到最新的記錄) ❌ 不可覆蓋
VLF 5 尚未寫到 空的

兩個關鍵後果

  • 以整個 VLF 為單位回收:一個 VLF 裡只要還有任何一筆 active 記錄,整格就不能重用。所以一個忘了關的長交易,會把它最舊記錄所在的那格「釘住」——因為環狀是循序的、不能跳過它去用後面的格子,它後面累積的空間全都繞不回來,這就是長交易撐爆 log 的原理。
  • 繞不回去就得變大:跑道繞一圈,下一個要用的 VLF 還是 active(沒有可重用的格子),SQL Server 只好 autogrowth 在檔尾追加新的 VLF,.ldf 實體檔案因此變大。

對回前面的名詞:「截斷」的真面目就是把不再需要的 VLF 標記成 inactive/可重用(不是刪資料,是打上「可覆寫」標記);「收縮」 則是把檔尾那些空的 VLF 實體移除、還空間給 OS。

VLF 碎片化

若初始檔案太小、又用很小的 autogrowth 增量(例如每次只長 1MB / 10%),檔案會被切出成千上萬個小 VLF。VLF 過多會拖慢資料庫啟動、備份、還原與崩潰復原。

-- 查目前 VLF 數量(SQL Server 2016 SP2+ / 2017+)
SELECT COUNT(*) AS vlf_count
FROM sys.dm_db_log_info(DB_ID());
-- 舊版可用未公開指令:DBCC LOGINFO;

三種復原模式

復原模式(recovery model) 決定「記錄檔何時可以被釋放」以及「能不能做時間點還原」。這是理解記錄檔行為的總開關。

復原模式 記錄空間何時釋放 能否 log 備份 / 時間點還原 適用情境
SIMPLE CHECKPOINT 後自動截斷 ❌ 不能做 log 備份,無 PITR 開發/測試、資料可從來源重建、只需還原到上次完整/差異備份
FULL 只有在 BACKUP LOG 之後 ✅ 可還原到任意時間點 正式環境、不允許遺失資料、需要 PITR
BULK_LOGGED 同 FULL(BACKUP LOG 後) ⚠️ 大量操作期間那段無法 PITR 短暫切換,給大量匯入/索引重建降低 log 量

查目前設定:

SELECT name, recovery_model_desc
FROM sys.databases
WHERE name = 'xxx';

切換模式(注意:切走再切回 FULL 會斷掉記錄檔鏈,見後文):

ALTER DATABASE xxx SET RECOVERY SIMPLE;   -- 或 FULL / BULK_LOGGED

最常見的災難組合是:資料庫是 FULL 模式,但從來沒做過 BACKUP LOG。此時記錄檔永遠等不到釋放時機,只會一路長大直到塞爆磁碟。要嘛排定期 log 備份,要嘛(若不需要 PITR)改成 SIMPLE。

預設值:新資料庫繼承自 model

新建立的資料庫沒有寫死的固定預設值,而是繼承自 model 系統資料庫的復原模式(連初始大小、autogrowth 也一併繼承)。因此「預設是什麼」取決於該執行個體的 model 設定,而 model 的出廠值又跟 edition(版本) 有關——與作業系統(Linux / Windows)無關

Edition model 預設復原模式 → 新資料庫預設
Express / Express LocalDB SIMPLE
Standard / Enterprise / Developer / Web / Evaluation FULL

這就是為什麼同一句建庫語法在不同機器上預設不同:

  • Docker 映像mcr.microsoft.com/mssql/server)未指定 MSSQL_PID 時預設跑 Developer / Evaluationmodel 為 FULL → 新資料庫預設 FULL
  • Windows 安裝看安裝時選的版本;若裝的是免費的 Expressmodel 為 SIMPLE → 新資料庫預設 SIMPLE

查證方式:

-- 版本與 edition
SELECT SERVERPROPERTY('Edition')        AS edition,
       SERVERPROPERTY('ProductVersion') AS version;

-- model 的復原模式 = 新資料庫的預設
SELECT name, recovery_model_desc
FROM sys.databases
WHERE name = 'model';

model 改成某個模式後,之後所有新建資料庫都會跟著變(可當成統一政策的手段)。另外 tempdb 永遠是 SIMPLE 且不可更改,master / msdb 預設 SIMPLE——這些系統庫的行為與「使用者新 DB 繼承 model」是兩回事。

復原模式會影響記錄檔大小嗎?

會,但要把兩件常被混為一談的事分開看:「單筆操作寫多少 log」「log 累積成多大檔案」

一般 DML(INSERT / UPDATE / DELETE):每筆操作產生的 log 記錄量,三種模式幾乎一樣——復原模式不會讓單筆 DML 少寫 log。真正的差別在保留策略:SIMPLE 在 checkpoint 後就截斷,.ldf 通常維持得小;FULL / BULK_LOGGED 要等 BACKUP LOG 才截斷,沒做或少做備份就會一路累積、愈長愈大。所以日常負載下「log 檔多大」主要取決於模式 + 記錄檔備份頻率,不是單筆寫多少。

大量操作(BULK INSERT / bcp / SELECT INTO / 索引重建):這裡復原模式真的會改變產生的 log 量

  • SIMPLE / BULK_LOGGED:這些操作可觸發最小記錄(minimal logging)——只記錄空間配置(extent),不逐筆記,log 量大幅下降。這正是 BULK_LOGGED 存在的理由:短暫切過去跑大量匯入/建索引,壓低 log。
  • FULL:同樣操作完整記錄,逐筆寫進 log → log 量暴增。
情境 SIMPLE FULL BULK_LOGGED
單筆 DML 的 log 量 一樣 一樣 一樣
.ldf 累積大小 小(checkpoint 自動截斷) 易變大(需 log 備份才截斷) 易變大(同 FULL)
大量操作的 log 量 少(可最小記錄) 多(完整記錄) 少(可最小記錄)

一個容易忽略的陷阱:BULK_LOGGED 下雖然記錄檔本身沒暴增,但接下來的 BACKUP LOG 備份檔會很大——因為 log 備份必須把那段期間被改動的資料 extent 一併備進去,且那段期間無法時間點還原。最小記錄還需要 TABLOCK、目標表未被複寫等額外條件,詳見 SQL Server 大量寫入與 TDS


關鍵區別:截斷 vs 收縮

這是整個主題最容易搞混、也最重要的一組概念。兩者常被混為一談,但做的事完全不同:

截斷 Truncation 收縮 Shrink
做什麼 把 inactive 的 VLF 標記為「可重用」 把記錄檔實體檔案縮小,還空間給作業系統
影響 釋放檔案內部空間(可被後續 log 覆寫) 減少 .ldf 在磁碟上的實體大小
檔案大小 不變 變小
何時發生 SIMPLE:checkpoint 後自動;FULL/BULK:BACKUP LOG 只有你手動 DBCC SHRINKFILE(或開啟自動收縮,不建議)
日常角色 正常、自動、頻繁發生 例外操作,只在異常膨脹後一次性收尾

一句話記憶:

  • 截斷 = 把檔案「內部」清空出可用空間(檔案還是那麼大)。
  • 收縮 = 把「已經清空的部分」實體還給磁碟(檔案變小)。

常見誤解:「截斷記錄檔可以讓 .ldf 變小」。不會。截斷只是讓內部空間可重用;要讓實體檔案變小得靠收縮,而收縮的前提是「內部已經先被截斷空出來」。所以正確順序永遠是:先讓它可截斷(備份 / checkpoint)→ 再收縮


log_reuse_wait_desc:為什麼空間不能回收

當記錄檔一直長大、截斷不掉時,sys.databaseslog_reuse_wait_desc 欄位會直接告訴你「是誰卡住了記錄空間的回收」。這是排查記錄檔問題的第一線索

SELECT name, recovery_model_desc, log_reuse_wait_desc
FROM sys.databases
WHERE name = 'xxx';

常見值與對策:

log_reuse_wait_desc 意思 對策
NOTHING 沒有東西擋著,空間可正常回收 正常,無需處理
CHECKPOINT 等待下一次 checkpoint(通常短暫) 多為暫時性;必要時手動 CHECKPOINT
LOG_BACKUP FULL/BULK_LOGGED 下,等一次記錄檔備份 最常見:執行 BACKUP LOG,並排定期 log 備份
ACTIVE_TRANSACTION 有長時間未 commit 的交易卡住 MinLSN 找出並處理長交易(見診斷段)
ACTIVE_BACKUP_OR_RESTORE 正在進行備份/還原 等它完成
REPLICATION 交易尚未送到散發者(複寫/CDC) 檢查複寫/CDC 是否落後或停擺
AVAILABILITY_REPLICA Always On 次要複本同步落後 檢查 AG 健康度與次要複本
DATABASE_MIRRORING 鏡像同步落後 檢查鏡像狀態
OLDEST_PAGE 間接檢查點的最舊頁面所致 與 indirect checkpoint 設定有關

排查心法:先看 log_reuse_wait_desc 找根因,再決定手段。看到 LOG_BACKUP 卻只顧著 SHRINKFILE,是治標不治本——備份沒做,收縮完馬上又長回來。


記錄檔與複寫 / CDC / 下游消費者

交易記錄檔不只給資料庫自己復原用,它同時是許多下游功能的資料來源——它們都靠「讀 log」把變更送出去,因此都會反過來影響記錄檔何時能截斷。可以把記錄檔想成「單一事實來源」,而復原、回復、複寫、CDC、AG、鏡像都是它的下游消費者;任何一個消費者落後,都會卡住截斷。

交易式複寫是「log 讀取者」

交易式複寫(transactional replication)的 Log Reader Agent(記錄讀取器) 會掃描發行資料庫的交易記錄檔,把標記為要複寫的交易複製到散發資料庫(distribution DB),再送給訂閱端。所以複寫本質上是在「消費 log」。

關鍵後果:被標記待複寫的 log 記錄,在 Log Reader Agent 讀走之前都算 active,不能截斷。 於是一旦複寫停擺、Log Reader Agent 沒在跑、或散發端塞住,記錄檔就會一直長大,log_reuse_wait_desc 顯示 REPLICATION

陷阱:SIMPLE 模式也會因此爆 log

交易式複寫支援任意復原模式(不強制 FULL),而待複寫的 log 是跨復原模式受保護的。因此即使是 SIMPLE 模式,checkpoint 也清不掉那些還沒被 log reader 讀走的記錄——這會讓「SIMPLE 就不會爆 log」的直覺失效,是很常見的踩雷點。

哪些複寫類型會 hold 住 log

類型 是否靠讀 log 會不會 hold 住記錄檔
交易式複寫 / 對等式(P2P) ✅ Log Reader Agent
CDC(變更資料擷取) ✅ 同一套 log reader
快照式複寫(snapshot) ❌ 大量快照複製,不持續讀 log 不會
合併式複寫(merge) ❌ 靠觸發程序 + 系統表 不會

CDC 用的是與複寫相同的 log reader 機制,所以就算沒設定任何複寫,只要開了 CDC,log_reuse_wait_desc 一樣可能是 REPLICATION,行為與交易式複寫相同。

其他也吃 log 的下游功能

複寫不是唯一讀 log 的功能。以下也一樣靠 log 傳遞變更,但用不同的 log_reuse_wait_desc 值,且對復原模式要求不同:

功能 log_reuse_wait_desc 復原模式要求
交易式複寫 / CDC REPLICATION 任意(含 SIMPLE)
Always On 可用性群組 AVAILABILITY_REPLICA 必須 FULL
資料庫鏡像 DATABASE_MIRRORING 必須 FULL
記錄傳送(log shipping) BACKUP LOG FULL / BULK_LOGGED

排查:懷疑是複寫卡住 log

-- 1) 確認就是複寫在卡(值為 REPLICATION 就往下查)
SELECT name, log_reuse_wait_desc
FROM sys.databases
WHERE name = 'xxx';

-- 2) 看複寫延遲與待散發的量、Log Reader Agent 進度
EXEC sp_replcounters;
-- 或到 distribution DB 查 undistributed commands、用 Replication Monitor 看 Log Reader Agent 狀態

-- 3) 已移除複寫卻仍卡 REPLICATION(殘留的孤兒中繼資料)
EXEC sp_removedbreplication 'xxx';   -- 清掉殘留複寫設定(確認真的不用了再執行)
-- 若是 CDC:EXEC sys.sp_cdc_disable_db;

治本是「讓 log reader 正常消化」——把 Log Reader Agent 修好、讓散發端跟上;而不是去收縮記錄檔。log reader 沒動,收縮完馬上又長回來。


CHECKPOINT 在做什麼

CHECKPOINT 把記憶體裡的 dirty page(改過但還沒寫回磁碟的資料頁)批次寫回資料檔,並在記錄檔寫下一個檢查點標記。

它的兩個作用:

  1. 縮短當機復原時間:復原時的 redo 只需從「最後一個檢查點」開始掃,而不是從頭掃整個 log。
  2. 在 SIMPLE 模式下觸發截斷:checkpoint 完成後,不再需要的 log 會被標記成可重用(active 記錄之外的部分)。
CHECKPOINT;        -- 手動強制一次檢查點

觸發來源:

類型 觸發時機
自動 (automatic) recovery interval 伺服器設定,SQL Server 自行判斷
間接 (indirect) 依資料庫的 TARGET_RECOVERY_TIME,把 dirty page 持續刷出以壓低復原時間(2016+ 預設)
手動 (manual) 執行 CHECKPOINT 陳述式
內部 (internal) 備份、ALTER DATABASE 等操作前系統自動下

注意:在 FULL / BULK_LOGGED 模式,CHECKPOINT 不會截斷記錄檔——那裡的截斷條件是「做過 BACKUP LOG」。所以在 FULL 模式對著爆掉的 log 猛下 CHECKPOINT 是沒用的。


記錄檔備份與記錄檔鏈

FULL / BULK_LOGGED 模式,只有 BACKUP LOG 能釋放記錄空間。它做兩件事:把自上次以來的 active log 記錄備份出去,然後截斷已不需要的部分。

BACKUP LOG xxx
TO DISK = N'D:\backup\xxx_log_20260702_1200.trn';

記錄檔鏈(log chain) 是一連串「完整備份 → 一連串 log 備份」的連續序列,時間點還原就是靠它把資料庫還原到某個精確時刻:

完整備份 (Full)
  → log 備份#1 → log 備份#2 → log 備份#3 → ...
                 (沿著這條鏈套用,就能還原到任一時間點)

斷鏈的動作要特別小心(斷鏈後必須立刻重做一次完整或差異備份,PITR 才能接續):

  • 把復原模式切成 SIMPLE(即使又切回 FULL,中間那段鏈已斷)。
  • BACKUP LOG ... WITH NO_LOG / TRUNCATE_ONLY(舊版才有,且已棄用)強制截斷。

正確做法:想控制 FULL 模式下的記錄檔大小,答案幾乎永遠是「排定期 BACKUP LOG」(例如每 15 分鐘一次),而不是去收縮它。定期 log 備份同時達成兩件事:控制 log 大小 + 保住 PITR 能力。


收縮記錄檔 DBCC SHRINKFILE

DBCC SHRINKFILE 把指定檔案的實體大小縮小,把已截斷的空閒空間還給作業系統。它只能收回已經是「可重用」的那部分,所以收縮前必須先讓記錄檔可截斷。

-- 語法:DBCC SHRINKFILE ( { 邏輯檔名 | file_id }, 目標大小_MB )
DBCC SHRINKFILE (N'xxx_log', 512);   -- 縮到約 512 MB

參數細節:

  • 第一個參數是邏輯檔名(logical name),不是實體 .ldf 路徑。N 前綴代表 Unicode 字串。
  • 第二個參數單位是 MB,代表「想縮到的目標大小」。
  • 查邏輯檔名對照:
SELECT name AS logical_name, type_desc, physical_name,
       size * 8 / 1024 AS size_mb
FROM sys.database_files;   -- LOG 那列的 name 就是要填的邏輯檔名

常見卡點:收縮不動 / 只縮一點點。 原因是 active 記錄剛好落在檔案實體尾端的 VLF——尾端被佔著就無法把檔案縮短。解法是先讓 active 位置往前移,再收縮第二次:

-- FULL 模式
BACKUP LOG xxx TO DISK = N'...';   -- 釋放並讓 active VLF 往前挪
DBCC SHRINKFILE (N'xxx_log', 512); -- 再收一次

-- SIMPLE 模式
CHECKPOINT;
DBCC SHRINKFILE (N'xxx_log', 512);

⚠️ 收縮是反模式,別排成定期維護作業。 記錄檔縮小後,遇到大交易又會 autogrow 長回來,一縮一長會造成 VLF 碎片化與實體檔案碎片,反而拖慢啟動、備份與還原。收縮只該當作「異常膨脹後的一次性收尾」,縮完就設一個合理大小與 autogrowth,別再動它。同理,資料庫層級的自動收縮(AUTO_SHRINK)也應關閉


診斷指令

一組平時/出事時常用的查詢:

-- 1) 復原模式 + 為什麼截斷不掉(第一線索)
SELECT name, recovery_model_desc, log_reuse_wait_desc
FROM sys.databases
WHERE name = 'xxx';

-- 2) 目前資料庫的記錄檔用量(總大小、已用位元組、已用百分比)
SELECT * FROM sys.dm_db_log_space_usage;   -- 2012+,只回目前 DB

-- 3) 一次看所有資料庫的 log 大小與使用率
DBCC SQLPERF(LOGSPACE);

-- 4) VLF 數量(碎片化指標)
SELECT COUNT(*) AS vlf_count FROM sys.dm_db_log_info(DB_ID());

-- 5) 找出卡住記錄回收的長交易(log_reuse_wait_desc = ACTIVE_TRANSACTION 時)
DBCC OPENTRAN('xxx');
-- 或用 DMV 看誰交易開最久
SELECT s.session_id, s.login_name, s.host_name,
       t.transaction_id, t.transaction_begin_time
FROM sys.dm_tran_active_transactions t
JOIN sys.dm_tran_session_transactions st ON st.transaction_id = t.transaction_id
JOIN sys.dm_exec_sessions s ON s.session_id = st.session_id
ORDER BY t.transaction_begin_time;

-- 6) 記錄檔的實體檔案資訊(邏輯名、路徑、大小、autogrowth)
SELECT name AS logical_name, physical_name,
       size * 8 / 1024 AS size_mb,
       CASE is_percent_growth WHEN 1 THEN CONCAT(growth, ' %')
            ELSE CONCAT(growth * 8 / 1024, ' MB') END AS autogrowth
FROM sys.database_files
WHERE type_desc = 'LOG';

實戰:記錄檔爆掉的排查流程

以最常見情境「某資料庫的 .ldf 異常變大、快塞爆磁碟」為例,把前面的觀念串成一套 SOP:

① 診斷:查復原模式 + log_reuse_wait_desc(先搞清楚根因)
  → ② 依根因處理(讓記錄檔變成「可截斷」狀態)
     → ③ 讓內部空間釋放(BACKUP LOG 或 CHECKPOINT)
        → ④ 一次性收縮實體檔案,還空間給 OS
           → ⑤ 設定合理大小與 autogrowth,避免再犯

對照本筆記開頭那組指令,它其實就是「SIMPLE 模式下的收尾三步」:

-- ① 診斷:這個 DB 是什麼模式?為什麼截斷不掉?
SELECT recovery_model_desc, log_reuse_wait_desc
FROM sys.databases
WHERE name = 'xxx';
-- 假設看到 recovery_model_desc = SIMPLE、log_reuse_wait_desc = NOTHING/CHECKPOINT

-- ② + ③ 強制一次檢查點:SIMPLE 模式下觸發截斷,把內部空間釋放出來
CHECKPOINT;

-- ④ 一次性收縮:把已釋放的空間實體還給磁碟
DBCC SHRINKFILE (N'xxx_log', 512);

不同根因對應不同的第 ②③ 步:

診斷結果 處理
SIMPLE + NOTHING/CHECKPOINT CHECKPOINT; 後即可收縮(上例)
FULL + LOG_BACKUP BACKUP LOG(治本應排定期備份),再視需要收縮一次
任何模式 + ACTIVE_TRANSACTION 先用 DBCC OPENTRAN 找出長交易並結束它,log 才回收得掉
FULL + REPLICATION 檢查複寫/CDC 是否落後或停擺,讓它把 log 消化掉

若已經跳出 error 9002(交易記錄檔已滿):緊急處理是「先讓 log 可截斷」——FULL 模式立刻 BACKUP LOG(若磁碟真的滿了,可備份到另一顆磁碟或臨時加一個 log 檔);SIMPLE 模式則 CHECKPOINT。切忌直接刪 .ldf,那會讓資料庫無法啟動。


最佳實踐

  1. 選對復原模式:需要 PITR 才用 FULL;不需要就用 SIMPLE。別預設 FULL 卻從不做 log 備份。
  2. FULL 模式一定排定期 BACKUP LOG:這是控制記錄檔大小的正解,同時保住 PITR。
  3. 別把收縮當維護作業:只在異常膨脹後一次性收尾,並關閉 AUTO_SHRINK
  4. 一開始就設足夠的初始大小:給 .ldf 合理初始大小,避免上線後狂 autogrow 長出一堆小 VLF。
  5. autogrowth 用固定 MB 增量:例如 512MB / 1GB,不要用百分比、也別用太小的 1MB。
  6. 監控 log 使用率sys.dm_db_log_space_usage / DBCC SQLPERF(LOGSPACE) 納入日常監控,別等塞爆才處理。
  7. 切模式要小心斷鏈:SIMPLE ⇄ FULL 之間切換後,回到 FULL 要立刻做一次完整或差異備份,重建記錄檔鏈。

常見問題

問題 1:截斷記錄檔會讓 .ldf 檔案變小嗎?

不會。截斷只釋放檔案內部的可重用空間;要讓實體檔案變小得用 DBCC SHRINKFILE(收縮),且收縮前必須先截斷空出空間。兩者是不同動作。

問題 2:為什麼我的記錄檔一直長大、縮不掉?

log_reuse_wait_desc。絕大多數是 FULL 模式沒做 BACKUP LOG(值為 LOG_BACKUP),其次是長交易(ACTIVE_TRANSACTION)或複寫落後(REPLICATION)。對症處理,別只顧收縮。

問題 3:可以直接刪掉 .ldf 檔案嗎?

不行。記錄檔是資料庫一致性的一部分,直接刪會導致資料庫無法啟動(進入 suspect / recovery pending)。要縮小請用收縮,不是刪檔。

問題 4:收縮後記錄檔又長回原本大小?

代表你把它縮得比實際工作負載需要的還小,或 autogrowth 設定不當。找出正常運作所需的大小,設成初始大小,並用合理的固定 MB autogrowth,就別再收縮它。

問題 5:CHECKPOINT 為什麼在 FULL 模式沒把 log 清掉?

FULL / BULK_LOGGED 模式的截斷條件是「做過 BACKUP LOG」,不是 checkpoint。CHECKPOINT 只在 SIMPLE 模式才會順帶觸發截斷。

問題 6:SIMPLE 復原模式可以做複寫嗎?

可以。交易式、快照式、合併式複寫都支援任意復原模式,包含 SIMPLE——複寫不強制 FULL(強制 FULL 的是 Always On 可用性群組與資料庫鏡像)。SIMPLE 下待複寫的 log 一樣會被 hold 到 Log Reader Agent 讀走(log_reuse_wait_desc = REPLICATION),讀走後才在 checkpoint 截斷。差別在代價:SIMPLE 沒有記錄檔備份鏈、無法時間點還原,publisher 若需從舊備份還原,較可能得重新初始化訂閱。因此正式環境的 publisher 仍常選 FULL,但那是為了復原能力,不是複寫的硬性要求。

問題 7:這些指令別的資料庫也能用嗎?

不能。sys.databasesrecovery_model_desclog_reuse_wait_desc、獨立的 CHECKPOINT 陳述式、DBCC SHRINKFILE 都是 SQL Server(T-SQL)專屬語法。概念(交易記錄、檢查點、記錄檔管理)各家資料庫相通,但 PostgreSQL 的 WAL、MySQL InnoDB 的 redo log、Oracle 的 redo/archive log 各有自己的機制與指令。


總結與速查表

核心要點

  • 交易記錄檔靠 WAL(log 先於資料頁落地)保證持久性與可復原性。
  • .ldf 內部由 VLF 組成,環狀重複使用;active 記錄不能被覆寫。
  • 復原模式是總開關:SIMPLE(checkpoint 自動截斷)、FULL(BACKUP LOG 才截斷、可 PITR)、BULK_LOGGED(給大量操作降 log 量)。
  • 截斷 ≠ 收縮:截斷釋放內部空間(檔案不變小、且自動頻繁發生),收縮縮小實體檔案(例外操作,別排定期)。
  • 出事先看 log_reuse_wait_desc 找根因,再決定 BACKUP LOG / CHECKPOINT / 處理長交易,最後才視需要收縮一次。

指令速查

目的 指令
查模式 + 截斷卡在哪 SELECT recovery_model_desc, log_reuse_wait_desc FROM sys.databases WHERE name='xxx';
查記錄檔用量(目前 DB) SELECT * FROM sys.dm_db_log_space_usage;
查所有 DB 的 log 使用率 DBCC SQLPERF(LOGSPACE);
查 VLF 數量 SELECT COUNT(*) FROM sys.dm_db_log_info(DB_ID());
找長交易 DBCC OPENTRAN('xxx');
記錄檔備份(FULL/BULK) BACKUP LOG xxx TO DISK = N'...';
強制檢查點(SIMPLE 截斷) CHECKPOINT;
收縮記錄檔 DBCC SHRINKFILE (N'xxx_log', 512);
切復原模式 ALTER DATABASE xxx SET RECOVERY SIMPLE|FULL|BULK_LOGGED;

快速記憶

方面 說明
是什麼 記錄每筆修改的 .ldf,靠 WAL 保證持久與可復原
為什麼會爆 FULL 模式沒做 log 備份 / 長交易 / 下游同步落後
怎麼查 log_reuse_wait_desc 一眼看出誰卡住回收
怎麼救 依根因 BACKUP LOGCHECKPOINT → 需要時收縮一次 → 設好大小別再動

建立日期:2026-07-02

🔗相關文章