数据库基础体系 · 第 49/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。

SQL Server 高可用与运维:Backup、Always On、监控和故障恢复

SQL Server 的高可用与运维不能只理解为“配置一个副本,再定期执行 BACKUP DATABASE”。一个可恢复的系统至少要回答四个问题:

  1. 数据如何被持久化和复制?
  2. 故障发生时,客户端如何切换到可用副本?
  3. 如何知道副本、日志和备份是否真的健康?
  4. 发生误删、逻辑错误、存储损坏或整个站点故障时,如何恢复到目标时间点?

本文以现代受支持版本的 SQL Server Database Engine 公开语义为基础,重点讨论:

  • 数据库备份、事务日志备份与恢复链;
  • 全量、差异、PITR、RPO/RTO 和恢复演练;
  • Always On Availability Groups(可用性组)的组件、数据流、同步与故障转移;
  • SQL Server Failover Cluster Instance(FCI)、Log Shipping 与 Availability Groups 的边界;
  • 备份、Always On、事务日志和监控之间的关系;
  • 故障诊断、计划内切换、强制故障转移和灾难恢复。

一、先建立三个边界:备份、复制和故障转移不是同一件事

1. 备份解决“回到过去”

备份是某个时刻数据库状态或事务日志的持久化副本。它主要用于:

  • 误删数据后的时间点恢复;
  • 数据库文件或存储损坏后的恢复;
  • 逻辑错误传播后的回滚;
  • 迁移、测试和恢复演练;
  • 灾难后在新服务器重建数据库。

备份通常不能让正在运行的连接无感知地切换到另一台服务器。

2. 复制解决“把变化送到另一处”

Always On Availability Groups、数据库镜像和 Log Shipping 都会把数据变化传送到其他副本或服务器,但复制行为不同:

  • Always On AG 复制事务日志记录;
  • Log Shipping 周期性备份、复制并还原事务日志;
  • FCI 通常由多个节点共享同一组存储,实例在节点之间切换,而不是维护独立数据库副本。

复制延迟、存储损坏、误操作和逻辑错误都可能被带到副本上。因此:

Always On 不能替代备份,备份也不能替代高可用。

3. 故障转移解决“改变服务入口”

故障转移把客户端从故障实例切换到另一个可服务实例。它解决的是服务连续性问题,但是否丢数据取决于:

  • 事务日志是否已经传到目标副本;
  • 目标副本是否已经将日志硬化到持久存储;
  • 是计划内切换还是强制切换;
  • 是否允许在不确定数据完整性的情况下接管服务。

二、必须先理解 SQL Server 的事务日志

1. 事务日志记录什么

SQL Server 使用事务日志记录数据库修改,例如:

  • 行插入、更新和删除;
  • 页面分配和释放;
  • 索引操作;
  • 事务提交和回滚;
  • 数据库恢复所需的内部操作。

日志记录按逻辑顺序分配 Log Sequence Number(LSN)。LSN 可以看作日志流中的单调位置。事务提交时,提交相关的日志记录必须先写入日志文件并满足持久性要求,SQL Server 才能向客户端确认提交成功。

数据库数据页可以晚于事务提交写入数据文件,因为崩溃恢复时可以利用日志重做或回滚操作。

2. 恢复模型决定日志能否连续备份

SQL Server 有三种恢复模型:

恢复模型 是否支持事务日志备份 能否通常进行时间点恢复
Simple
Full
Bulk-logged 有条件

FULL 恢复模型并不是“自动生成日志备份”。如果长期不执行日志备份,日志截断可能无法推进,日志文件会不断增长。

可以先检查恢复模型和日志复用等待原因:

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

典型结果可能是:

name       recovery_model_desc  log_reuse_wait_desc
---------  -------------------- -------------------
Inventory  FULL                  LOG_BACKUP

这表示数据库处于完整恢复模型,但缺少事务日志备份。解决方法不是盲目扩大日志文件,而是建立日志备份计划,并调查为什么日志无法截断。

在 Always On 环境中,还可能看到:

AVAILABILITY_REPLICA

这表示某个可用性副本仍需要日志,主库不能仅因为本地已经完成检查点就截断相关日志。

3. LSN 是恢复链的核心

一次典型的恢复链如下:

全量备份 F0
    └── 差异备份 D1
          └── 日志备份 L1 ── L2 ── L3

差异备份记录的是自某个全量备份以来发生变化的数据页。日志备份记录的是日志序列中的连续区间。

如果恢复了 F0,可以选择:

  • 直接恢复 F0
  • 恢复 F0 后再恢复 D1,跳过 L1 之前已经包含在差异中的日志;
  • 恢复 F0,再依次恢复 L1L2L3

不能在恢复 L1 后再恢复 D1,因为 D1 的内容已经基于 F0 形成了另一条恢复路径。


三、Backup:全量、差异、日志和 Copy-only

1. 全量备份

全量备份包含恢复数据库所需的完整数据内容,并包含恢复所需的日志信息。它是恢复链的基础。

BACKUP DATABASE [Inventory]
TO DISK = N'/var/opt/mssql/backup/Inventory_full.bak'
WITH
    INIT,
    COMPRESSION,
    CHECKSUM,
    STATS = 10;

这里假定:

  • SQL Server 运行在 Linux;
  • SQL Server 服务账户能够写入 /var/opt/mssql/backup
  • 该目录已经存在;
  • 目标磁盘有足够空间。

Windows 示例可以使用:

BACKUP DATABASE [Inventory]
TO DISK = N'C:\SqlBackups\Inventory_full.bak'
WITH
    INIT,
    COMPRESSION,
    CHECKSUM,
    STATS = 10;

关键选项含义:

  • INIT:覆盖备份介质中的现有备份集。生产环境必须确认不会误覆盖仍需保留的备份。
  • COMPRESSION:请求备份压缩。实际收益取决于数据可压缩性和 CPU。
  • CHECKSUM:在备份过程中进行校验,并在还原时可以继续校验。
  • STATS = 10:每完成约 10% 输出进度。

CHECKSUM 不是绝对防止介质损坏的保证。它能发现一部分读取或写入过程中的问题,但仍需要把备份复制到独立存储,并执行实际还原验证。

2. 差异备份

差异备份记录自某个全量备份以来发生变化的数据页。它不是“上一次备份以来的差异”,而是“自差异基准全量备份以来的差异”。

BACKUP DATABASE [Inventory]
TO DISK = N'/var/opt/mssql/backup/Inventory_diff.bak'
WITH
    DIFFERENTIAL,
    COMPRESSION,
    CHECKSUM,
    STATS = 10;

如果每天执行一次全量、每小时执行一次差异,那么随着时间推移,差异备份可能越来越大,直到下一次全量备份重置差异基准。

差异备份的恢复过程是:

恢复全量 F0 WITH NORECOVERY
恢复最近的差异 Dn WITH NORECOVERY
恢复 Dn 之后的日志备份
最后一个日志使用 WITH RECOVERY

3. 事务日志备份

完整恢复模型下,日志备份形成连续日志链:

BACKUP LOG [Inventory]
TO DISK = N'/var/opt/mssql/backup/Inventory_log_20250308_1200.trn'
WITH
    COMPRESSION,
    CHECKSUM,
    STATS = 10;

日志备份能够:

  • 使日志截断具备条件;
  • 支持时间点恢复;
  • 降低 RPO;
  • 将事务日志传送到其他服务器或副本。

但日志备份不等于“每条事务提交后立即复制”。备份任务是一个独立的调度过程,其频率决定了仅依靠备份时的潜在数据丢失窗口。

4. Copy-only 备份

Copy-only 备份用于临时复制,不改变常规备份链的正常语义。

BACKUP DATABASE [Inventory]
TO DISK = N'/var/opt/mssql/backup/Inventory_copy_only.bak'
WITH
    COPY_ONLY,
    COMPRESSION,
    CHECKSUM;

典型用途:

  • 临时创建测试环境;
  • 在不影响差异备份基准的情况下获取一个全量副本;
  • 对生产数据库做一次独立取样备份。

Copy-only 全量备份可以用于独立恢复,但不会成为后续差异备份的差异基准。Copy-only 日志备份则不会改变普通日志备份链的截断行为。

5. 备份文件检查与备份历史

可以先检查备份介质中的备份集:

RESTORE HEADERONLY
FROM DISK = N'/var/opt/mssql/backup/Inventory_full.bak';

该命令不会恢复数据库,而是返回备份集元数据,例如:

  • BackupType
  • FirstLSNLastLSN
  • DatabaseBackupLSN
  • BackupStartDateBackupFinishDate
  • 是否包含校验信息。

查看最近的备份历史:

SELECT TOP (20)
    bs.database_name,
    bs.backup_start_date,
    bs.backup_finish_date,
    CASE bs.type
        WHEN 'D' THEN 'Full'
        WHEN 'I' THEN 'Differential'
        WHEN 'L' THEN 'Log'
        ELSE bs.type
    END AS backup_type,
    bs.first_lsn,
    bs.last_lsn,
    bs.database_backup_lsn,
    bs.is_copy_only,
    bs.has_backup_checksums,
    bmf.physical_device_name
FROM msdb.dbo.backupset AS bs
LEFT JOIN msdb.dbo.backupmediafamily AS bmf
    ON bs.media_set_id = bmf.media_set_id
WHERE bs.database_name = N'Inventory'
ORDER BY bs.backup_finish_date DESC;

msdb 中的历史记录很重要,但它只存在于某个 SQL Server 实例中。若实例丢失,备份文件仍然存在,并不意味着备份目录、保留策略和元数据也完整。因此应同时保存备份文件、备份清单和恢复说明。


四、RPO、RTO 与 PITR

1. RPO 与 RTO 的定义

RPO(Recovery Point Objective) 是允许丢失的数据时间范围。

如果最近一次可用日志备份在 12:00,而故障发生在 12:07,且没有其他副本已经保存 12:00 之后的事务,那么基于该备份方案的 RPO 至少接近 7 分钟。

RTO(Recovery Time Objective) 是从故障发生到业务恢复的允许时间。它包括:

  • 找到正确备份;
  • 创建或准备目标实例;
  • 复制备份文件;
  • 执行还原;
  • 回滚未完成事务;
  • 应用配置和权限;
  • 验证数据;
  • 切换客户端;
  • 解除应用层错误状态。

因此,RPO 是“最多丢多少数据”,RTO 是“最多停多久”。

2. 时间点恢复的形式化条件

设:

  • F0 是一个有效全量备份;
  • L1, L2, ..., Ln 是从 F0 之后连续的日志备份;
  • T 是需要恢复到的目标时间;
  • Lk 是包含时间 T 的日志备份。

要进行时间点恢复,必须满足:

  1. F0 可读且属于目标数据库;
  2. 如果使用差异备份,差异备份必须以 F0 为基准;
  3. L1Lk 的日志区间连续;
  4. 所有中间备份均能成功还原;
  5. T 落在 Lk 覆盖的事务日志范围内;
  6. 如果希望恢复到故障前最后状态,还需要获得故障前的尾日志。

用恢复顺序表示:

RESTORE DATABASE Inventory FROM F0 WITH NORECOVERY;
RESTORE DATABASE Inventory FROM Dn WITH NORECOVERY; -- 可选
RESTORE LOG Inventory FROM L1 WITH NORECOVERY;
...
RESTORE LOG Inventory FROM Lk
    WITH STOPAT = '2025-03-08T12:05:00', RECOVERY;

NORECOVERY 表示还会继续应用备份,数据库保持还原状态;最后一步使用 RECOVERY,表示完成恢复并打开数据库。

3. 一个完整的恢复示例

假设已有:

  • /var/opt/mssql/backup/Inventory_full.bak
  • /var/opt/mssql/backup/Inventory_diff.bak
  • /var/opt/mssql/backup/Inventory_log_1200.trn
  • /var/opt/mssql/backup/Inventory_log_1230.trn

先查看备份内容:

RESTORE FILELISTONLY
FROM DISK = N'/var/opt/mssql/backup/Inventory_full.bak';

如果目标服务器上的数据文件路径不同,需要根据该结果使用 MOVE

RESTORE DATABASE [Inventory]
FROM DISK = N'/var/opt/mssql/backup/Inventory_full.bak'
WITH
    MOVE N'Inventory'     TO N'/var/opt/mssql/data/Inventory.mdf',
    MOVE N'Inventory_log' TO N'/var/opt/mssql/data/Inventory_log.ldf',
    NORECOVERY,
    CHECKSUM,
    STATS = 10;

然后恢复差异备份:

RESTORE DATABASE [Inventory]
FROM DISK = N'/var/opt/mssql/backup/Inventory_diff.bak'
WITH
    NORECOVERY,
    CHECKSUM,
    STATS = 10;

再按日志顺序恢复:

RESTORE LOG [Inventory]
FROM DISK = N'/var/opt/mssql/backup/Inventory_log_1200.trn'
WITH
    NORECOVERY,
    CHECKSUM,
    STATS = 10;

RESTORE LOG [Inventory]
FROM DISK = N'/var/opt/mssql/backup/Inventory_log_1230.trn'
WITH
    STOPAT = '2025-03-08T12:25:00',
    RECOVERY,
    CHECKSUM,
    STATS = 10;

这里恢复顺序成立的原因是:

  1. 全量备份提供数据库基线;
  2. 差异备份提供自该全量以来变化的页面;
  3. 12:00 的日志备份补充差异备份之后的日志;
  4. 12:30 的日志备份包含 12:25 的目标时间;
  5. STOPAT 让恢复过程在目标时间停止,而不是应用该备份中的全部日志。

如果恢复的是新数据库,MOVE 中的逻辑文件名必须来自 RESTORE FILELISTONLY 的实际结果,不能把物理文件名或任意字符串当作逻辑文件名。

4. 尾日志备份

如果数据库仍然可以访问,但数据文件或实例即将损坏,故障前最后一段日志可能尚未进入常规日志备份。这时可以尝试尾日志备份:

BACKUP LOG [Inventory]
TO DISK = N'/var/opt/mssql/backup/Inventory_tail.trn'
WITH
    NO_TRUNCATE,
    NORECOVERY,
    CHECKSUM,
    STATS = 10;

NORECOVERY 会让源数据库进入还原状态,阻止普通连接继续使用它,因此必须确认这是恢复路径的一部分。

NO_TRUNCATE 不是“总能备份尾日志”的保证。数据库状态、文件可访问性和损坏程度都会影响结果。如果数据库无法读取所需日志,尾日志备份也可能失败。


五、备份验证不等于“备份命令成功”

1. 三种不同层次的验证

第一层:备份任务成功

SQL Agent、调度器或自动化系统报告命令返回成功。这只能说明本次操作没有报告错误。

第二层:备份介质可读

RESTORE VERIFYONLY
FROM DISK = N'/var/opt/mssql/backup/Inventory_full.bak'
WITH CHECKSUM;

VERIFYONLY 检查备份是否看起来完整、可用于还原,以及备份集是否满足基本格式要求。它不会创建数据库,也不会验证业务数据是否正确。

第三层:真正还原

最可靠的验证是在隔离环境中实际执行 RESTORE DATABASE,然后:

DBCC CHECKDB (N'Inventory') WITH NO_INFOMSGS;

DBCC CHECKDB 检查数据库结构和一致性,但它不能证明业务逻辑正确,也不能证明应用程序会正常工作。恢复演练还应验证:

  • 登录名、用户和权限;
  • Agent Job、凭据和外部依赖;
  • 加密密钥和证书;
  • 应用连接字符串;
  • 数据库兼容级别和配置;
  • 关键查询和写入流程。

2. 常见失败:只做备份,不做恢复演练

以下方案看起来合理,但不可靠:

每天全量备份;
每小时日志备份;
备份任务没有报错。

它仍可能在故障时失败,因为:

  • 备份文件未复制到灾备位置;
  • 恢复服务器磁盘路径不同;
  • 缺少中间日志文件;
  • 备份文件被覆盖;
  • 加密证书没有保存;
  • 数据库用户与实例登录名映射不正确;
  • 恢复所需时间超过 RTO;
  • 备份已经过保留期;
  • 逻辑错误已经传播到所有在线副本。

六、Always On Availability Groups 的组件与数据流

1. Availability Group 的基本对象

Always On Availability Groups,简称 AG,是 SQL Server 的数据库级高可用和灾难恢复技术。其核心对象包括:

  • Availability Group:包含一组需要一起提供高可用能力的用户数据库;
  • Primary Replica:承担读写请求并产生事务日志;
  • Secondary Replica:接收主副本发送的日志并应用;
  • Availability Database:加入 AG 的具体数据库;
  • Availability Group Listener:对应用提供稳定网络入口;
  • WSFC:Windows Server Failover Clustering,负责集群成员、健康状态和仲裁;
  • Database Mirroring Endpoint:副本之间传输数据库镜像/AG 数据流的端点。

AG 中的每个副本通常是一个 SQL Server 实例。数据库副本不共享同一份数据文件,而是各自拥有自己的数据库文件和日志文件。

2. 日志数据流

主副本上的事务通常经历如下路径:

客户端提交
   ↓
主副本生成日志记录
   ↓
写入主副本日志文件并硬化
   ↓
日志捕获与发送
   ↓
次副本接收日志块
   ↓
次副本硬化日志
   ↓
次副本 redo 线程重做日志
   ↓
次副本数据库状态前进

这里有三个容易混淆的时间点:

  1. 发送:日志已经离开主副本;
  2. 硬化:日志已经写入次副本持久存储;
  3. 重做:次副本已经把日志应用到数据页。

同步提交等待的是目标副本的日志硬化,而不是目标副本完成所有 redo。故障转移后,次副本可以先通过恢复流程处理已硬化但尚未重做的日志。

3. 同步提交与异步提交

同步提交

主副本提交事务时,需要等待配置的同步提交次副本确认日志已硬化。

特点:

  • 正常条件下可以实现较低的数据丢失风险;
  • 跨远距离部署时,提交延迟会受到网络往返时间和次副本存储延迟影响;
  • “同步提交”不表示次副本已经完成查询可见的数据重做;
  • “同步提交”也不等于任何强制故障转移都绝对零丢失。

异步提交

主副本不等待次副本硬化后再向客户端确认提交。

特点:

  • 主库提交延迟较低;
  • 次副本可能有发送队列或重做队列;
  • 发生灾难时可能丢失尚未送达或尚未硬化的日志;
  • 更适合远距离灾备,而不是把它当作无损本地高可用。

可以用一个简化的潜在数据丢失模型理解异步副本:

潜在丢失日志 ≈ 主副本已生成的日志
             - 次副本已硬化的日志

这不是直接用于精确计算的 SQL 公式,但它说明了为什么“副本在线”不代表“副本已经包含全部提交”。


七、AG 的状态、故障转移和客户端连接

1. 副本状态不是只有“在线”和“离线”

常见状态包括:

  • ONLINE:副本实例可用;
  • SYNCHRONIZING:次副本正在追赶;
  • SYNCHRONIZED:同步提交关系中,数据库已达到同步状态;
  • NOT SYNCHRONIZING:数据移动未运行或无法保持同步;
  • SUSPENDED:数据库级数据移动被暂停;
  • RESOLVING:集群正在处理主角色归属;
  • FAILED_NO_QUORUM:集群缺少仲裁,无法安全决定角色。

数据库同步状态和副本连接状态必须结合判断。实例能 ping 通,不代表数据库日志传输正常。

2. 自动故障转移的必要条件

自动故障转移不是“主库进程退出就一定切换”。通常需要同时满足:

  • AG 配置允许自动故障转移;
  • 目标副本配置为同步提交;
  • 目标数据库处于可同步状态;
  • WSFC 仍能形成有效仲裁;
  • 集群和 SQL Server 健康检测判定主副本不可用;
  • 目标副本满足自动切换所需的角色和状态条件。

具体版本、部署拓扑和故障条件会影响判定。自动故障转移追求的是在安全条件下自动接管,而不是任何故障都强制接管。

3. 计划内手动切换

在主副本健康、目标副本同步完成时,可以执行计划内故障转移。命令应在目标次副本上执行:

ALTER AVAILABILITY GROUP [AG_Inventory] FAILOVER;

该操作的前置条件包括:

  • 目标副本已经配置为同步提交;
  • 目标数据库显示为同步状态;
  • 应用已准备好处理连接中断和重连;
  • 运维人员确认切换窗口;
  • 客户端通过 Listener 连接,而不是硬编码当前主机名。

计划内切换的关键是先让数据状态收敛,再改变主角色。它不同于强制故障转移。

4. 强制故障转移

当主副本不可用、WSFC 或网络隔离导致无法完成正常协调时,可能需要在次副本上执行:

ALTER AVAILABILITY GROUP [AG_Inventory]
FORCE_FAILOVER_ALLOW_DATA_LOSS;

这个命令明确包含 ALLOW_DATA_LOSS,因为 SQL Server 无法证明次副本拥有主副本已经提交的全部事务。

强制切换后可能出现两类问题:

  1. 次副本成为新的主副本,但旧主副本后来恢复并仍认为自己可写;
  2. 旧主副本包含新主副本没有的数据,重新加入 AG 时产生分叉或需要重新同步。

因此强制切换后的流程不是“命令成功就结束”,还必须:

  • 隔离旧主副本,避免双主写入;
  • 检查应用连接是否已转移;
  • 评估丢失的事务;
  • 处理旧副本的恢复、重新加入或重新初始化;
  • 对受影响业务进行数据校验。

5. Listener 与读写路由

应用应连接 AG Listener,而不是直接连接某个副本实例。Listener 提供:

  • 稳定的 DNS 名称;
  • 虚拟网络名和端口;
  • 当前主副本定位;
  • 在配置正确时的只读路由能力。

只读路由需要同时满足多个条件:

  • 客户端连接意图为 ReadOnly
  • AG 配置了只读路由 URL;
  • 当前副本允许读取;
  • 应用驱动支持相关连接属性;
  • 连接字符串使用正确的 Listener 和端口。

例如,应用连接字符串通常需要包含类似 ApplicationIntent=ReadOnly 的客户端属性,但具体语法取决于驱动。仅仅连接到次副本并不能自动保证查询被路由到期望节点。


八、AG 配置前置条件与生命周期

1. 配置不是单条 CREATE 语句

建立 AG 前,需要完成一组实例级准备:

  1. 实例使用支持 AG 的版本和 Edition;
  2. 所有副本实例的版本兼容;
  3. 配置 WSFC 和集群节点;
  4. 启用 Always On;
  5. 为每个实例配置数据库镜像端点;
  6. 配置端点认证和网络访问;
  7. 为次副本准备数据库;
  8. 使数据库满足加入 AG 的条件;
  9. 创建 AG;
  10. 将次副本数据库加入并开始数据同步;
  11. 创建 Listener;
  12. 用实际客户端验证读写、切换和重连。

SQL Server Configuration Manager 通常用于启用实例的 Always On 功能和管理服务。端点可以通过 T-SQL 创建,例如:

CREATE ENDPOINT [Hadr_endpoint]
STATE = STARTED
AS TCP (LISTENER_PORT = 5022)
FOR DATABASE_MIRRORING
(
    ROLE = ALL,
    AUTHENTICATION = WINDOWS NEGOTIATE,
    ENCRYPTION = REQUIRED ALGORITHM AES
);

这条语句并不意味着 AG 已经可用。前置条件仍包括:

  • 端口 5022 在副本之间可达;
  • 运行 SQL Server 服务的账户在对端点执行连接时具有所需权限;
  • 端点名称和端口没有冲突;
  • 防火墙和网络设备允许双向通信;
  • 所有实例的端点状态为 STARTED

生产环境不应直接复制端点配置而忽略认证、服务账户和证书要求。Windows 身份验证、证书身份验证和网络拓扑必须与实际部署一致。

2. 数据库如何加入 AG

常见方式是:

  1. 在主副本创建数据库;
  2. 切换到完整恢复模型;
  3. 创建至少一个全量备份;
  4. 将全量备份和连续日志备份还原到次副本,使用 NORECOVERY
  5. 在 AG 中添加数据库;
  6. 在次副本执行加入数据库操作;
  7. 等待同步状态收敛。

示例:

ALTER DATABASE [Inventory]
SET RECOVERY FULL;
GO

BACKUP DATABASE [Inventory]
TO DISK = N'/var/opt/mssql/backup/Inventory_ag_seed_full.bak'
WITH COPY_ONLY, COMPRESSION, CHECKSUM;
GO

在次副本:

RESTORE DATABASE [Inventory]
FROM DISK = N'/var/opt/mssql/backup/Inventory_ag_seed_full.bak'
WITH NORECOVERY, CHECKSUM;
GO

如果全量备份之后又产生了日志,需要继续按 LSN 顺序还原日志:

RESTORE LOG [Inventory]
FROM DISK = N'/var/opt/mssql/backup/Inventory_ag_seed_log.trn'
WITH NORECOVERY, CHECKSUM;
GO

之后才可以将数据库加入 AG。若次副本数据库已经恢复并打开,或者缺少中间日志,加入过程可能失败,或者需要重新初始化。


九、FCI、AG 和 Log Shipping 的取舍

1. FCI:实例级切换,共享存储

Failover Cluster Instance 是一个 SQL Server 实例资源组,多个节点访问同一套共享存储。故障时实例资源转移到另一个节点。

优势:

  • 对应用通常表现为同一个实例;
  • 不需要为每个数据库维护独立副本;
  • 实例级对象和数据库一起切换。

边界:

  • 共享存储本身可能成为故障域;
  • 不能天然解决跨站点存储灾难;
  • 节点切换期间实例需要重新启动或恢复;
  • 它不是独立存储副本。

2. AG:数据库级副本

AG 每个副本有独立存储,通过日志传输保持一致。

优势:

  • 支持同步和异步提交;
  • 可以跨机房部署;
  • 支持读扩展和备份卸载等能力;
  • 故障转移粒度是数据库组。

边界:

  • 需要管理副本、端点、WSFC、Listener 和数据库状态;
  • AG 只保护加入 AG 的用户数据库;
  • mastermsdb 等系统数据库不会因为 AG 自动同步;
  • 登录名、Agent Job、Linked Server、凭据等实例级对象需要单独同步。

3. Log Shipping:基于备份的简单灾备

Log Shipping 通过定时任务完成:

主服务器备份日志
    ↓
复制 .trn 文件
    ↓
灾备服务器还原日志

它通常具有较大的切换延迟,但结构简单、跨机房适应性较好,也可以作为比 AG 更低成本的灾备方案。

它的限制是:

  • 不是实时副本;
  • 切换通常需要人工或额外自动化;
  • 目标库通常处于 STANDBYNORECOVERY
  • 应用切换、登录同步和回切需要额外设计。

十、监控:不要只监控 SQL Server 进程

高可用监控至少要覆盖四层:

业务连接层
  ↓
Listener、DNS、端口和驱动重连

数据库副本层
  ↓
角色、同步状态、挂起状态、发送与重做队列

实例资源层
  ↓
CPU、内存、I/O、Worker、锁和等待

备份恢复层
  ↓
最近成功备份、日志链、备份可读性、恢复演练

1. 数据库状态与日志复用

SELECT
    name,
    state_desc,
    user_access_desc,
    recovery_model_desc,
    log_reuse_wait_desc
FROM sys.databases
ORDER BY name;

重点观察:

  • 数据库是否为 ONLINE
  • 是否意外变为 SUSPECTRECOVERY_PENDINGEMERGENCY
  • 生产数据库是否错误地处于 SIMPLE
  • log_reuse_wait_desc 是否长期为 LOG_BACKUPAVAILABILITY_REPLICAACTIVE_TRANSACTION 或其他原因。

ACTIVE_TRANSACTION 需要调查长事务;AVAILABILITY_REPLICA 需要调查副本数据移动和次副本存储,而不是简单收缩日志。

2. 日志空间

在较新的受支持版本中,可以使用:

USE [Inventory];
GO

SELECT
    total_log_size_in_bytes / 1024.0 / 1024.0 AS total_log_size_mb,
    used_log_space_in_percent,
    used_log_space_in_bytes / 1024.0 / 1024.0 AS used_log_space_mb
FROM sys.dm_db_log_space_usage;

这个 DMV 反映当前数据库日志空间使用情况。日志文件增长时,应同时回答:

  • 业务是否产生了异常写入量;
  • 日志备份是否停止;
  • 是否存在长事务;
  • AG 次副本是否落后;
  • 是否正在进行大事务、索引操作或批量导入。

直接执行 DBCC SHRINKFILE 只能缩小已经不再需要的尾部空间,不能修复持续增长原因。频繁收缩还会造成文件反复增长和物理碎片。

3. AG 副本状态

副本级状态:

SELECT
    ar.replica_server_name,
    ars.role_desc,
    ars.connected_state_desc,
    ars.operational_state_desc,
    ars.recovery_health_desc,
    ars.synchronization_health_desc
FROM sys.availability_replicas AS ar
JOIN sys.dm_hadr_availability_replica_states AS ars
    ON ar.replica_id = ars.replica_id;

数据库级状态:

SELECT
    DB_NAME(drs.database_id) AS database_name,
    ar.replica_server_name,
    ars.role_desc,
    drs.synchronization_state_desc,
    drs.synchronization_health_desc,
    drs.database_state_desc,
    drs.is_suspended,
    drs.suspend_reason_desc,
    drs.log_send_queue_size,
    drs.log_send_rate,
    drs.redo_queue_size,
    drs.redo_rate,
    drs.last_commit_time
FROM sys.dm_hadr_database_replica_states AS drs
JOIN sys.availability_replicas AS ar
    ON drs.replica_id = ar.replica_id
JOIN sys.dm_hadr_availability_replica_states AS ars
    ON drs.replica_id = ars.replica_id
   AND drs.group_id = ars.group_id
WHERE drs.is_local = 1;

这些字段需要结合解释:

  • log_send_queue_size:主副本尚未发送到次副本的日志量,通常以 KB 表示;
  • log_send_rate:日志发送速率,通常以 KB/s 表示;
  • redo_queue_size:次副本已收到但尚未重做的日志量;
  • redo_rate:次副本重做速率;
  • is_suspended:该数据库的数据移动是否被暂停;
  • suspend_reason_desc:暂停原因;
  • last_commit_time:副本上最近提交相关状态的时间。

在速率稳定且不为零时,可以粗略估算追赶时间:

发送追赶时间 ≈ log_send_queue_size / log_send_rate
重做追赶时间 ≈ redo_queue_size / redo_rate

但这是诊断估算,不是 SLA 保证。速率会受到磁盘、网络、日志生成速率、锁和查询负载影响。若速率为零,不能用除法得出“无限大”之外的有意义结论,而应先检查连接、端点、暂停状态和错误日志。

4. 集群和 Listener 不能漏监控

AG 依赖的健康范围还包括:

  • WSFC 节点和仲裁;
  • 集群网络名称资源;
  • Listener IP 和 DNS;
  • SQL Server 服务状态;
  • HADR endpoint;
  • 防火墙端口;
  • 见证资源或云见证配置;
  • 客户端驱动的多子网重连行为。

数据库副本显示 SYNCHRONIZED,但 Listener DNS 已失效,应用仍然无法连接。反过来,Listener 可以解析,但数据库已经停止数据移动,也不代表目标副本可安全接管。


十一、从故障现象反推原因

1. 日志文件持续增长

可能原因:

  • 未执行日志备份;
  • 长事务未结束;
  • AG 次副本落后;
  • 数据库镜像或复制相关消费者未推进;
  • 大型事务或批处理正在运行。

诊断顺序:

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

然后检查:

  • SQL Agent 日志备份 Job;
  • msdb.dbo.backupset 的最近日志备份;
  • AG 发送和重做队列;
  • 活跃事务;
  • SQL Server 错误日志。

不要在不了解原因时切换到 SIMPLE、删除日志文件或反复收缩日志。这样可能破坏原本需要的日志链,且无法解决 AG 或长事务导致的等待。

2. 次副本“在线”但不断落后

需要先区分是发送队列还是重做队列:

  • log_send_queue_size 大:主库到次库的网络、端点或次库接收能力有问题;
  • redo_queue_size 大:日志已到达次库,但次库重做能力不足;
  • 两者都小但状态异常:检查连接状态、暂停状态、数据库错误和集群状态。

常见根因包括:

  • 次副本存储延迟高;
  • 次副本 CPU 或内存不足;
  • 网络抖动;
  • HADR endpoint 未启动;
  • 数据库数据移动被暂停;
  • 次副本长时间执行阻塞或资源密集型操作。

3. AG 自动切换没有发生

不要直接得出“AG 失效”的结论。应验证:

  1. 当前主副本和目标副本的角色;
  2. 目标副本是否同步提交;
  3. 数据库是否为 SYNCHRONIZED
  4. 副本是否配置自动故障转移;
  5. WSFC 是否有仲裁;
  6. 是否发生网络分区;
  7. SQL Server 错误日志和集群日志中的故障检测结果;
  8. 自动故障转移是否被维护操作、暂停或不支持的状态阻止。

自动切换的保守行为是设计的一部分:在无法证明安全时不切换,比同时出现两个可写主库更安全。

4. 故障转移后应用仍报错

可能原因:

  • 应用连接的是实例主机名而不是 Listener;
  • DNS 缓存未刷新;
  • 客户端驱动不支持或未启用多子网重连;
  • 登录名只在旧实例存在;
  • 数据库用户与新实例登录名 SID 不匹配;
  • 应用连接池保留了旧连接;
  • 防火墙未允许新路径;
  • 应用将只读连接错误地发送到主副本,或反过来。

AG 不会自动同步实例级对象。登录名、Job、Linked Server、凭据、数据库邮件、服务器级配置和证书都要纳入部署与灾备管理。


十二、故障恢复路径

路径 A:主副本仍可访问,计划内切换

这是风险最低的切换类型:

  1. 确认目标副本健康;
  2. 确认目标数据库为同步状态;
  3. 停止或排空会产生新写入的维护任务;
  4. 执行计划内故障转移;
  5. 通过 Listener 验证写入;
  6. 验证新主副本继续执行日志备份;
  7. 检查旧主副本是否转为次副本;
  8. 验证应用连接池和后台任务。

路径 B:主副本不可用,但同步副本可接管

如果集群仍可协调,优先使用正常故障转移。若无法完成正常切换但确认业务必须恢复,可以考虑强制故障转移。

关键风险是:同步配置降低了数据丢失概率,但强制切换时如果存在未确认的提交、网络隔离或集群状态不完整,仍不能把“同步”解释为无条件零丢失。

路径 C:强制切换后旧主副本恢复

必须首先防止旧主副本重新对外提供写服务。然后:

  1. 确认新主副本的业务状态;
  2. 记录可能丢失或分叉的数据范围;
  3. 检查旧副本是否还能作为次副本加入;
  4. 必要时从新主副本重新初始化旧副本;
  5. 对应用关键数据进行对账;
  6. 重新建立备份与监控链路。

最危险的操作不是“暂时没有副本”,而是两个实例都被应用认为是主库并同时接受写入。

路径 D:整组副本和存储均不可用

这时进入备份恢复流程:

  1. 找到最近可用全量备份;
  2. 找到对应差异备份;
  3. 按 LSN 顺序收集连续日志备份;
  4. 如果源服务器仍可访问,尝试尾日志备份;
  5. 在目标实例恢复数据库;
  6. 执行 DBCC CHECKDB
  7. 恢复登录名、权限、Job、证书和应用配置;
  8. 用业务查询验证数据;
  9. 切换连接入口;
  10. 记录恢复耗时和实际数据丢失点。

若目标时间是逻辑错误发生之前,而不是服务器宕机时间,应使用 PITR。例如,误删在 12:05 发生,不能简单恢复到 12:30;应恢复到 12:04:59 附近,然后从业务日志或其他来源重放合法操作。


十三、备份与 AG 的组合设计

一种常见的组合如下:

本地同步副本
    └── 应对节点或实例故障,缩短 RTO

异地异步副本
    └── 应对机房级故障,接受一定 RPO

独立备份存储
    └── 应对误删、逻辑错误、全副本损坏和时间点恢复

备份任务可以按照 AG 的备份偏好选择副本执行,但“首选备份副本”是调度建议,不是安全边界。必须验证:

  • 实际执行备份的是哪个副本;
  • 备份文件写到了哪里;
  • 日志备份是否仍保持连续链;
  • 备用副本切换后备份任务是否继续工作;
  • 备份历史是否能正确记录;
  • 备份文件是否被复制到与所有副本不同的故障域。

独立备份存储至少应避免与数据库数据文件、同一台虚拟机和同一存储阵列共享单一故障点。更高要求的环境还要考虑删除保护、不可变存储、访问控制和加密密钥保管。


十四、恢复演练应该验证什么

一次完整演练不应只执行:

RESTORE VERIFYONLY ...

而应至少验证以下流程:

  1. 从备份清单找到目标全量备份;
  2. 找到匹配的差异备份;
  3. 找到连续的日志备份;
  4. 在隔离 SQL Server 实例还原;
  5. 执行 DBCC CHECKDB
  6. 检查关键表的行数、校验和或业务汇总;
  7. 测试登录和权限;
  8. 测试应用连接;
  9. 测试 Listener 或灾备入口;
  10. 测量从开始恢复到业务可用的时间;
  11. 记录实际可恢复到的时间点;
  12. 模拟备份缺失、磁盘路径不同和凭据失效等错误。

演练的输出应是可验证的事实:

最近可恢复时间:2025-03-08 12:25:00
恢复开始时间:13:00:00
数据库打开时间:13:18:00
应用验证完成:13:27:00
实际 RPO:约 5 分钟
实际 RTO:27 分钟

这些结果比“配置了高可用”“每天都有备份”更能说明系统是否满足业务目标。


十五、几个必须纠正的误解

误解一:同步提交等于永不丢数据

同步提交表示事务提交需要等待同步副本的日志硬化,但强制故障转移、网络隔离、集群判定和未完成协调都可能改变结果。正常计划内切换与强制切换必须分开讨论。

误解二:AG 副本可以替代备份

逻辑删除、错误更新、恶意操作会被复制。副本能提供另一个服务实例,却不能提供任意历史时间点。

误解三:全量备份后就不需要日志备份

FULLBULK_LOGGED 模型下,没有持续日志备份就没有完整的时间点恢复链,也可能导致日志无法截断。

误解四:Listener 可连接就说明数据库同步正常

Listener 只说明网络入口可能可达。必须分别检查数据库同步状态、日志发送队列、重做队列和数据移动是否暂停。

误解五:RESTORE VERIFYONLY 通过就完成了恢复验证

它不能替代实际还原、数据库一致性检查和应用验证。只有真正恢复并测试,才能知道备份链、目标路径、权限和依赖是否可用。

误解六:把日志文件收缩到很小可以解决日志问题

收缩只能处理暂时不再需要的尾部空间,不能解决长事务、缺少日志备份、AG 副本落后或持续高写入。错误收缩还会让日志反复增长,增加 I/O 和碎片问题。


结语

SQL Server 高可用的核心不是某个单独功能,而是一条完整的数据与故障路径:

事务日志产生
    → 日志硬化
    → 备份或副本传输
    → 目标副本硬化与重做
    → 状态监控
    → 计划内或强制切换
    → 必要时基于备份进行时间点恢复

Backup 提供历史版本和灾难后的恢复能力;Always On 提供数据库级副本和快速切换;FCI 提供实例级共享存储切换;Log Shipping 提供相对简单的异地日志灾备。只有把它们与 LSN、恢复模型、Listener、WSFC、监控和恢复演练结合起来,RPO 与 RTO 才具有可验证的含义。


系列导航与关联阅读

官方资料

本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。