ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SQL Server迁移必备:SSMS生成脚本导出可选表与数据

SQL Server迁移必备:SSMS生成脚本导出可选表与数据 最近有个活儿数据库要从测试环境挪到本地几十张表数据量不大但不能整库备份恢复因为只需要其中几张业务表而且最后要交付的是纯SQL文件。这类需求在开发、运维、实施场景里太常见了SQL Server用户第一个想到的就是SSMS自带的“生成脚本”功能——把选中的表结构连同数据一起dump成.sql文件拿到目标库执行一遍数据就过去了。这个方式核心其实是两件事导出可选表、导出可选数据最终产物就是一段一段的CREATE TABLE和INSERT语句。这种方案的优点很直接脚本文件即人类可读可放进版本库出错也好排查跨实例迁移比如从SQL Server 2008迁到2019或者Azure SQL不受备份文件版本限制还可以筛选表、筛选行数据比较灵活。但它也有坑大数据量表一条条INSERT性能很糟糕外键和标识列顺序处理不当就会报错。这篇文章我就结合自己的实操经验把从导出到导入、再到踩坑修复的完整过程捋一遍给做SQL Server迁移维护的朋友留个参考。1. 方案选型为什么用SQL脚本而不是备份文件1.1 脚本方式的适用场景做数据库迁移大家第一反应往往是“备份-还原”因为备份文件是物理级复制快且完整。可很多场景下备份还原是过度的甚至不可用。比如你要把某个库的20张表单独迁到另一个库里备份还原会把100张表全带过来再比如你面对的是云上实例没有文件系统访问权限备份文件根本拷不出来还有一种常见情况是跨大版本迁移旧备份在新版本上可能因为兼容级别、元数据格式问题还原不了。这时候“脚本化”就是最稳妥的迁移方式。SQL脚本迁移的本质是逻辑级导出把对象定义表结构、索引、约束、触发器翻译成DDL语句把数据翻译成DML语句。它不关心源库物理文件结构只关心逻辑结构。所以版本差异、实例差异都能兼容。代价就是速度慢、大数据量不友好而且对象之间的依赖关系需要靠脚本生成器自动排序或者靠语句顺序维护。1.2 SSMS“生成脚本”与bcp/sqlcmd的分工SQL Server生态里做逻辑导出不止一种工具。最常用的是SSMS的“生成脚本”向导它适合中小型数据量、且要精确控制导出对象范围的场景。另外还有几个命令行工具也值得了解bcp用于批量导出表数据为数据文件适合海量数据产出是文本文件而不是带结构的SQL脚本sqlcmd常用于执行脚本也能配合:r语法拼接脚本sqlpackage.exedacpac方式适合做架构对比发布。各有分工不能互相替代。我的选择逻辑很简单如果单表行数在几十万以内、表数量在几十张以内、且后续还要给同事审核脚本内容就选SSMS生成脚本如果单表上千万行还要求速度就别用脚本了直接用bcp OUT导出或定期备份增量否则那个包含几百万条INSERT的脚本光执行时间就够你喝几杯茶的。提示这里说的“可选表”完全可以用生成脚本向导勾选但要注意是否连同数据一起导出取决于向导“高级脚本选项”里的“要编写脚本的数据的类型”默认值可能只写架构不写数据很多人第一次用就在这里翻了车。2. 导出前的准备正确理解“可选表”和“依赖项”2.1 勾选对象要认清表之间的引用关系打开SSMS右键要导出的数据库选“任务 → 生成脚本”。向导第一步会让你选择对象有两个选项整库、或者自己选择特定数据库对象。选“选择特定数据库对象”然后下面列出四类表、视图、存储过程、用户定义函数等。想导出哪张表就勾选哪张表。看起来很直白但坑在依赖关系。比如你有两张表订单Order和订单明细OrderDetail明细表有个外键引用订单表。如果你只勾选订单明细而不勾选订单生成脚本会默认不处理外键最终你拿着脚本去目标库执行CREATE TABLE可能顺利但后续的约束、索引就要单独想办法。反向也头疼你勾了订单表系统会提示“是否包含依赖对象”如果你选“是”它会自动把外键关联的明细表也加进来这就违背了你“只导订单表”的初衷。所以实操经验是对于简单的库老老实实按“业务功能模块”勾选比如“用户模块表”就一次勾5、6张相关表对于有强外键关系的表组尽量一起选上别只选单张。如果你确实想只导某一层数据建议在“选择对象”页面点“高级”按钮把“编写外键脚本”选为False这样生成的建表脚本不会带FOREIGN KEY约束数据导入不受顺序影响后面需要外键再单独手动补。2.2 脚本选项里的“数据类型”到底指什么很多新手不知道向导里的“高级”按钮下藏着真正的控制面板。关键的一项叫 “要编写脚本的数据的类型”值有三种仅限架构、仅限数据、架构和数据。选“仅限架构”你得到的是建表、建索引、建存储过程脚本不含INSERT语句选“架构和数据”才会把INSERT语句也生成出来。这里要特别注意默认值在不同版本SQL Server里可能不一样SQL Server 2012、2014默认多半是“仅限架构”所以你明明勾了表最后脚本里却没有一条INSERT就是这个原因。我们要导出可选表和数据第一件事就是把这项改成“架构和数据”。其它几个选项同样影响结果“编写外键脚本”在数据导入阶段建议先设为False“编写索引脚本”建议保留True因为索引就是表结构的一部分“使用USE DATABASE”建议False这样生成脚本第一行不会带USE [源库名]目标库名不同也不会搞混“包含SET ANSI_NULLS 和 SET QUOTED_IDENTIFIER”保留True这个能防止新建脚本执行时会话选项不同导致部分对象创建失败。2.3 选定输出位置文件、剪贴板还是新建查询向导最后一步是选择输出方式通常选“保存到文件”文件编码可选UTF-8或ANSI。这个选择看似小事实际影响很大如果脚本里数据含生僻汉字ANSI编码可能乱码这时候必须选UTF-8如果只是给同机房机器执行选默认编码倒是无所谓。我用过的版本里SQL Server 2012以后默认提供“保存到文件Unicode 文本”选项建议优先选这个。如果脚本特别大比如几百MB别用“保存到剪贴板”或“新建查询”SSMS文本编辑器一次加载好几百万行会把内存干爆直接保存成文件最稳。还要注意生成过程中如果勾选了多个对象向导会自动生成一个主文件里面包含每个对象的脚本内容不需要手动合并。注意如果你的库启用了“行级安全”或列级加密生成脚本可能无法完整导出安全选项这个属于高级话题日常迁移一般不会遇到但如果遇到脚本里出现奇怪的加密语句报错就要回来检查这些特殊功能。3. 实操SSMS里10分钟完成“选表选数据”导出3.1 逐步操作从右键数据库到拿到脚本先放一段我常用的完整操作路径适合SQL Server 2016及以上版本2012界面也差不多打开SSMS连接到源实例。在对象资源管理器中右键数据库名 → 任务 → 生成脚本。进入“介绍”页直接下一步。选择对象选“选择特定数据库对象”展开“表”勾选你要导出的那些表。如果还要导视图、存储过程一并勾选。点“高级”先设置“要编写脚本的数据的类型” “架构和数据”再设置“编写外键脚本” False或按需求保留True接着把“使用USE DATABASE” False。点“确定”回到向导下一步。选择输出类型“保存到文件”指定文件路径比如D:\backup\order_tables.sql可以勾选“在可能时生成每个对象的脚本文件”但我一般选单文件管理简单。下一步点“完成”等待生成进度条结束。生成好的文件打开看一下大致是这么个结构SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[Order]( [Id] [int] IDENTITY(1,1) NOT NULL, [OrderNo] [nvarchar](32) NOT NULL, [CustomerId] [int] NOT NULL ) GO SET IDENTITY_INSERT [dbo].[Order] ON GO INSERT [dbo].[Order] ([Id], [OrderNo], [CustomerId]) VALUES (1, NSO2023001, 1001) ... GO SET IDENTITY_INSERT [dbo].[Order] OFF GO这里面几个点我要展开讲一下SET IDENTITY_INSERT ... ON是为了让显式插入自增主键值N前缀表示Unicode字符串源库是nvarchar脚本没问题。如果你要导入的目标库已有数据可能需要关掉身份插入再清表这个后面说。3.2 数据量控制怎么只导出部分行生成脚本向导不会让你直接写WHERE条件它导出的是整表数据。如果只想导出满足条件的数据有几个办法用SSMS生成“SELECT * INTO”脚本手动加WHERE但这样结构是导出了索引和约束全部丢失。用视图做中间层建一个带过滤条件的视图再对视图生成脚本但这样生成的还是视图定义INSERT数据不见得是物理表结构。我自己更推荐的做法先生成“仅架构”脚本然后单独用bcp或导出数据向导再加WHERE条件导出数据文件。举个例子订单表只想导近一个月的bcp SELECT * FROM AdventureWorks.dbo.[Order] WHERE OrderDate 2025-01-01 queryout D:\backup\Order.dat -S . -U sa -P xxxx -c -t |目标端再用bcp in导入。这种方式的坏处是不生成建表语句所以要保留前面的“仅架构”脚本。如果必须全用纯SQL脚本那可以采用“临时表 INSERT SELECT”的方式自己拼比如生成一个INSERT INTO dbo.Order SELECT * FROM dbo.Order WHERE ...的脚本这个要小心自增列得用SET IDENTITY_INSERT ON并且显式列名。3.3 多脚本文件的拆分与顺序执行生成脚本向导的缺点是所有对象挤在一个文件里执行顺序是生成器帮你排好的。如果你喜欢拆分开可以按对象类型自己拆先跑所有CREATE TABLE再跑索引和外键最后跑INSERT。但要注意如果表之间有自引用外键或者循环依赖现实中很多烂表有环形外键单纯拆分会出现“CREATE TABLE的时候引用的表还没建”的报错。我通常在高级选项里先关闭外键生成就完美避开了这个顺序问题。等数据导入完再执行一个独立的ALTER TABLE ... WITH CHECK ADD CONSTRAINT ...脚本把外键补回来。这种方式在数据仓库重建、测试环境刷新时非常实用因为大数据量插入时外键检查会严重拖慢性能先物理隔离约束再一次性补上效率和成功率都高。4. 导入目标库执行SQL脚本的三道关卡4.1 新建目标库与基础检查拿到order_tables.sql后先在目标实例上新建一个数据库名字随意比如OrderDev。新库默认兼容级别是当前实例的默认级别一般不用改但如果源库用了比较新的语法比如STRING_AGG、TRIM而目标实例版本较老脚本执行可能在特定语句直接报错。执行前最好先确认一下SELECT name, compatibility_level FROM sys.databases;如果源库兼容级别140SQL Server 2017目标库是120SQL Server 2014很多新函数不支持建议先把目标库兼容级别调低或者干脆把不兼容的语句手工替换掉。这个检查很多人忽略等脚本跑到一半报错才想起来看版本很耽误时间。数据库建好后用SQL Server Management Studio打开生成的脚本文件点击“执行”。如果SSMS打开大文件卡死那就用命令行方式sqlcmd -S localhost -U sa -P xxxx -d OrderDev -i D:\backup\order_tables.sql注意-d指定目标数据库脚本里如果没有USE语句所有表都会建在指定的OrderDev里如果脚本里有USE [源库名]而你没有改就会报错“数据库不存在”。这就是我刚才建议把“使用USE DATABASE”设成False的原因。4.2 执行顺序表结构先来数据后插如果你严格按照我的建议生成脚本文件内部顺序是先建表后INSERT结构天然成立。如果遇到“表已存在”报错多半是目标库里已经有同名对象或者脚本重复执行。重复执行时应先手动清掉目标库里的旧对象-- 假设只清这几张表 DROP TABLE IF EXISTS dbo.OrderDetail; DROP TABLE IF EXISTS dbo.[Order];然后再执行脚本。对生产环境千万不要这样搞仅在私有测试库或临时库。如果表之间外键没被脚本包含那DROP顺序倒是无所谓如果包含了外键先删子表再删父表否则外键会阻止删除。4.3 约束与自增值导入后容易忽略的两个小尾巴导入后最容易被忽略的是自增ID的“种子值”。脚本里使用SET IDENTITY_INSERT [Order] ON插入具体ID后系统会自动把IDENTITY种子更新为“已插入的最大ID1”这个一般是符合预期的。但如果脚本插入的数据ID不是连续的后面新插入的行会出现ID跳跃比如插到了1000下一行ID是1001这没什么问题。可如果脚本里数据没有显式ID而表本身有IDENTITY列那INSERT就不带Id列值目标表Id会从1重新开始可能与旧业务系统交互时造成主键冲突。解决办法是在导入后手动执行DBCC CHECKIDENT (dbo.Order, RESEED, 10000);把下一个ID重置到期望值。这一步写在脚本里也不是不行但我习惯单独做毕竟每个表期望值不一样混在一起容易出错。外键约束如果在生成脚本时被排除导入数据后还得手工补。比如ALTER TABLE dbo.OrderDetail WITH CHECK ADD CONSTRAINT FK_OrderDetail_Order FOREIGN KEY (OrderId) REFERENCES dbo.[Order](Id);补约束前务必要确认子表数据里没有孤儿记录。可以用一个简单查询验证SELECT COUNT(*) FROM dbo.OrderDetail od LEFT JOIN dbo.[Order] o ON od.OrderId o.Id WHERE o.Id IS NULL;有孤儿记录时加外键会失败要先清理脏数据。5. 执行时常见的坑与排查方法5.1 报错“对象名无效”或“数据库中已存在名为...的对象”这种情况九成是脚本里的USE语句没关脚本开头写了USE [原库]但目标库里没有原库名或者脚本执行时选中了“master”数据库而不是新建的库。解决办法很简单检查SQL脚本第一段去掉USE行或者在sqlcmd里明确指定-d目标库。另外导入时如果目标库已有同名的表脚本里的CREATE TABLE就会直接报“数据库中已存在名为 Order 的对象”。此时要么先清表要么把生成脚本选项“如果目标对象存在则删除它”打开在SSMS高级选项中叫“包含DROP语句”让它先执行DROP TABLE再CREATE TABLE。注意这个选项也可以设置成“保留对象”不生成DROP。日常迁移我倾向于关掉DROP手动控制删除避免误删。5.2 乱码与排序规则冲突源库的排序规则Collation如果与目标库不一致脚本里的建表语句可能把“排序规则”也写进去例如CREATE TABLE dbo.Order ( [OrderNo] nvarchar(32) COLLATE Chinese_PRC_CI_AS NOT NULL )目标库里执行没问题但如果后续要跨库关联查询两个库排序规则不同会报“无法解决排序规则冲突”。处理办法要么建目标库时将排序规则设为与源库一致建库时指定COLLATE Chinese_PRC_CI_AS要么在关联查询里临时指定COLLATE。如果脚本中的列没有带COLLATE则使用数据库默认排序规则那就在建库时干脆与源库保持一致。数据中的字符乱码主要发生在编码。执行脚本时确保文件编码是UTF-8带BOM或不带都行。如果乱码已经出现检查你是否在生成时选了“Unicode 文本”或者用Notepad转一下编码再重跑。5.3 标识列报错当IDENTITY_INSERT开关没配对脚本数据插入自增列时肯定会有SET IDENTITY_INSERT语句。它有个重要特性一个会话里在同一时刻只能有一个表的IDENTITY_INSERT是ON。如果你把多个表的脚本片段合并到了一个文件生成器会在每个INSERT段前加ON、段后加OFF一般没问题。但如果你自己手写过类似的批量INSERT忘记了OFF后面脚本执行到下一个表时大概率报错“仅当使用了列列表并且IDENTITY_INSERT为ON时才能为表...中的标识列指定显式值”。我花了不少时间踩这个坑后来养成个习惯用生成向导别自己拼。如果手工处理一定每一段都对称地ON/OFF。同时注意SET IDENTITY_INSERT只能在包含标识列的表中使用并且目标表必须存在标识列否则也会报错。5.4 主键冲突目标表已有数据怎么办如果你的目标库不是空库而是已经存在部分数据比如测试环境刷新增量直接执行INSERT会因为主键冲突报错。操作顺序应当是先按业务要求决定是覆盖还是追加。追加的话脚本里的INSERT语句主键值可能会和现有数据撞覆盖的话先执行DELETE FROM dbo.OrderDetail; DELETE FROM dbo.[Order];如果要保留种子值也可以TRUNCATE TABLE dbo.Order;但TRUNCATE不能用于被外键引用的表。清完之后再跑脚本基本可避免冲突。这里提醒一句对生产库做删除前务必开启事务并备份或者用BEGIN TRAN包住删除语句执行完检查行数再COMMIT。5.5 大表的INSERT执行太慢一个包含百万行数据的表生成脚本会写出一百万条INSERT还是每条多行SQL Server 2012以后的SSMS生成脚本默认每条INSERT只带少量行我记得是10行一组不同版本可能不同效果就是脚本体积爆炸执行速度慢。要提速有几种办法如果目标是同一网络环境可以把脚本中的多条INSERT语句保持原样但把整个脚本放进一个显式事务里减少自动提交开销。把外键、触发器、非聚集索引先不建插入完成后再建。更彻底的办法是放弃纯SQL脚本改走bcp out/bcp in或使用BULK INSERT。这就不在“纯sql脚本”范围内了但数据量大时值得考虑。5.6 权限或安全上下文引发的问题生成脚本时提示“权限不足”或者“找不到对象”多半是源库账号没有VIEW DEFINITION权限。用sa或db_owner登录能解决大多数情况。执行脚本时目标库账号需要“CREATE TABLE”“INSERT”“ALTER”权限一般给db_owner即可。如果是托管SQL账号只有特定schema权限连CREATE TABLE都会失败那先把脚本内容按角色权限拆分或改用DBA介入。6. 进阶技巧让脚本变得更可控、更可复现6.1 用T-SQL脚本批量生成“生成脚本”如果你要导出的表很多又需要反复执行可以写一段T-SQL动态生成每个表的BCP或SELECT ... INTO语句。比如你想把库里所有行数小于5万、且表名带dim_前缀的表全部生成BCP导出命令可以用元数据动态拼接。日常我没那么自动化但会把常用表的导出参数存成一个小配置表写个简单存储过程直接调。6.2 对比脚本比导入后靠眼睛更靠谱导入完成后别急着收工强烈建议做一遍行数对比。最省事的方法是分别连源库和目标库跑SELECT Order AS TableName, COUNT(*) AS Cnt FROM dbo.[Order] UNION ALL SELECT OrderDetail, COUNT(*) FROM dbo.OrderDetail;如果行数对不上说明脚本漏数据了通常发生在过滤视图或脚本生成中断。还有更严格的方式对每张表算一个CHECKSUM_AGG(BINARY_CHECKSUM(*))作为指纹两边对比能发现同一数据在不同库里的细微差别。6.3 归档、发布和版本管理的延续生成出来的SQL脚本不只是一次性迁移工具它本身也是一个交付物。把脚本放进Git仓库后续环境重建时一条命令执行完整个库的“逻辑快照”就出来了。这种“Schema as Code”的用法对开发环境非常友好也能让新同事快速理解表结构与初始化数据。这种思路我用了很多年每次项目组看到一份能重复执行的SQL脚本都比我口头讲一遍结构高效得多。7. 个人实操经验总结与最后的提醒前面把导出、导入和排错的主要环节都讲了最后聊点个人体会。我做了这么多年数据库运维对“SQL Server导出和导入可选的数据库表和数据”这件事的理解是SSMS生成脚本永远是我首选的快速方案但永远不要在没看高级选项的情况下就点“下一步”。你可能觉得这些都是常规操作可现实中我接手的项目有相当比例就是这么“常规”翻车——导出来的脚本是一条INSERT都没有或者外键顺序直接让整个导入失败。回头查原因全是选项设置问题。另外一个让我印象深刻的点是很多人忽视“脚本执行顺序”和“约束的关闭时机”。如果只是导一两张表外键直接生成也没事但导一整个业务模块外键全开一次性跑脚本很可能由于某个孤儿数据导致后面几步全部失败。所以我的习惯是数据导入前禁用外键和触发器数据导入后先做数据校验再重新启用约束。这个流程看起来多好几个动作但在真实项目里能省掉至少一小时的排查时间。如果你想长期依赖这套流程建议抽时间把生成的脚本放到一个测试库在目标实例版本上先跑一遍确认无误后再交付给生产。脚本文件没有版本概念你不会希望一个明明少了两张表的“初始脚本”在半年后被别人当作标本来用。每次生成时都顺手记录一下源库名、生成时间、对象范围成本很低回报很大。最后补充一个不算小的小技巧用sqlcmd执行脚本时加上-b参数可以让脚本在遇到错误时返回非零退出码这样你在CI/CD或批处理里就能及时发现失败而不是让脚本“带病执行”直到最后才报错。反正我现在凡是脚本化部署必加-b这个参数普通文档里提得少但实战极有用。
返回列表