SQL Server 2014:无需备份与向导,用脚本克隆带数据的数据库
在SQL Server 2014中无备份创建带数据的数据库副本(脚本方式)
首先得明确:DBCC CLONEDATABASE确实只复制架构、统计信息和元数据,完全不会包含用户数据,所以要实现带数据的副本,咱们可以用以下几种纯脚本方式来搞定:
方法1:手动复制架构+批量导入数据
这个方法分两步走:先复刻源库的所有架构对象(表、约束、索引、存储过程等),再把源库的数据批量插入到目标库的对应表中。
步骤1:创建空的目标数据库
先搭一个和源库文件配置匹配的空库(可以根据自己的存储路径调整参数):
CREATE DATABASE [YourTargetDB] ON PRIMARY ( NAME = N'YourTargetDB', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\YourTargetDB.mdf' , SIZE = 5120KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB ) LOG ON ( NAME = N'YourTargetDB_log', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\YourTargetDB_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%) GO
步骤2:复制源库的架构对象
可以通过系统视图自动生成所有对象的创建脚本,这里先给你表、主键约束和非主键索引的生成逻辑(你可以扩展到视图、存储过程等对象):
-- 生成所有用户表的CREATE TABLE脚本(包含主键约束) SELECT 'USE [YourTargetDB]; CREATE TABLE ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' (' + STRING_AGG( QUOTENAME(c.name) + ' ' + CASE WHEN c.system_type_id = c.user_type_id THEN t.name ELSE ut.name END + CASE WHEN c.max_length <> -1 THEN '(' + CASE WHEN c.system_type_id IN (167, 175, 231, 239) THEN CAST(c.max_length/2 AS VARCHAR) ELSE CAST(c.max_length AS VARCHAR) END + ')' ELSE '' END + CASE WHEN c.is_nullable = 0 THEN ' NOT NULL' ELSE ' NULL' END + CASE WHEN dc.name IS NOT NULL THEN ' DEFAULT ' + dc.definition ELSE '' END, ', ' ) + CASE WHEN pk.name IS NOT NULL THEN ', CONSTRAINT ' + QUOTENAME(pk.name) + ' PRIMARY KEY (' + STRING_AGG(QUOTENAME(ic.name), ', ') + ')' ELSE '' END + ');' AS CreateTableScript FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id JOIN sys.columns c ON t.object_id = c.object_id JOIN sys.types ut ON c.user_type_id = ut.user_type_id LEFT JOIN sys.default_constraints dc ON c.object_id = dc.parent_object_id AND c.column_id = dc.parent_column_id LEFT JOIN sys.key_constraints pk ON t.object_id = pk.parent_object_id AND pk.type = 'PK' LEFT JOIN sys.index_columns ic ON pk.parent_object_id = ic.object_id AND pk.unique_index_id = ic.index_id GROUP BY s.name, t.name, pk.name ORDER BY s.name, t.name; -- 生成所有非主键索引的创建脚本 SELECT 'USE [YourTargetDB]; CREATE ' + CASE WHEN i.is_unique = 1 THEN 'UNIQUE ' ELSE '' END + i.type_desc + ' INDEX ' + QUOTENAME(i.name) + ' ON ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' (' + STRING_AGG(QUOTENAME(c.name) + CASE WHEN ic.is_descending_key = 1 THEN ' DESC' ELSE ' ASC' END, ', ') + ');' AS CreateIndexScript FROM sys.indexes i JOIN sys.tables t ON i.object_id = t.object_id JOIN sys.schemas s ON t.schema_id = s.schema_id JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE i.type <> 0 -- 排除堆表的默认索引 AND i.is_primary_key = 0 GROUP BY s.name, t.name, i.name, i.is_unique, i.type_desc ORDER BY s.name, t.name;
把生成的脚本执行一遍,目标库就有和源库完全一致的表结构、约束和索引了。
步骤3:批量导入数据
对每个用户表执行INSERT INTO ... SELECT语句就行,要是表有自增列,记得先开IDENTITY_INSERT:
-- 示例:复制dbo.Customers表的数据 USE [YourTargetDB]; INSERT INTO dbo.Customers SELECT * FROM [YourSourceDB].dbo.Customers; -- 带自增列的表处理方式 USE [YourTargetDB]; SET IDENTITY_INSERT dbo.Orders ON; INSERT INTO dbo.Orders (OrderID, CustomerID, OrderDate) SELECT OrderID, CustomerID, OrderDate FROM [YourSourceDB].dbo.Orders; SET IDENTITY_INSERT dbo.Orders OFF;
嫌手动写麻烦?可以用下面的脚本批量生成所有表的插入语句:
SELECT 'USE [YourTargetDB]; ' + CASE WHEN EXISTS (SELECT 1 FROM sys.identity_columns WHERE object_id = t.object_id) THEN 'SET IDENTITY_INSERT ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' ON; ' ELSE '' END + 'INSERT INTO ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' SELECT * FROM [YourSourceDB].' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ';' + CASE WHEN EXISTS (SELECT 1 FROM sys.identity_columns WHERE object_id = t.object_id) THEN ' SET IDENTITY_INSERT ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' OFF;' ELSE '' END AS InsertDataScript FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 -- 排除系统表 ORDER BY s.name, t.name;
方法2:使用SELECT INTO快速复制表(适合小型数据库)
如果你的数据库不大,且能接受先复制表(自动创建结构)再补全约束/索引的方式,SELECT INTO会更快捷:
-- 先创建目标库(同方法1) CREATE DATABASE [YourTargetDB]; GO -- 复制所有用户表到目标库(自动创建表,但不包含非主键约束、索引、触发器) USE [YourTargetDB]; GO SELECT * INTO dbo.Customers FROM [YourSourceDB].dbo.Customers; SELECT * INTO dbo.Orders FROM [YourSourceDB].dbo.Orders; -- ... 其他表依次类推 -- 之后需要手动添加缺失的约束、索引和触发器(可以用方法1中的脚本生成)
注意:SELECT INTO会自动创建表,但不会复制默认约束、外键、非主键索引、触发器这些对象,所以后续得手动补全。
一些注意事项
- 执行脚本前,确保你有足够的权限(
CREATE DATABASE、源库的SELECT权限、目标库的INSERT权限等)。 - 如果源库数据量很大,建议在业务非高峰时段执行,避免影响线上业务。
- 有外键约束的表要注意插入顺序:先插父表数据,再插子表数据,否则会触发外键冲突。
内容的提问来源于stack exchange,提问作者carlosm




