一次 SQL Server 2019 数据库迁移实战

举报
Lucifer三思而后行 发表于 2026/10/27 16:27:01 2026/10/27
【摘要】 前言 最近迁移了一套 SQL Server 2019 数据库,原服务器使用 Windows Server 2019,新服务器换成 Windows Server 2025,数据库版本保持不变。 这次主要涉及 2 个数据库,一个只迁移结构,另一个需要连同数据一起迁移。另外,还要处理业务账号、权限和自动备

前言

最近迁移了一套 SQL Server 2019 数据库,原服务器使用 Windows Server 2019,新服务器换成 Windows Server 2025,数据库版本保持不变。

这次主要涉及 2 个数据库,一个只迁移结构,另一个需要连同数据一起迁移。另外,还要处理业务账号、权限和自动备份任务。

本文记录一下整个过程。

迁移之前,先确认源端配置

首先检查 SQL Server 的版本、排序规则和身份验证模式。

SELECT
    @@SERVERNAME AS ServerName,
    SERVERPROPERTY('ProductVersion') AS ProductVersion,
    SERVERPROPERTY('Edition') AS Edition,
    SERVERPROPERTY('Collation') AS Collation,
    SERVERPROPERTY('IsIntegratedSecurityOnly') AS WindowsAuthOnly;

源端使用 SQL Server 2019 Standard,默认实例 MSSQLSERVER,实例排序规则为 Chinese_PRC_CI_AS,采用混合身份验证模式。

接着检查数据库清单:

SELECT
    name,
    state_desc,
    compatibility_level,
    collation_name,
    recovery_model_desc
FROM sys.databases
WHERE database_id > 4
ORDER BY name;

检查时发现,部分数据库的排序规则与实例不同。

因此,新服务器安装时保持实例排序规则一致,而数据库自身的排序规则和兼容级别则按照源端保留。

再检查数据库文件:

SELECT
    DB_NAME(database_id) AS DatabaseName,
    name AS LogicalName,
    type_desc,
    physical_name,
    CAST(size * 8.0 / 1024 AS DECIMAL(18, 2)) AS SizeMB
FROM sys.master_files
WHERE database_id > 4
ORDER BY database_id, file_id;

这一步主要是确认数据库文件位置、大小,以及目标服务器的磁盘空间是否足够。

新服务器怎么安装和配置?

新服务器使用 Windows Server 2025 Datacenter,安装 SQL Server 2019 Standard。

安装时选择数据库引擎和复制组件,使用默认实例 MSSQLSERVER,身份验证模式选择 Mixed Mode。

数据库文件目录重新规划:

C:\Program Files\Microsoft SQL Server\
    SQL Server 程序及系统数据库

D:\MSSQL\Data\
    用户数据库数据文件

D:\MSSQL\Log\
    用户数据库日志文件

D:\MSSQL\TempDB\
    TempDB 文件

D:\MSSQL\Backup\
    数据库备份

这台服务器有 4 个逻辑 CPU,安装时配置了 4 个 TempDB 数据文件,每个初始大小 256MB、自动增长 256MB,日志文件初始大小 512MB。

MAXDOP 设置为 4,max server memory 设置为 12800MB。

安装完成后,通过 SSMS 检查数据库实例:

SELECT
    @@VERSION AS VersionInfo,
    SERVERPROPERTY('Edition') AS Edition,
    SERVERPROPERTY('Collation') AS Collation;

再检查几个关键参数:

SELECT
    name,
    value_in_use
FROM sys.configurations
WHERE name IN (
    'max degree of parallelism',
    'cost threshold for parallelism',
    'max server memory (MB)',
    'min server memory (MB)'
);

同时检查默认数据和日志目录:

SELECT
    SERVERPROPERTY('InstanceDefaultDataPath') AS DefaultDataPath,
    SERVERPROPERTY('InstanceDefaultLogPath') AS DefaultLogPath;

实例正常启动,参数和路径也符合预期后,开始迁移数据库。

只迁移数据库结构,怎么做?

这次 APP_DB 不需要业务数据,因此采用 SSMS 的 Generate Scripts 功能导出数据库结构。

操作路径:

数据库 → Tasks → Generate Scripts

对象选择:

Script entire database and all database objects

然后进入 Advanced,主要确认以下选项:

配置项 设置
Types of data to script Schema only
Script Indexes True
Script Primary Keys True
Script Foreign Keys True
Script Unique Keys True
Script Check Constraints True
Script Defaults True
Script Triggers True

生成的脚本保存为 Unicode 格式的 .sql 文件,再复制到目标服务器执行。

这里要注意,生成数据库结构脚本不等于只包含 CREATE TABLE。

索引、约束、视图、存储过程、函数、触发器以及部分用户和权限语句,都可能出现在脚本中。

本次执行过程中就遇到了:

Msg 15151
Cannot find the user 'legacy_user',
because it does not exist or you do not have permission.

原因是脚本包含了针对数据库用户的授权语句,但目标端还没有对应的用户。

这类问题需要单独处理 Login 和 User,不能因为大部分脚本执行成功,就认为所有对象和权限已经完整迁移。

怎么确认数据库结构迁移完整?

脚本执行完成后,先统计对象数量。

USE [APP_DB];
GO

SELECT
    type_desc,
    COUNT(*) AS ObjectCount
FROM sys.objects
WHERE is_ms_shipped = 0
  AND type IN ('U', 'V', 'P', 'FN', 'IF', 'TF', 'TR')
GROUP BY type_desc
ORDER BY type_desc;

本次目标端检查结果:

对象类型 数量
用户表 84
视图 18
存储过程 7
标量函数 2

再检查索引和约束:

USE [APP_DB];
GO

SELECT 'INDEX' AS ObjectType, COUNT(*) AS ObjectCount
FROM sys.indexes i
JOIN sys.tables t
    ON i.object_id = t.object_id
WHERE t.is_ms_shipped = 0
  AND i.index_id > 0
  AND i.is_primary_key = 0
  AND i.is_unique_constraint = 0

UNION ALL

SELECT 'PRIMARY KEY', COUNT(*)
FROM sys.key_constraints
WHERE type = 'PK'

UNION ALL

SELECT 'FOREIGN KEY', COUNT(*)
FROM sys.foreign_keys

UNION ALL

SELECT 'UNIQUE CONSTRAINT', COUNT(*)
FROM sys.key_constraints
WHERE type = 'UQ'

UNION ALL

SELECT 'CHECK CONSTRAINT', COUNT(*)
FROM sys.check_constraints

UNION ALL

SELECT 'DEFAULT CONSTRAINT', COUNT(*)
FROM sys.default_constraints;

目标端检查到 62 个主键、4 个唯一约束、211 个默认约束,以及其他索引对象。

这些数量需要和源端采用相同 SQL 对比,才能判断是否存在遗漏。

由于 APP_DB 只需要结构,还要确认没有业务数据:

USE [APP_DB];
GO

SELECT
    COALESCE(SUM(p.rows), 0) AS TotalRows
FROM sys.partitions p
JOIN sys.tables t
    ON p.object_id = t.object_id
WHERE p.index_id IN (0, 1)
  AND t.is_ms_shipped = 0;

最终返回:

TotalRows
---------
0

说明目标端用户表没有业务数据,符合本次迁移要求。

另一套数据库需要连同数据一起迁移

SCHEDULER_DB 需要保留原有数据,因此采用完整备份恢复。

先在源端执行:

BACKUP DATABASE [SCHEDULER_DB]
TO DISK = N'E:\SQLBackup\SCHEDULER_DB_full.bak'
WITH
    INIT,
    COMPRESSION,
    CHECKSUM,
    STATS = 10;
GO

备份完成后,将文件复制到目标服务器。

恢复前先检查备份集:

RESTORE VERIFYONLY
FROM DISK = N'D:\MSSQL\Backup\SCHEDULER_DB_full.bak'
WITH CHECKSUM;
GO

再查看逻辑文件名:

RESTORE FILELISTONLY
FROM DISK = N'D:\MSSQL\Backup\SCHEDULER_DB_full.bak';
GO

这一步很重要。

因为源服务器数据库文件位于 E: 盘,而新服务器规划到了 D:\MSSQL。

如果直接按照原路径恢复,就会出现:

The path specified by
"E:\Program Files\Microsoft SQL Server\..."
is not in a valid directory.

所以恢复时需要使用 WITH MOVE。

下面的逻辑文件名为脱敏示例,实际执行时必须以 RESTORE FILELISTONLY 返回的结果为准。

RESTORE DATABASE [SCHEDULER_DB]
FROM DISK = N'D:\MSSQL\Backup\SCHEDULER_DB_full.bak'
WITH
    MOVE N'SCHEDULER_DB'
        TO N'D:\MSSQL\Data\SCHEDULER_DB.ndf',
    MOVE N'SCHEDULER_DB_log'
        TO N'D:\MSSQL\Log\SCHEDULER_DB_log.ldf',
    RECOVERY,
    STATS = 10;
GO

恢复成功后检查数据库状态:

SELECT
    name,
    state_desc,
    compatibility_level,
    collation_name
FROM sys.databases
WHERE name = N'SCHEDULER_DB';

结果显示数据库 ONLINE,兼容级别为 100,排序规则为 Chinese_PRC_CI_AS。

再统计表数据:

USE [SCHEDULER_DB];
GO

SELECT
    s.name AS SchemaName,
    t.name AS TableName,
    SUM(p.rows) AS RowCount
FROM sys.tables t
JOIN sys.schemas s
    ON t.schema_id = s.schema_id
JOIN sys.partitions p
    ON t.object_id = p.object_id
WHERE p.index_id IN (0, 1)
GROUP BY s.name, t.name
ORDER BY s.name, t.name;

相关业务表的数据已经恢复。

数据库迁移完了,账号为什么还会报错?

数据库迁移完成后,业务方使用客户端连接新服务器,出现:

Login failed for user 'app_user'. (18456)

开始检查发现,目标端缺少对应的 Login。

创建账号并处理数据库 User 映射后,业务已经可以正常连接,两个数据库的基本读写权限检查也全部返回 1。

原以为账号问题处理完了,结果业务继续操作时又出现:

CREATE TABLE permission denied in database 'APP_DB'.

接着又报:

The specified schema name "dbo"
either does not exist or you do not have permission to use it.

当时第一反应是缺少 DDL 权限,于是开始检查 CREATE TABLE 和 Schema 权限。

但仔细想想,问题有点不对。

源服务器上业务一直正常运行,为什么迁移之后会连续缺少这么多权限?

与其继续一个个补授权,不如直接看看源服务器上这个账号到底拥有什么权限。

最后发现,源端账号竟然是 sysadmin

先在源服务器检查 Login:

USE master;
GO

SELECT
    name,
    type_desc,
    is_disabled,
    default_database_name
FROM sys.server_principals
WHERE name = N'app_user';

账号正常,默认数据库指向另一套业务库。

接着查询服务器角色:

SELECT
    m.name AS LoginName,
    r.name AS ServerRole
FROM sys.server_role_members rm
JOIN sys.server_principals r
    ON rm.role_principal_id = r.principal_id
JOIN sys.server_principals m
    ON rm.member_principal_id = m.principal_id
WHERE m.name = N'app_user';

结果很直接:

LoginName    ServerRole
-----------  ----------
app_user     sysadmin
app_user     dbcreator

原来源端业务账号不仅是 dbcreator,还是 sysadmin。

这就解释了之前为什么会连续出现权限问题。

源端账号本来就有整个 SQL Server 实例的管理权限,而目标端最初只配置了普通业务账号的读写权限。

两边的权限根本不在一个级别。

同时,在源端查询两个业务数据库的数据库用户、数据库角色及显式授权时,没有找到单独的 app_user 记录。

这也不奇怪。

对于 sysadmin 成员,SQL Server 在数据库中通常以 dbo 身份执行操作,不需要依靠普通数据库 User 的显式授权来获得权限。

所以之前只检查数据库级权限,实际上漏掉了最关键的服务器角色。

把源端权限恢复到目标服务器

既然本次要求与源端保持一致,就在确认授权范围后,将原有服务器角色恢复到目标端。

USE master;
GO

ALTER SERVER ROLE [sysadmin]
ADD MEMBER [app_user];

ALTER SERVER ROLE [dbcreator]
ADD MEMBER [app_user];
GO

这里需要说明一下:

sysadmin 已经包含 dbcreator 所需的管理能力,保留两个角色成员关系只是为了与源端配置一致。

但 sysadmin 的权限范围是整个 SQL Server 实例,并不是只对两个业务数据库生效。

因此,这种做法适用于本次经过确认的原权限恢复,不建议把它作为普通业务账号的默认授权方式。

执行完成后检查:

SELECT
    sp.name AS LoginName,
    IS_SRVROLEMEMBER('sysadmin', sp.name) AS IsSysadmin,
    IS_SRVROLEMEMBER('dbcreator', sp.name) AS IsDbcreator,
    sp.is_disabled
FROM sys.server_principals sp
WHERE sp.name = N'app_user';

目标端返回:

LoginName    IsSysadmin    IsDbcreator    is_disabled
-----------  ------------  -------------  -----------
app_user     1             1              0

至此,源端最关键的服务器角色已经恢复。

目标端账号的默认数据库与源端不同,可以根据业务连接方式调整:

ALTER LOGIN [app_user]
WITH DEFAULT_DATABASE = [APP_DB];
GO

默认数据库只影响登录后未明确指定数据库时的初始上下文,不决定账号是否具有 sysadmin 权限。

最后,还需要让业务端重新建立连接,实际测试建表等操作,才能确认之前的 DDL 报错已经消除。

自动备份任务也不能漏

数据库迁移完成后,还需要处理 SQL Server Agent。

因为数据库备份恢复或结构脚本迁移,并不会自动把原服务器上的 Agent Job 一起迁移。

这次针对两个业务数据库,分别重新配置了自动备份任务。

备份策略如下:

数据库 执行时间 备份类型 保留时间
APP_DB 每天 02:00 完整备份 7 天
SCHEDULER_DB 每天 02:30 完整备份 7 天

每个任务分成三个步骤:

01_FULL_BACKUP
      ↓
02_VERIFY_BACKUP
      ↓
03_DELETE_OLD_BACKUPS

完整备份使用压缩和校验和:

DECLARE @BackupFile NVARCHAR(4000);

SET @BackupFile =
    N'D:\MSSQL\Backup\APP_DB\APP_DB_FULL_'
    + CONVERT(CHAR(8), GETDATE(), 112)
    + N'_'
    + REPLACE(CONVERT(CHAR(8), GETDATE(), 108), ':', '')
    + N'.bak';

BACKUP DATABASE [APP_DB]
TO DISK = @BackupFile
WITH
    COMPRESSION,
    CHECKSUM,
    STATS = 10;

备份验证步骤使用 RESTORE VERIFYONLY 检查本次生成的备份文件。

历史备份清理步骤则删除超过 7 天的备份文件,避免长期积累占满磁盘。

两个任务创建完成后,分别手动执行一次:

USE msdb;
GO

EXEC dbo.sp_start_job
    @job_name = N'APP_DB_FULL_DAILY_0200';
GO

EXEC dbo.sp_start_job
    @job_name = N'SCHEDULER_DB_FULL_DAILY_0230';
GO

接着检查任务历史:

SELECT
    j.name AS JobName,
    h.step_id,
    h.step_name,
    CASE h.run_status
        WHEN 0 THEN 'FAILED'
        WHEN 1 THEN 'SUCCEEDED'
        WHEN 2 THEN 'RETRY'
        WHEN 3 THEN 'CANCELED'
        WHEN 4 THEN 'IN PROGRESS'
    END AS RunStatus,
    h.message
FROM msdb.dbo.sysjobs j
JOIN msdb.dbo.sysjobhistory h
    ON j.job_id = h.job_id
WHERE j.name IN (
    N'APP_DB_FULL_DAILY_0200',
    N'SCHEDULER_DB_FULL_DAILY_0230'
)
ORDER BY h.instance_id DESC;

实际检查中,两套数据库的三个步骤均显示 SUCCEEDED。

最后检查备份历史:

SELECT TOP (20)
    bs.database_name,
    bs.backup_finish_date,
    CAST(
        bs.backup_size / 1024.0 / 1024
        AS DECIMAL(18, 2)
    ) AS SizeMB,
    bs.has_backup_checksums,
    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 IN (
    N'APP_DB',
    N'SCHEDULER_DB'
)
  AND bs.type = 'D'
ORDER BY bs.backup_finish_date DESC;

两个数据库都能查询到新生成的完整备份记录,并且启用了备份校验和。

需要注意,RESTORE VERIFYONLY 不能代替实际恢复演练,7 天保留策略也还需要后续观察定时执行情况。

最后总结

这次 SQL Server 迁移,数据库结构和数据恢复都比较顺利,反而是账号权限花了不少时间。

一开始只检查了数据库 User 和显式授权,后来才发现源端账号属于 sysadmin。权限模型没有迁移完整,自然会不断出现新的报错。

以后再做 SQL Server 迁移,除了数据库本身,Login、SID、服务器角色、数据库权限和 Agent Job 也得一起检查。

这些东西不在业务表里,但少了任何一项,都可能影响业务正常使用。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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