
最近折腾了 SQL(结构化查询语言)数据库备份和恢复,踩了几个坑,这篇把问题说清楚。先用 SSMS(SQL Server Management Studio,SQL Server 管理工具)给 DemoDb 做一次完整备份,故意改坏一条数据、删掉一条数据,再用备份覆盖恢复;最后再把同一个备份恢复成副本。下面每一步都按实际操作走,中间两次恢复失败也原样拆开讲清楚,省得你再摸一遍。
本教程使用的版本
- SQL Server 2025(17.x)——Enterprise Developer 版(可用于开发和测试),版本号 17.0.1000.7
- SQL Server Management Studio 22.11.3
- 演示数据库:DemoDb,其中包含表 dbo.DemoCustomers——与上一篇教程连接 Visual Studio 时使用的是同一个数据库
截图拍摄于 Windows Server 2022;在 Windows 10 和 Windows 11 桌面环境中,SSMS(SQL Server Management Studio,SQL Server 管理工具)里的操作步骤完全相同。
这套流程不要求你已经运行着 SQL Server。如果上一篇教程的环境还能用,直接跳到验证环节即可。
从微软下载并安装独立的免费工具 SQL Server Management Studio 22,本教程使用 22.11.3。备份和恢复功能都在 SSMS(SQL Server Management Studio,SQL Server 管理工具)中,不在 Visual Studio 里;服务器管理本来就是 SSMS 的职责。
C:\VSL-Backups。这里先提醒一个很容易踩的坑:执行备份时,读取和写入目录的是 SQL Server 服务账户,不是当前登录的 Windows 用户。本地默认安装一般没有问题;如果备份时报无访问权限,就需要给服务账户补上目录权限,具体见错误 5。setup.sql:CREATE DATABASE DemoDb;
GO
USE DemoDb;
GO
CREATE TABLE dbo.DemoCustomers ( CustomerId INT IDENTITY(1,1) PRIMARY KEY, FullName NVARCHAR(100) NOT NULL, City NVARCHAR(100) NOT NULL
);
INSERT INTO dbo.DemoCustomers (FullName, City) VALUES
(N'Amara', N'Toronto'),
(N'Jonas', N'Berlin'),
(N'Priya', N'Lagos');
SELECT CustomerId, FullName, City FROM dbo.DemoCustomers;
先做两项检查。第一项,安装脚本中的 SELECT 应当返回三行:Amara 在 Toronto、Jonas 在 Berlin、Priya 在 Lagos。这张结果表就是后续恢复后的验收基准。
第二项,检查数据库的 恢复模式。这个配置决定步骤 13 中某个选项是否会出现:
SELECT name, recovery_model_desc FROM sys.databases WHERE name = 'DemoDb';
默认安装通常会返回 FULL。新数据库会继承 model 系统数据库的恢复模式,而 model 默认就是 FULL。这里的 DemoDb 也是 FULL。如果你的结果是 SIMPLE,本文的大部分步骤仍然可用,只是步骤 13 最后的日志尾部备份部分不会出现,也没有对应的勾选项。现在可以开始备份。
打开 SSMS 并连接实例。本文环境中的服务器名称是 localhost,表示同一台电脑上的默认实例,认证方式使用 Windows Authentication。
SSMS 22 的连接对话框默认将 Encrypt 设为 Mandatory。连接本地实例时,通常还要勾选 Trust Server Certificate,原因还是上一篇文章里提到过的自签名证书。
展开 Databases,找到 DemoDb → Tables → dbo.DemoCustomers。如果表存在,说明环境已经准备完毕,可以开始第一部分。
备份入口在数据库节点上。右键 DemoDb,将鼠标移到 Tasks,然后点击 Back Up…。它是 Tasks 子菜单中的第一项。
对话框默认打开 General 页面,其中四个字段决定了后续行为:
在 Destination 区域,SSMS 会预填实例自身的 Backup 目录。先将它删除,再点击 Add…,在 Select Backup Destination 中输入自己的完整路径:
C:\VSL-Backups\DemoDb-full-2026-11-05.bak
.bak 只是约定,不是数据库引擎强制要求的扩展名,但所有工具和开发者都习惯使用这个格式,对话框本身也会提示使用它。文件名加上日期后,目录里存在多个备份时就不容易选错。
点击 OK 之前,可以先点对话框顶部的 Script 按钮,选择 Script → Script Action to New Query Window。SSMS(SQL Server Management Studio,SQL Server 管理工具)会把刚才的界面操作转换成一段等价的 T-SQL(Transact-SQL,SQL Server 扩展脚本):
BACKUP DATABASE [DemoDb] TO DISK = N'C:\VSL-Backups\DemoDb-full-2026-11-05.bak'
WITH NOFORMAT, NOINIT, NAME = N'DemoDb-Full Database Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10
这段语句里有两个选项会直接影响后面的恢复。
NOINIT 表示追加。如果目标文件已经存在,新备份会作为另一个备份集追加进去,而不是覆盖原文件。反向操作是创建全新介质集,也就是对话框 Media Options 页面中的 Overwrite all existing backup sets,对应 T-SQL 中的 FORMAT。追加是默认行为,恢复时经常因此出问题,详见错误 8。配套目录中的 01-backup-full.sql 就是这段语句,并附带一条可选的 RESTORE VERIFYONLY 命令,用来检查备份集是否完整、可读。
如果打开了额外的查询窗口,可以将其关闭。回到 Back Up Database 对话框,点击 OK,等待确认信息。
在文件资源管理器中打开 C:\VSL-Backups,可以看到 .bak 文件。本文环境中文件大小为 3,636 KB,写入时间就是刚才点击确认的时间。没有亲眼确认过的备份只是传闻,文件真正躺在磁盘上才算数。
如果从未亲眼看到备份修复真实问题,就很难真正相信恢复能力。所以这里先主动制造两类生产环境中常见的事故:把字段更新成错误值,以及直接删除一行数据。
在连接到 DemoDb 的新查询窗口中运行 02-break-it.sql:
USE DemoDb;
UPDATE dbo.DemoCustomers SET City = N'Munich' WHERE FullName = N'Jonas';
DELETE FROM dbo.DemoCustomers WHERE FullName = N'Priya';
SELECT CustomerId, FullName, City FROM dbo.DemoCustomers;
现在 SELECT 只返回 2 行:Amara 没受影响,Jonas 所在城市变成了 Munich,而他原本根本不在那里;Priya 的记录则被删掉了。先别关掉这个查询窗口,再过两步,它就会成为恢复失败的直接原因。
第一次做恢复时,几乎都会在这里走错:不要直接右键目标数据库。应当右键所有数据库上方的 Databases 节点,选择 Restore Database…。
从节点启动恢复,才能在同一个对话框中覆盖已有数据库,或者使用新名称恢复出一份副本。
在 General 页面的 Source 区域选择 Device,不要选择 Database。点击右侧的 … 按钮,在 Select backup devices 中点击 Add。
找到 C:\VSL-Backups\DemoDb-full-2026-11-05.bak,一路点击 OK 返回。设备对话框会列出这个备份文件,后续恢复将从这里读取其中的备份集。
回到 General 页面,SSMS 已经读取备份文件并填好恢复计划:
注意对话框顶部的黄色提示:“A tail-log backup of the source database will be taken. View this setting on the Options page.” SSMS 已经在提醒步骤 13 中的配置问题,下面会专门处理。
打开 Files 页面。即使不修改任何内容,也应该先看清这张表格。每一行对应备份中的一个文件,包括:
DemoDb,日志文件是 DemoDb_log;覆盖恢复时,Restore As 指向当前数据库正在使用的文件,这正是想要的结果。这张表本质上就是 T-SQL 中的 WITH MOVE 子句。第四部分会修改这些路径,错误 4 则是路径指向不存在目录时会发生的情况。
打开 Options 页面。对话框首次打开时,Overwrite 未勾选,Close existing connections 未勾选,Take tail-log backup before restore 已勾选。这三个默认值都不适合本次操作,需要手动调整:
Recovery state 保持默认的 RESTORE WITH RECOVERY。恢复完成后,数据库会直接上线并恢复可用状态。
本次操作在正式跑通之前,连续碰到了两次相同的恢复错误,但根因不同。这两种情况也很值得提前熟悉。
第一次尝试——没有勾选 Overwrite。原本以为会直接看到教材里常见的 3154 错误,实际却先遇到了 “Exclusive access could not be obtained because the database is in use”。原因是步骤 8 的查询窗口仍连接到 DemoDb。数据库占用检查发生在覆盖安全检查之前,一个没关的查询窗口就能先把恢复拦下来。
第二次尝试——勾选了 Overwrite,但没有勾选 Close existing connections。错误信息完全相同。此时 Overwrite 已经打开,剩下的阻塞因素就是数据库连接本身。完整操作过程中,Take tail-log backup 此时也仍然处于勾选状态,相当于连续配置错了三个开关,只是每次剥掉一个。
两次失败的解决方法都是步骤 13 中的 Close existing connections。自己的查询窗口也可以先执行 USE master; 切换上下文,本次正式恢复前两个动作都做了。
三个开关设置完成后,点击 OK。小型数据库的恢复通常只需要几秒,完成后 SSMS 会给出确认信息。
不要只相信“恢复成功”的弹窗,一定要验证数据。运行 05-verify.sql:
USE DemoDb;
SELECT CustomerId, FullName, City FROM dbo.DemoCustomers;
结果应当是 三行。除了行数,还要检查具体值:Jonas 回到 Berlin,Priya 的记录也重新出现了。
这次恢复不只是把行数补回三行,而是将数据库准确还原到创建备份的那个时刻,一次性撤销了 UPDATE 和 DELETE。如果结果有三行,但 Jonas 仍然在 Munich,说明选错了备份集,详见错误 8。
备份文件不变,恢复目标换掉:在原数据库旁边创建一份独立副本。这样既可以在不碰生产数据的情况下检查备份,也能给开发人员提供一份接近真实的数据集,或者把变更前的快照放在高风险修改旁边。
右键 Databases → Restore Database…,Source 选择 Device,添加同一个 .bak 文件。然后在 General 页面将 Destination Database 从 DemoDb 改为 DemoDbCopy。
关键就是输入一个尚不存在的名称,对话框标题也会随之变化。
在点击其他按钮前,先打开 Files 页面。备份中的原始文件名是 DemoDb.mdf 和 DemoDb_log.ldf,它们已经被原数据库占用。需要修改两行的 Restore As,目录保持不变,只改文件名:
...\MSSQL\DATA\DemoDbCopy.mdf
...\MSSQL\DATA\DemoDbCopy_log.ldf
接着打开 Options 页面,再次取消勾选 Take tail-log backup。使用新名称恢复时,这个选项尤其危险,详见错误 3。
Overwrite 保持未勾选。DemoDbCopy 还不存在,没有旧数据需要覆盖,SSMS 也不会要求勾选它。
点击 OK。恢复完成后,刷新对象资源管理器中的 Databases,可以看到 DemoDb 和 DemoDbCopy 并排出现。
在 DemoDbCopy 中打开查询窗口并执行同一个 SELECT,副本中同样是三行数据,而第二次恢复完全没有改动原数据库。
第四部分对应的 T-SQL(Transact-SQL,SQL Server 扩展脚本)位于 04-restore-as-copy.sql,它展示了 Files 页面修改后的最终结果。注意其中的逻辑名称来自步骤 12 的表格:
RESTORE DATABASE [DemoDbCopy]
FROM DISK = N'C:\VSL-Backups\DemoDb-full-2026-11-05.bak'
WITH MOVE 'DemoDb' TO 'C:\Program Files\Microsoft SQL Server\MSSQL17.MSSQLSERVER\MSSQL\DATA\DemoDbCopy.mdf', MOVE 'DemoDb_log' TO 'C:\Program Files\Microsoft SQL Server\MSSQL17.MSSQLSERVER\MSSQL\DATA\DemoDbCopy_log.ldf', RECOVERY;
前面操作的全是 完整备份:整个数据库保存在一个文件中,也是其他备份类型的基础。现在有了完整备份,再来看另外两种:
选择恢复模式,本质上是在决定允许丢失多少数据。SIMPLE 用日志管理的便利换取较弱的恢复能力;FULL 保留完整日志记录,可以恢复到指定时间点,代价是必须定期执行日志备份,避免日志文件无限增长。
但有一条原则比这些配置都重要:没有实际恢复过一次的备份,不能算真正可靠的备份。如果你跟着本文操作了一遍,现在已经恢复两次了。
这是安全检查正常发挥作用:你正在覆盖已有数据库,却没有勾选 Overwrite,也就是没有使用 WITH REPLACE。SQL Server 不允许悄悄用另一份数据库备份替换现有数据库。
微软 SSMS 文档中的完整错误示例是:“System.Data.SqlClient.SqlError: The backup set holds a backup of a database other than the existing 'Sales' database. (Microsoft.SqlServer.SmoExtended)”,实际使用时会把 Sales 换成你的数据库名。
解决方法:如果确实要覆盖,在 Options 页面勾选 Overwrite the existing database (WITH REPLACE);如果目的不是覆盖,就立即停止,这个错误刚刚替你避免了一次事故。
还有一个执行顺序上的细节:如果仍有连接正在访问目标数据库,你会先遇到错误 2,因为数据库占用检查发生在前面。
这是本次操作实际碰到两次的错误。恢复需要独占访问权限,但某个应用程序、服务,甚至自己的 SSMS 查询窗口仍在连接目标数据库。
解决方法:在 Options 页面勾选 Close existing connections to destination database。SSMS 会将数据库切换到单用户模式,完成恢复后再切回多用户模式。恢复前也可以把自己的查询窗口切换到 master:USE master;。
03-restore-overwrite.sql 中包含这个勾选项对应的 T-SQL 写法。
第一种表现:你要恢复到旧备份,也就是第三部分,但 Take tail-log backup before restore 仍然保持勾选。在 FULL 恢复模式下,这是默认行为。日志尾部备份会保留你正准备撤销的那些变更;如果备份文件又无法写入,恢复甚至会在开始前就失败。需要回到旧状态时,必须取消勾选。
第二种表现,也是更危险的一种:使用新名称恢复时,如果保留该选项,可能让原数据库停留在 Restoring 状态。微软恢复文档记录了这一场景,以及伴随出现的“the tail of the log for the database has not been backed up”错误。第四部分同样要取消勾选。
一条规则就能覆盖两种情况:日志尾部备份用于恢复前把数据库向前推进,不用于回到旧状态或横向复制。
Files 页面中的 Restore As 路径来自创建备份的那台机器。恢复其他服务器创建的备份,或者在第四部分中粗心改名后,这些路径可能指向本机不存在的目录,最终创建文件时失败。
解决方法:在 Files 页面中修正路径,或者在 T-SQL 中使用 WITH MOVE,具体写法可以参考 04-restore-as-copy.sql。同时检查新文件名是否与其他数据库的现有文件冲突。
备份和恢复由 SQL Server 服务账户执行,不是当前登录用户。你的账户能够打开目录,不代表服务账户也能访问,网络共享目录尤其常见,而且权限问题通常要等操作真正运行时才暴露。
解决方法:给 SQL Server 服务账户授予备份目录的读写权限,或者把备份文件放到服务账户已经拥有访问权限的位置。实例自身的 Backup 目录始终满足这个要求。
备份只能单向前进。SQL Server 2025 创建的备份不能恢复到 SQL Server 2022 或更旧版本,否则会因版本不匹配而失败。
从旧版本恢复到新版本没有问题。例如,将 SQL Server 2022 的备份恢复到 SQL Server 2025 上可以成功,恢复过程中还会升级数据库的内部格式。制定迁移方案时要记住:备份恢复可以升级,不能降级。
常见原因有两个。
第一,还没有创建完整备份。差异备份和日志备份都依赖完整备份,应当先执行第一部分。
第二,数据库使用的是 SIMPLE 恢复模式,该模式根本不支持日志备份。可以通过步骤 D 的查询检查恢复模式。如果业务需要日志备份,数据库必须改为 FULL,并同时建立日志备份计划。
还记得步骤 5 中的 NOINIT 吗?再次备份到同一个 .bak 文件时,新备份集会追加到文件末尾,而不是替换原内容。
以后从这个文件恢复时,Backup sets to restore 列表会展示文件中的所有备份集。需要根据备份日期和位置选择,并确认勾选的是目标备份集。把上个月的备份集恢复到今天的数据上,一次例行恢复就会直接变成事故。
设备对话框中的 Contents 按钮可以在不启动恢复的情况下查看文件包含的备份集。
自己试一遍,10 分钟,代码都在配套目录中:
- 运行
setup.sql,使用01-backup-full.sql创建完整备份,再通过02-break-it.sql破坏数据。- 通过 SSMS 对话框覆盖恢复,也就是步骤 9~15。第一次故意不勾选 Close existing connections,并保持损坏数据的查询窗口处于打开状态,先阅读错误,再设置步骤 13 的三个开关并完成恢复。
- 运行
05-verify.sql,认真检查验收结果:三行数据,并且 Jonas 位于 Berlin。- 附加练习:使用
04-restore-as-copy.sql将同一个备份恢复为DemoDbPractice。记得先修改 Restore As 路径或 WITH MOVE 目标路径。
可以在评论区留下你的备份文件大小,单位是 KB,我会逐条查看。
执行差异备份或日志备份之前,必须先做完整备份吗?
必须。差异备份保存的是上一次完整备份之后的变化,事务日志备份链也从完整备份开始。没有完整备份,其他备份就失去了恢复基础,所以第一部分必须先做。
能把 SQL Server 2025 的备份恢复到 SQL Server 2022 吗?
不能。恢复只能进入相同或更高版本,详见错误 6。如果要降级,需要导出数据库结构并迁移数据,不能直接使用备份恢复。
恢复数据库时会不会不提示就覆盖原数据库?
不会。没有 Overwrite (WITH REPLACE) 时,SQL Server 的安全检查会阻止覆盖已有数据库,详见错误 1。REPLACE 是一项明确操作:界面中需要手动勾选,脚本中则需要明确输入。
RESTORE VERIFYONLY 验证通过,能证明备份一定可用吗?
它只能证明备份集完整、可读,值得执行,01-backup-full.sql 也包含了这条命令。但它无法证明目标服务器上的路径、权限和恢复环境一定满足要求。
真正完整的验证只有测试恢复。第四部分恢复出的副本,就是一次针对副本执行的完整测试。
配套目录包含本文用到的全部脚本,执行顺序如下:setup.sql、SSMS 生成的备份语句 01-backup-full.sql、故意破坏数据的 02-break-it.sql、两种恢复脚本 03-restore-overwrite.sql 和 04-restore-as-copy.sql,以及恢复验证查询 05-verify.sql。
https://github.com/sajjad037/visual-studio-learn-code/tree/main/topic-08-backup-restore-ssms
本文中的操作和错误说明均可在微软官方文档中查到,访问时间为 2026 年 11 月 4 日至 5 日。
说明:Visual Studio Learn 的内容由读者支持。部分链接可能属于推广链接,频道可能获得少量佣金,不会增加你的成本。教程只推荐实际使用过的工具,是否存在推广关系也不会影响结论。