Skip to content

SQLServer 页级恢复

页级恢复概述

什么是页级恢复

页级恢复(Page-Level Recovery)是指只恢复数据库中损坏的页,而不是整个数据库或文件组的恢复方式。SQLServer数据库的基本存储单位是页(Page),每个页大小为8KB。当数据库中的个别页损坏时,可以使用页级恢复只恢复损坏的页,从而减少恢复时间和资源消耗。

页级恢复的适用场景

  1. 个别页损坏:当数据库中只有少数页损坏时,页级恢复比全库恢复更高效
  2. 减少恢复时间:页级恢复只恢复损坏的页,减少了恢复时间和系统停机时间
  3. 降低资源消耗:页级恢复只需要恢复少量数据,降低了CPU、内存和I/O资源消耗
  4. 在线恢复:在SQL Server 2005及以上版本中,页级恢复可以在数据库在线状态下执行
  5. 大型数据库恢复:对于大型数据库,页级恢复可以避免长时间的全库恢复

页级恢复前准备

识别损坏的页

  1. 通过错误日志识别

    sql
    -- 查看错误日志中的页损坏信息
    EXEC xp_readerrorlog 0, 1, '823', NULL, NULL, NULL, 'DESC';
    EXEC xp_readerrorlog 0, 1, '824', NULL, NULL, NULL, 'DESC';
    EXEC xp_readerrorlog 0, 1, '825', NULL, NULL, NULL, 'DESC';
  2. 通过DBCC CHECKDB识别

    sql
    -- 执行DBCC CHECKDB检查页损坏
    DBCC CHECKDB ([AdventureWorks2022]) WITH NO_INFOMSGS, ALL_ERRORMSGS;
    -- 查找包含 "error 823"、"error 824" 或 "error 825" 的错误信息,记录损坏的页ID
  3. 通过sys.dm_db_missing_index_details视图识别

    sql
    -- 查看缺失的索引和损坏的页
    SELECT * FROM sys.dm_db_missing_index_details;

恢复环境检查

  1. 备份文件检查

    sql
    -- 验证完整备份文件完整性
    RESTORE VERIFYONLY FROM DISK = N'D:\Backup\AdventureWorks2022_Full_20251227.bak' WITH CHECKSUM;
    
    -- 验证事务日志备份文件完整性
    RESTORE VERIFYONLY FROM DISK = N'D:\Backup\AdventureWorks2022_Log_20251227_1215.trn' WITH CHECKSUM;
  2. 数据库状态检查

    sql
    -- 检查数据库状态
    SELECT 
        name AS DatabaseName,
        state_desc,
        recovery_model_desc
    FROM sys.databases
    WHERE name = 'AdventureWorks2022';
  3. 恢复模式检查

    • 页级恢复要求数据库使用完整恢复模式或大容量日志恢复模式
    • 如果数据库使用简单恢复模式,需要先切换到完整恢复模式并创建完整备份
    sql
    -- 切换到完整恢复模式
    ALTER DATABASE [AdventureWorks2022] SET RECOVERY FULL;
    
    -- 创建完整备份
    BACKUP DATABASE [AdventureWorks2022] 
    TO DISK = N'D:\Backup\AdventureWorks2022_Full_Before_Page_Recovery.bak' 
    WITH NOFORMAT, NOINIT, NAME = N'AdventureWorks2022-Full Database Backup Before Page Recovery', 
    SKIP, NOREWIND, NOUNLOAD, STATS = 10;

页级恢复实施步骤

使用SSMS进行页级恢复

  1. 打开SQL Server Management Studio,连接到SQL Server实例
  2. 展开"数据库"节点
  3. 右键单击要恢复的数据库,选择"任务" > "还原" > "页..."
  4. 在"还原页"对话框中:
    • 在"还原页"区域,输入损坏的页ID,格式为"文件ID:页ID"(例如:1:256)
    • 在"源"区域,选择"源设备",点击"..."按钮,选择完整备份和事务日志备份文件
    • 在"恢复选项"区域,配置恢复选项:
      • 恢复状态:选择"RESTORE WITH RECOVERY"(如果只恢复损坏的页)
      • 覆盖现有数据库:不需要勾选,因为只恢复损坏的页
    • 点击"确定"执行恢复

使用T-SQL进行页级恢复

基本页级恢复

sql
-- 1. 识别损坏的页
-- 假设DBCC CHECKDB检测到页1:256损坏

-- 2. 恢复损坏的页
RESTORE DATABASE [AdventureWorks2022] 
PAGE = '1:256' 
FROM DISK = N'D:\Backup\AdventureWorks2022_Full_20251227.bak' 
WITH NORECOVERY, 
NOUNLOAD, STATS = 10;

-- 3. 恢复所有后续的事务日志备份
RESTORE LOG [AdventureWorks2022] 
FROM DISK = N'D:\Backup\AdventureWorks2022_Log_20251227_1215.trn' 
WITH NORECOVERY, 
NOUNLOAD, STATS = 10;

RESTORE LOG [AdventureWorks2022] 
FROM DISK = N'D:\Backup\AdventureWorks2022_Log_20251227_1300.trn' 
WITH RECOVERY, 
NOUNLOAD, STATS = 10;

恢复多个损坏的页

sql
-- 恢复多个损坏的页
RESTORE DATABASE [AdventureWorks2022] 
PAGE = '1:256', '1:512', '2:1024' 
FROM DISK = N'D:\Backup\AdventureWorks2022_Full_20251227.bak' 
WITH NORECOVERY, 
NOUNLOAD, STATS = 10;

-- 恢复所有后续的事务日志备份
RESTORE LOG [AdventureWorks2022] 
FROM DISK = N'D:\Backup\AdventureWorks2022_Log_20251227_1215.trn' 
WITH NORECOVERY, 
NOUNLOAD, STATS = 10;

RESTORE LOG [AdventureWorks2022] 
FROM DISK = N'D:\Backup\AdventureWorks2022_Log_20251227_1300.trn' 
WITH RECOVERY, 
NOUNLOAD, STATS = 10;

使用差异备份加速页级恢复

sql
-- 1. 恢复损坏的页
RESTORE DATABASE [AdventureWorks2022] 
PAGE = '1:256' 
FROM DISK = N'D:\Backup\AdventureWorks2022_Full_20251227.bak' 
WITH NORECOVERY, 
NOUNLOAD, STATS = 10;

-- 2. 恢复差异备份(可选,加速恢复)
RESTORE DATABASE [AdventureWorks2022] 
FROM DISK = N'D:\Backup\AdventureWorks2022_Diff_20251227_1200.bak' 
WITH NORECOVERY, 
NOUNLOAD, STATS = 10;

-- 3. 恢复所有后续的事务日志备份
RESTORE LOG [AdventureWorks2022] 
FROM DISK = N'D:\Backup\AdventureWorks2022_Log_20251227_1215.trn' 
WITH NORECOVERY, 
NOUNLOAD, STATS = 10;

RESTORE LOG [AdventureWorks2022] 
FROM DISK = N'D:\Backup\AdventureWorks2022_Log_20251227_1300.trn' 
WITH RECOVERY, 
NOUNLOAD, STATS = 10;

页级恢复场景

场景1:个别页损坏恢复

恢复步骤

  1. 识别损坏的页

    sql
    -- 执行DBCC CHECKDB检测损坏的页
    DBCC CHECKDB ([AdventureWorks2022]) WITH NO_INFOMSGS, ALL_ERRORMSGS;
    -- 假设检测到页1:256和1:512损坏
  2. 恢复损坏的页

    sql
    -- 恢复损坏的页
    RESTORE DATABASE [AdventureWorks2022] 
    PAGE = '1:256', '1:512' 
    FROM DISK = N'D:\Backup\AdventureWorks2022_Full_20251227.bak' 
    WITH NORECOVERY, 
    NOUNLOAD, STATS = 10;
  3. 恢复事务日志备份

    sql
    -- 恢复所有后续的事务日志备份
    RESTORE LOG [AdventureWorks2022] 
    FROM DISK = N'D:\Backup\AdventureWorks2022_Log_20251227_1215.trn' 
    WITH NORECOVERY, 
    NOUNLOAD, STATS = 10;
    
    RESTORE LOG [AdventureWorks2022] 
    FROM DISK = N'D:\Backup\AdventureWorks2022_Log_20251227_1300.trn' 
    WITH RECOVERY, 
    NOUNLOAD, STATS = 10;
  4. 验证恢复结果

    sql
    -- 验证页恢复结果
    DBCC CHECKDB ([AdventureWorks2022]) WITH NO_INFOMSGS, ALL_ERRORMSGS;
    -- 确认损坏的页已恢复

场景2:大型数据库页损坏恢复

恢复步骤

  1. 识别损坏的页

    sql
    -- 对于大型数据库,使用更高效的方式检测损坏的页
    DBCC CHECKDB ([AdventureWorks2022]) WITH PHYSICAL_ONLY, NO_INFOMSGS;
    -- 假设检测到页1:1024损坏
  2. 恢复损坏的页

    sql
    -- 恢复损坏的页
    RESTORE DATABASE [AdventureWorks2022] 
    PAGE = '1:1024' 
    FROM DISK = N'D:\Backup\AdventureWorks2022_Full_20251227.bak' 
    WITH NORECOVERY, 
    NOUNLOAD, STATS = 10;
  3. 恢复差异备份

    sql
    -- 恢复差异备份,加速恢复过程
    RESTORE DATABASE [AdventureWorks2022] 
    FROM DISK = N'D:\Backup\AdventureWorks2022_Diff_20251227_1200.bak' 
    WITH NORECOVERY, 
    NOUNLOAD, STATS = 10;
  4. 恢复事务日志备份

    sql
    -- 恢复所有后续的事务日志备份
    DECLARE @LogBackupPath NVARCHAR(255) = N'D:\Backup\AdventureWorks2022_Log_';
    DECLARE @LogBackupFiles TABLE (FileName NVARCHAR(255));
    
    -- 假设我们有多个事务日志备份文件
    INSERT INTO @LogBackupFiles VALUES 
    (@LogBackupPath + '20251227_1215.trn'),
    (@LogBackupPath + '20251227_1230.trn'),
    (@LogBackupPath + '20251227_1245.trn'),
    (@LogBackupPath + '20251227_1300.trn');
    
    DECLARE @CurrentFile NVARCHAR(255);
    DECLARE @IsLastFile BIT = 0;
    DECLARE FileCursor CURSOR FOR 
    SELECT FileName, 
           CASE WHEN ROW_NUMBER() OVER (ORDER BY FileName DESC) = 1 THEN 1 ELSE 0 END AS IsLastFile
    FROM @LogBackupFiles;
    
    OPEN FileCursor;
    FETCH NEXT FROM FileCursor INTO @CurrentFile, @IsLastFile;
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        IF @IsLastFile = 1
        BEGIN
            -- 最后一个事务日志备份,使用RECOVERY
            RESTORE LOG [AdventureWorks2022] 
            FROM DISK = @CurrentFile 
            WITH RECOVERY, 
            NOUNLOAD, STATS = 10;
        END
        ELSE
        BEGIN
            -- 中间的事务日志备份,使用NORECOVERY
            RESTORE LOG [AdventureWorks2022] 
            FROM DISK = @CurrentFile 
            WITH NORECOVERY, 
            NOUNLOAD, STATS = 10;
        END
        
        FETCH NEXT FROM FileCursor INTO @CurrentFile, @IsLastFile;
    END;
    
    CLOSE FileCursor;
    DEALLOCATE FileCursor;
  5. 验证恢复结果

    sql
    -- 验证页恢复结果
    DBCC CHECKDB ([AdventureWorks2022]) WITH NO_INFOMSGS, ALL_ERRORMSGS;
    -- 确认损坏的页已恢复

页级恢复最佳实践

恢复前准备

  1. 及时识别损坏的页

    • 定期执行DBCC CHECKDB检查数据库完整性
    • 监控SQL Server错误日志,及时发现页损坏错误
    • 配置数据库邮件告警,当发生页损坏时及时通知DBA
  2. 确保备份文件完整

    • 定期验证备份文件的完整性
    • 确保有足够的备份文件覆盖损坏的页
    • 对于大型数据库,考虑使用文件组备份,便于页级恢复
  3. 准备恢复环境

    • 确保有足够的磁盘空间
    • 确保系统有足够的CPU和内存资源
    • 关闭不必要的应用程序,减少系统负载

恢复过程中的最佳实践

  1. 正确使用NORECOVERY和RECOVERY选项

    • 页级恢复和中间的事务日志恢复使用NORECOVERY
    • 最后一个事务日志恢复使用RECOVERY,完成恢复
    • 避免在中间步骤使用RECOVERY,导致恢复链中断
  2. 使用差异备份加速恢复

    • 对于大型数据库,使用差异备份可以减少需要恢复的事务日志备份数量
    • 差异备份只包含自上次完整备份以来更改的数据,恢复速度更快
  3. 监控恢复进度

    • 使用STATS选项监控恢复进度
    • 定期检查恢复状态
    • 记录恢复开始时间和完成时间

恢复后的最佳实践

  1. 验证恢复结果

    • 执行DBCC CHECKDB验证数据完整性
    • 验证损坏的页已恢复
    • 运行应用程序测试,确保数据库正常工作
  2. 分析页损坏原因

    • 检查存储设备的健康状态
    • 检查磁盘控制器和电缆
    • 检查系统事件日志,查找硬件故障信息
    • 考虑更换有问题的存储设备
  3. 更新备份策略

    • 根据页损坏的原因调整备份策略
    • 增加备份频率,减少数据丢失风险
    • 考虑使用更可靠的存储设备

页级恢复常见问题处理

问题1:页级恢复失败,提示找不到指定的页

错误信息

Msg 3157, Level 16, State 1, Line 1
The backup set does not contain the requested database page. The page number is (1:256).
Msg 3013, Level 16, State 1, Line 1
RESTORE DATABASE is terminating abnormally.

解决方案

  1. 检查备份文件是否包含损坏的页:

    sql
    -- 查看备份文件中的页范围
    RESTORE FILELISTONLY FROM DISK = N'D:\Backup\AdventureWorks2022_Full_20251227.bak';
  2. 使用包含损坏页的备份文件:

    • 查找包含损坏页的完整备份或文件组备份
    • 使用正确的备份文件进行页级恢复
  3. 如果没有包含损坏页的备份文件,考虑使用其他恢复方式:

    • 全库恢复
    • 文件组恢复
    • 从其他备份恢复数据

问题2:页级恢复失败,提示数据库处于只读状态

错误信息

Msg 3127, Level 16, State 1, Line 1
The database is read-only. It cannot be recovered. Use RESTORE WITH RECOVERY to bring the database online.

解决方案

  1. 检查数据库状态:

    sql
    SELECT 
        name AS DatabaseName,
        state_desc,
        is_read_only
    FROM sys.databases
    WHERE name = 'AdventureWorks2022';
  2. 如果数据库处于只读状态,修改为读写状态:

    sql
    -- 将数据库修改为读写状态
    ALTER DATABASE [AdventureWorks2022] SET READ_WRITE;
  3. 重新执行页级恢复:

    sql
    -- 重新执行页级恢复
    RESTORE DATABASE [AdventureWorks2022] 
    PAGE = '1:256' 
    FROM DISK = N'D:\Backup\AdventureWorks2022_Full_20251227.bak' 
    WITH NORECOVERY, 
    NOUNLOAD, STATS = 10;
    
    -- 恢复事务日志备份
    RESTORE LOG [AdventureWorks2022] 
    FROM DISK = N'D:\Backup\AdventureWorks2022_Log_20251227_1215.trn' 
    WITH RECOVERY, 
    NOUNLOAD, STATS = 10;

问题3:页级恢复后数据库无法访问

错误信息

Msg 927, Level 14, State 2, Line 1
Database 'AdventureWorks2022' cannot be opened. It is in the middle of a restore.

解决方案

  1. 检查数据库状态:

    sql
    SELECT 
        name AS DatabaseName,
        state_desc,
        is_in_standby
    FROM sys.databases
    WHERE name = 'AdventureWorks2022';
  2. 如果数据库处于RESTORING状态,完成恢复:

    sql
    -- 使用RECOVERY选项完成恢复
    RESTORE DATABASE [AdventureWorks2022] WITH RECOVERY;

页级恢复性能优化

优化备份策略

  1. 使用文件组备份

    • 对于大型数据库,使用文件组备份可以减少页级恢复需要的备份文件大小
    • 文件组备份只包含特定文件组的数据,恢复速度更快
    sql
    -- 创建文件组备份
    BACKUP DATABASE [AdventureWorks2022] FILEGROUP = N'PRIMARY' 
    TO DISK = N'D:\Backup\AdventureWorks2022_Primary_FG.bak' 
    WITH NOFORMAT, NOINIT, NAME = N'AdventureWorks2022-Primary Filegroup Backup', 
    SKIP, NOREWIND, NOUNLOAD, STATS = 10;
  2. 使用备份压缩

    • 启用备份压缩可以减少备份文件大小,提高恢复速度
    • 对于页级恢复,压缩的备份文件需要解压,可能会增加CPU消耗,但减少了I/O消耗
    sql
    -- 创建压缩的完整备份
    BACKUP DATABASE [AdventureWorks2022] 
    TO DISK = N'D:\Backup\AdventureWorks2022_Full_Compressed.bak' 
    WITH COMPRESSION, NOFORMAT, NOINIT, 
    NAME = N'AdventureWorks2022-Compressed Full Database Backup', 
    SKIP, NOREWIND, NOUNLOAD, STATS = 10;

优化恢复过程

  1. 使用更快的存储设备

    • 将备份文件存储在高性能存储上(如SSD)
    • 将数据库文件恢复到高性能存储上
  2. 增加恢复并行度

    • 使用多个备份设备并行恢复
    • 对于大型数据库,考虑使用文件组备份和并行恢复
  3. 使用加速数据库恢复(ADR)

    • SQL Server 2019及以上版本支持加速数据库恢复
    • ADR可以显著减少恢复时间,尤其是对于大型数据库
    • 启用ADR:
      sql
      ALTER DATABASE [AdventureWorks2022] SET ACCELERATED_DATABASE_RECOVERY = ON;

常见问题(FAQ)

Q1: 页级恢复和全库恢复有什么区别?

A: 页级恢复只恢复数据库中损坏的页,而全库恢复恢复整个数据库。页级恢复的恢复时间更短,资源消耗更少,适用于只有少数页损坏的情况。全库恢复适用于数据库完全损坏或大部分页损坏的情况。

Q2: 页级恢复需要什么条件?

A: 页级恢复需要满足以下条件:

  1. 数据库必须使用完整恢复模式或大容量日志恢复模式
  2. 必须有包含损坏页的完整备份或文件组备份
  3. 必须有从完整备份到当前时间的所有事务日志备份
  4. 损坏的页必须可以通过DBCC CHECKDB或其他方式识别

Q3: 如何确定损坏的页ID?

A: 可以通过以下方式确定损坏的页ID:

  1. 执行DBCC CHECKDB命令,查看错误信息中的页ID
  2. 查看SQL Server错误日志,查找包含页损坏信息的错误
  3. 使用sys.dm_db_missing_index_details视图查看缺失的页

Q4: 页级恢复可以恢复到只读数据库吗?

A: 页级恢复不能直接恢复到只读数据库,需要先将数据库修改为读写状态,然后执行页级恢复,最后将数据库修改回只读状态。

Q5: 如何预防页损坏?

A: 可以通过以下方式预防页损坏:

  1. 使用可靠的存储设备
  2. 定期检查存储设备的健康状态
  3. 定期执行DBCC CHECKDB检查数据库完整性
  4. 配置数据库邮件告警,及时发现页损坏
  5. 实施3-2-1备份原则,确保有足够的备份

Q6: 页级恢复失败后如何处理?

A: 页级恢复失败后,可以采取以下措施:

  1. 查看错误信息,确定失败原因
  2. 根据错误信息采取相应措施,如更换备份文件、修改数据库状态等
  3. 如果页级恢复无法成功,可以考虑使用其他恢复方式,如全库恢复或文件组恢复
  4. 必要时联系Microsoft支持获取帮助

版本差异

SQLServer版本页级恢复特性差异
2008 R2基本页级恢复功能
2012改进页级恢复性能
2014支持使用差异备份加速页级恢复
2016支持压缩备份的页级恢复
2017支持直接从Azure Blob Storage恢复页
2019引入加速数据库恢复(ADR),提高页级恢复速度
2022改进ADR性能,支持从S3兼容存储恢复页
2024增强页级恢复可靠性,支持更多恢复场景

总结

页级恢复是SQLServer恢复操作中一种高效的恢复方式,它只恢复数据库中损坏的页,而不是整个数据库或文件组。页级恢复适用于个别页损坏的情况,可以减少恢复时间和资源消耗。在实施页级恢复时,需要遵循最佳实践,包括恢复前准备、恢复过程中的监控和恢复后的验证。通过合理的恢复策略和优化措施,可以提高页级恢复的速度和可靠性,确保在发生页损坏时,能够快速、可靠地恢复损坏的页,满足业务的RTO和RPO要求。