两年前的 SQL Server 全备,竟然还能救回误删的数据?

举报
Lucifer三思而后行 发表于 2026/11/11 10:28:44 2026/11/11
【摘要】 前言 今天上班路上,突然收到业务消息,说 SQL Server 中一张表的历史数据被误删了,需要尽快恢复。 到了公司,立刻登录 SQL Server 服务器检查,本来想着找一份最近的全备,把数据恢复出来再导回生产库就行。结果翻了一圈备份目录,发现能找到的全备竟然还是两年前的,看来这次没那么简单了。

👋 我是三笠,一名专注数据库运维与架构的 DBA,平时分享数据库实战经验与技术笔记,个人网站:ora100.com

前言

今天上班路上,突然收到业务消息,说 SQL Server 中一张表的历史数据被误删了,需要尽快恢复。

到了公司,立刻登录 SQL Server 服务器检查,本来想着找一份最近的全备,把数据恢复出来再导回生产库就行。结果翻了一圈备份目录,发现能找到的全备竟然还是两年前的,看来这次没那么简单了。

好在后面确认了可用的日志恢复条件,最终通过辅助数据库找回了历史数据,并成功回填到生产表。

数据虽然找回来了,但检查备份的过程中,又发现了不少问题。这次恢复算是完成了,后面的备份管理还得好好整理一下。

本文记录一下完整的恢复过程,分享给大家。

一张表的历史数据没了,生产库还在正常运行

接到消息后,先登录数据库确认情况。

数据库版本是 SQL Server 2016 SP2 Standard,实例运行正常,业务系统也能正常连接,没有出现数据库损坏或服务中断。

先查一下数据库状态和恢复模式:

SELECT
    name,
    state_desc,
    recovery_model_desc
FROM sys.databases
WHERE name = N'BusinessDB';

再看看这张表还剩多少数据:

USE BusinessDB;
GO

SELECT COUNT(*) AS RowCount
FROM dbo.SalesDetail;

结果只有:

RowCount
--------
31133

业务反馈,这张销售明细表原本有不少历史数据,现在只剩下 31,133 行,明显少了很多。

由于暂时无法确定数据被删除的具体时间,还得先看看现有备份能恢复到什么程度。

考虑到生产数据库还在正常运行,不可能为了恢复一张表就把整个库回退。所以打算先恢复一套独立数据库,把历史数据找回来,核对没问题后再回填到生产表。

翻了半天备份,最近的全备竟然是两年前的

先查 SQL Server 的备份历史:

SELECT
    bs.database_name,
    CASE bs.type
        WHEN 'D' THEN 'FULL'
        WHEN 'I' THEN 'DIFF'
        WHEN 'L' THEN 'LOG'
    END AS BackupType,
    bs.backup_start_date,
    bs.backup_finish_date,
    bs.is_copy_only,
    bmf.physical_device_name
FROM msdb.dbo.backupset bs
JOIN msdb.dbo.backupmediafamily bmf
    ON bs.media_set_id = bmf.media_set_id
WHERE bs.database_name = N'BusinessDB'
ORDER BY bs.backup_finish_date DESC;

检查了一下服务器上的备份目录,找到一份 2024 年 10 月 8 日的全备,而这次数据误删发生在 2026 年 11 月 10 日,已经过去两年多了。

这么老的备份,还能恢复出现在的数据吗?

SQL Server 在完整恢复模式下,可以通过全备和连续的事务日志备份,将数据库恢复到指定时间点。

不过,前提是日志链完整。 如果中间缺少必要的日志备份,导致日志无法衔接,即使有全备,也没办法恢复到事故发生前的状态。

所以先不急着还原,继续检查事务日志备份,看看能不能找到一条完整的恢复链。

先把当前日志备份下来

数据库还在运行,数据删除之后也已经过去了一段时间,为了尽可能保留可用于恢复的日志,先做了一份紧急日志备份:

BACKUP LOG [BusinessDB]
TO DISK = N'E:\SQLBackup\BusinessDB_emergency.trn'
WITH
    COPY_ONLY,
    COMPRESSION,
    CHECKSUM,
    STATS = 10;
GO

这里使用 COPY_ONLY,是为了尽量不影响原有日志备份安排。

不过要注意,COPY_ONLY 日志备份并不能补齐之前缺失的日志。如果中间已经存在无法衔接的日志区间,这份备份也解决不了。

备份完成后,检查备份头信息:

RESTORE HEADERONLY
FROM DISK = N'E:\SQLBackup\BusinessDB_emergency.trn';
GO

主要看 FirstLSN、LastLSN、DatabaseBackupLSN,以及恢复分支相关信息。

后面结合备份记录和实际恢复验证,确认了可用的恢复链。

这一步很重要。

遇到误删数据,尤其是还没确定删除时间的时候,最好先把当前能够保留的日志和备份文件保护好,别急着在生产库上操作。

不动生产库,先恢复一套辅助数据库

确定恢复方案后,准备了一套独立数据库:

BusinessDB_RECOVER

这样就不用直接碰生产库,可以先把历史数据恢复出来,再慢慢核对。

先查看全备中的逻辑文件名:

RESTORE FILELISTONLY
FROM DISK = N'E:\SQLBackup\BusinessDB_20241008.bak';
GO

确认文件名和存放路径后,开始恢复全备:

RESTORE DATABASE [BusinessDB_RECOVER]
FROM DISK = N'E:\SQLBackup\BusinessDB_20241008.bak'
WITH
    MOVE N'BusinessDB_Data'
        TO N'E:\SQLData\BusinessDB_RECOVER.mdf',
    MOVE N'BusinessDB_Log'
        TO N'E:\SQLData\BusinessDB_RECOVER_log.ldf',
    NORECOVERY,
    STATS = 10;
GO

这里使用 NORECOVERY,因为后面还要继续应用事务日志。

接着按照已经确认的日志链顺序恢复日志,最后一步再指定恢复时间点。

例如:

RESTORE LOG [BusinessDB_RECOVER]
FROM DISK = N'E:\SQLBackup\BusinessDB_emergency.trn'
WITH
    STOPAT = '2026-11-10T08:00:00',
    RECOVERY,
    STATS = 10;
GO

这里的恢复时间只是示例,实际需要根据数据误删的时间来确定。

需要注意的是,两年前的全备并不能直接接上事故当天的日志备份。如果中间还有其他必需的日志备份,就要按照日志链顺序依次恢复,最后一步再使用 STOPAT 和 RECOVERY。

这次按照确认好的恢复顺序,成功将辅助数据库恢复到指定时间点,目标表的历史数据也找回来了。

历史数据找回来了,怎么回填生产表?

先对比一下两边的数据量:

SELECT 'Production' AS DBName, COUNT(*) AS RowCount
FROM BusinessDB.dbo.SalesDetail
UNION ALL
SELECT 'Recovered', COUNT(*)
FROM BusinessDB_RECOVER.dbo.SalesDetail;

结果如下:

数据来源 数据量
生产表 31,133 行
恢复表 276,304 行
相差 245,171 行

恢复库中有 276,304 行数据,而生产表只剩下 31,133 行,历史数据总算找回来了。

不过,生产表里还有现存数据,不能直接覆盖,得先确认这些数据是否需要保留。

回填前检查了一下表结构,这张表共有 16 个字段,没有主键、索引、外键和触发器,但有 27 个依赖存储过程。由于没有主键,也没办法直接通过主键判断哪些数据需要补回。

最后经过业务核对,确认可以用恢复库的数据整表替换生产表,并在操作期间暂停相关业务写入,避免回填过程中产生新的数据。

回填前,先备份生产表

确定回填方案后,先把生产表中现有的 31,133 行数据备份一份,避免后续操作出现问题时无法回退。

USE BusinessDB;
GO

SELECT *
INTO dbo.SalesDetail_BAK_20261110
FROM dbo.SalesDetail;
GO

这里直接使用 SELECT INTO 保存原表数据,操作比较简单。不过这种方式不会复制原表的索引、约束等对象,不能代替正式的数据库备份。

确认备份完成后,开始回填:

USE BusinessDB;
GO

SET XACT_ABORT ON;
GO

BEGIN TRY
    BEGIN TRAN;

    DELETE FROM dbo.SalesDetail;

    INSERT INTO dbo.SalesDetail
    SELECT *
    FROM BusinessDB_RECOVER.dbo.SalesDetail;

    IF (SELECT COUNT_BIG(*) FROM dbo.SalesDetail) <> 276304
        THROW 50001, 'Row count validation failed', 1;

    COMMIT TRAN;
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRAN;

    THROW;
END CATCH;
GO

这里加了事务控制,如果中途报错,就回滚本次操作,避免生产表只恢复了一部分数据。

这里的回填 SQL 只是简化示例,实际操作还需要根据表结构和业务情况调整。如果误删之后产生了需要保留的新数据,就不能直接整表替换。

276,304 行数据,恢复成功

回填完成后,再次检查生产表的数据量:

SELECT COUNT(*) AS RowCount
FROM BusinessDB.dbo.SalesDetail;

查询结果:

RowCount
--------
276304

生产表从原来的 31,133 行恢复到了 276,304 行。又抽查了部分关键业务记录,确认数据没有问题,业务验证也顺利通过。

这次恢复使用的辅助数据库占用了大约 12GB 磁盘空间,确认数据恢复完成、不再需要保留后,便将其删除。

USE master;
GO

ALTER DATABASE [BusinessDB_RECOVER]
SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
GO

DROP DATABASE [BusinessDB_RECOVER];
GO

删除前再次确认数据库名称,避免误删生产库。

到这里,这次数据恢复就算完成了。

数据找回来了,但备份的问题还没解决

这次恢复折腾下来,最麻烦的其实不是执行恢复命令,而是确认手里的备份到底能不能用,以及怎么把数据安全地放回生产库。

毕竟能找到的全备已经是两年前的了,生产数据库又不能随便动。

好在最后通过辅助库找回了历史数据,生产表也恢复到了 276,304 行。

不过,恢复结束后又检查了一遍数据库备份情况,发现问题还不少。

SQL Server Agent 的运行状态、事务日志备份、历史备份清理都存在需要调整的地方。尤其是常规日志备份长期没有正常执行,日志文件空间占用也已经比较高。

这次数据虽然找回来了,但不能每次出问题都指望还能找到可用的备份。

所以后面又花了些时间,把 SQL Server 的 FULL、LOG 自动备份和历史文件清理策略重新整理了一遍。

写文章之外,我也在持续整理自己的数据库学习与运维平台 ORA100。

一些平时用到的数据库 SQL、运维脚本和排障笔记,都会慢慢整理到网站上,方便以后遇到类似问题时直接查找。

网站地址:ora100.com

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。