3. SQL Server恢复模型与日志架构

恢复模型决定了SQL Server如何记录事务日志、日志能否被截断、以及数据库在故障后能恢复到什么时间点。理解恢复模型与日志架构,是判断“日志为什么涨、能不能收缩、故障后能恢复到哪一秒”的基础。本节围绕三种恢复模型、事务日志的物理结构(VLF)、检查点机制以及崩溃恢复过程展开。

三种恢复模型的核心差异

恢复模型本质上是“日志记录粒度”与“日志截断时机”的组合选择,它直接决定了可恢复性和日志空间压力。

恢复模型日志记录方式日志截断时机可恢复粒度典型场景
简单(SIMPLE)最小化记录(部分操作)检查点后自动截断仅到最近一次完整备份开发库、只读报表库、可重建数据
完整(FULL)所有操作完整记录日志备份后截断可恢复到任意时间点(含日志备份链)生产核心库、金融、订单系统
大容量日志(BULK_LOGGED)大容量操作最小化记录,其余完整记录日志备份后截断可恢复到任意时间点,但大容量操作区间内无法精确到点批量导入、索引重建窗口期

关键点:简单恢复模型下日志仍会增长,只是检查点后会自动截断可重用空间;如果长事务或复制/镜像等阻塞截断,日志依然会撑满。完整恢复模型下,不做日志备份,日志就永远不会被截断,这是生产库日志暴涨最常见的原因。

切换恢复模型不会立即改变日志占用:从完整切到简单后,需要一次检查点(或备份)才会截断;从简单切到完整后,必须立即做一次完整备份,否则日志链不完整,无法做日志备份。

事务日志的物理结构:VLF

事务日志在物理上是一个或多个日志文件(.ldf),逻辑上被划分为多个虚拟日志文件(VLF,Virtual Log File)。SQL Server不直接管理字节,而是以VLF为单位进行分配、截断和重用。VLF的数量和大小由日志文件的增长方式决定:

  • 日志文件初始创建时,按固定规则切分出若干VLF;
  • 每次自动增长(如按10%或固定MB),会追加一段新空间并再次切分;
  • 增长次数越多、每次增量越小,VLF数量越多、碎片越严重;
  • VLF过多会导致日志备份、恢复、截断变慢,甚至影响启动速度。

可以用未公开但稳定的系统函数查看VLF分布:

DBCC LOGINFO('YourDatabase');
-- 每行一个VLF,关注 FileSize、StartOffset、FSeqNo、Status
-- Status = 2 表示该VLF是活动日志(active),不能被截断
-- Status = 0 表示可重用

判断标准:如果DBCC LOGINFO返回几百上千行,且大量VLF很小(如256KB),说明日志碎片严重。此时应通过一次“收缩+按合理大小重新增长”来重建VLF布局,而不是反复小步增长。

检查点与日志截断的关系

检查点(CHECKPOINT)是把内存中已修改的数据页(脏页)刷写到数据文件,并在日志中记录一个已知的、所有脏页都已落盘的起点。它的作用不是“清空日志”,而是推进可截断的日志边界。

  • 简单恢复模型:检查点后,日志中早于“最小恢复LSN”的部分可被截断并重用;
  • 完整/大容量日志恢复模型:检查点只推进可截断边界,真正截断要等日志备份完成;
  • 长事务、未提交事务、复制、数据库镜像、AlwaysOn可用性组都会阻止日志截断。

因此,看到日志不收缩时,先查log_reuse_wait_desc:

SELECT name, log_reuse_wait_desc
FROM sys.databases
WHERE name = 'YourDatabase';
-- 常见值:NOTHING(可截断)、LOG_BACKUP(等日志备份)、
-- ACTIVE_TRANSACTION(长事务)、REPLICATION、AVAILABILITY_REPLICA 等

崩溃恢复过程

当SQL Server异常重启或数据库从意外状态恢复时,会经历三个阶段,这也是日志存在的根本意义:

  1. 分析阶段(Analysis):读取日志,确定哪些事务已提交、哪些未完成,构建脏页表和活动事务表。
  2. 重做阶段(Redo):把所有已提交但可能未写入数据文件的操作重新应用,保证已提交事务不丢失(Durability)。
  3. 撤销阶段(Undo):回滚未提交的事务,保证未提交数据不残留(Atomicity)。

恢复时间取决于需要重做/撤销的日志量。频繁的大事务、长时间未做检查点、日志文件过大,都会拉长恢复时间。生产库应控制单事务规模,并合理设置recovery interval(或间接检查点)以平衡恢复时间与运行时开销。

恢复模型 · VLF · 检查点 · 崩溃恢复 简单 SIMPLE 检查点后自动截断 仅恢复到最近完整备份 日志仍会增长 开发库 / 可重建数据 完整 FULL 日志备份后才截断 可恢复到任意时间点 不做日志备份=日志不截断 生产核心库 大容量日志 BULK_LOGGED 大容量操作最小化记录 日志备份后截断 大容量区间内无法精确到点 批量导入 / 索引重建窗口 事务日志物理结构(VLF) VLF1 VLF2 VLF3 VLF4 VLF5 已截断 活动日志 可重用 DBCC LOGINFO → Status=2 为活动VLF,不可截断 检查点与日志截断 脏页刷盘 CHECKPOINT 推进可截断边界 最小恢复LSN 截断 重用 完整模型下:截断需等日志备份完成 崩溃恢复三阶段 ① 分析 Analysis 读日志,确定已提交/未完成事务 ② 重做 Redo 已提交事务重新应用,保证不丢 ③ 撤销 Undo 回滚未提交事务,保证不残留

生产实践中的判断与处置

结合上述结构,遇到日志相关故障时可按以下顺序判断:

  1. 查sys.databases.log_reuse_wait_desc,确认日志为何不能截断;
  2. 若为LOG_BACKUP,说明是完整/大容量日志模型且缺少日志备份,应补日志备份而非直接收缩;
  3. 若为ACTIVE_TRANSACTION,定位长事务并评估是否可提交或回滚;
  4. 用DBCC LOGINFO检查VLF数量,若碎片严重,在业务低峰做一次日志收缩并按合理增量重建;
  5. 评估恢复模型是否匹配业务RPO:核心库必须完整恢复模型并建立日志备份链,批量窗口可临时切大容量日志以减少日志量,但完成后应切回完整并做日志备份。
不要用“收缩日志文件”作为日志暴涨的常规手段。收缩只是把已截断的空间还给操作系统,若截断条件未满足,收缩无效;若截断条件满足,收缩后日志很快又会增长,且会加剧VLF碎片。正确做法是解决截断阻塞并建立合理的日志备份策略。

理解恢复模型、VLF、检查点与崩溃恢复之间的因果链,才能在日志暴涨、恢复缓慢、无法恢复到指定时间点等故障中快速定位根因,而不是盲目收缩或重启。