用于克隆数据库的SQL脚本,保持原始状态不变

时间:2013-09-02 08:25:02

标签: sql tsql sql-server-2012

我们目前有一个CMS的基本安装,在这个CMS中它包含一个完整的用户,产品,内容等工作数据集。我们正在寻求增加安装时间,因为目前我们现在必须进入SQL Server 2012 ,创建一个新数据库,然后从现有的基本安装数据库中恢复数据库。

我们每次安装可能需要10到15分钟。

我们还确保我们的基础数据库具有我们构建的网站的所有要求。

我们的问题是,我们希望做到以下几点。

  1. 让脚本创建一个全新的空数据库
  2. 将此数据库复制到新的.bak文件中
  3. 获取此.bak文件,然后将其重新分配到一个全新的数据库中,该数据库分别包含自己的MDF和LDF文件。
  4. 数据库位于同一台服务器上,因此我们无需将其迁移到任何其他计算机或实例。

    我们的代码位于

    之下
    CREATE database my_test
    
    BACKUP DATABASE test_db TO DISK = 'C:\my_test\my_test.bak' WITH INIT;
    EXEC internal_lab_test.dbo.sp_helpfile;
    
    RESTORE FILELISTONLY
      FROM DISK = 'C:\my_test\my_test.bak'
    
    RESTORE DATABASE my_test
      FROM DISK = 'C:\my_test\my_test.bak'
      WITH MOVE 'my_test' TO 'C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\test_db.mdf',
      MOVE 'my_test_log' TO 'C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\test_db_log.ldf'
    

    我们希望确保所有内容都清新干净,但仍然包含所有数据,但每次运行此代码时,都会收到以下错误消息

    我们还希望确保原始数据库mdf和ldf文件保持完整并且未在新数据库中使用

    Msg 3154, Level 16, State 4, Line 10
    The backup set holds a backup of a database other than the existing 'my_test' database.
    Msg 3013, Level 16, State 1, Line 10
    RESTORE DATABASE is terminating abnormally.
    

2 个答案:

答案 0 :(得分:4)

我知道这是旧的,但它是谷歌的第二个条目,所以完整性。

这是因为数据库已经存在。所以要么删除数据库,要么添加替换。

my_test和my_test_log名称也需要是restore filelistonly命令中的逻辑名称。

RESTORE DATABASE my_test
  FROM DISK = 'C:\my_test\my_test.bak'
  WITH Replace,
  MOVE 'my_test' TO 'C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\test_db.mdf',
  MOVE 'my_test_log' TO 'C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\test_db_log.ldf'

答案 1 :(得分:1)

完成我写的脚本,它对于常见的开发任务足够灵活(克隆db来自'template',应用增量脚本,然后删除克隆的db)。 注意:在执行过程adv.cloneDatabase之前,您应该使DB'read_only,single_user',并在之后返回原始状态,您还需要执行xp_cmdshell的权限。

CREATE PROCEDURE adv.alterateFileNames(
        @mdfFileName nvarchar(256), 
        @ldfFileName nvarchar(256), 
        @newMdfFileName nvarchar(256) OUTPUT, 
        @newLdfFileName nvarchar(256) OUTPUT
)
AS
BEGIN
    DECLARE @path_data nvarchar(256)
    DECLARE @ext_data nvarchar(4)
    DECLARE @path_log nvarchar(256)
    DECLARE @ext_log nvarchar(4)

    -- respect file extensions
    if (RIGHT(@mdfFileName , 4)='.mdf')
    BEGIN
        SET @path_data = SUBSTRING(@mdfFileName,0,LEN(@mdfFileName)-3)
        SET @ext_data = '.mdf'
    END
    ELSE
    BEGIN
        SET @path_data = @mdfFileName
        SET @ext_data = ''
    END


    if (RIGHT(@ldfFileName , 4)='.ldf')
    BEGIN
        SET @path_log = SUBSTRING(@ldfFileName,0,LEN(@ldfFileName)-3)
        SET @ext_log = '.ldf'
    END
    ELSE
    BEGIN
        SET @path_log = @ldfFileName
        SET @ext_log = ''
    END

    -- respect suffix counters like dbname_2 (that means add value to them)
    DECLARE @iData int

    DECLARE @data_suffix_index int = len(@path_data) - charindex('_', reverse(@path_data))
    IF (@data_suffix_index < len(@path_data)-1 AND @data_suffix_index > 0 )
    BEGIN
        DECLARE @data_suffix nvarchar(128) = substring(@path_data, @data_suffix_index+2, len(@path_data)-@data_suffix_index-1 ) 
        IF @data_suffix NOT LIKE '%[^0-9]%'
        BEGIN
            SET @path_data = SUBSTRING(@path_data,0,@data_suffix_index+1)
            SET @iData = CAST(@data_suffix as int);
        END
    END
    IF (@iData is null)
    BEGIN
            SET @path_data = @path_data
            SET @iData = 0
    END 

    DECLARE @iLog int

    DECLARE @log_suffix_index int = len(@path_log) - charindex('_', reverse(@path_log))
    IF (@log_suffix_index < len(@path_log)-1 AND @log_suffix_index > 0 )
    BEGIN
        DECLARE @log_suffix nvarchar(128) = substring(@path_log, @log_suffix_index+2, len(@path_log) - @log_suffix_index-1 ) 
        IF @log_suffix NOT LIKE '%[^0-9]%'
        BEGIN
            SET @path_log = SUBSTRING(@path_log,0,@log_suffix_index+1)
            SET @iLog = CAST(@log_suffix as int);
        END
    END
    IF (@iLog is null)
    BEGIN
            SET @path_log = @path_log
            SET @iLog = 0
    END 

    WHILE 1=1
    BEGIN
        IF EXISTS(SELECT * FROM sys.master_files WHERE physical_name=@path_data+'_'+CAST(@iData AS varchar(6))+@ext_data)
            SET @iData=@iData+1
        ELSE
        BEGIN
            SET @path_data= @path_data+'_'+CAST(@iData AS varchar(6))+@ext_data
            BREAK
        END
    END

    WHILE 1=1
    BEGIN
        IF EXISTS(SELECT * FROM sys.master_files WHERE physical_name=@path_log+'_'+CAST(@iLog AS varchar(6))+@ext_log)
            SET @iLog=@iLog+1
        ELSE
        BEGIN
            SET @path_log= @path_log+'_'+CAST(@iLog AS varchar(6))+@ext_log
            BREAK
        END
    END
    SET @newMdfFileName = @path_data
    SET @newLdfFileName = @path_log
END
GO
CREATE PROCEDURE adv.cloneDatabase
( 
    @databaseName sysname,
    @newDatabaseName sysname
)
AS
BEGIN
    SET NOCOUNT ON

    IF NOT EXISTS ( SELECT * FROM sys.databases WHERE name = @databaseName)
        THROW 50000, 'Database doesn''t exist', 1;
    IF NOT EXISTS ( SELECT * FROM sys.databases WHERE name = @databaseName AND owner_sid<>0x01)
        THROW 50000, 'Clonning of system database is not supported', 1;
    IF NOT EXISTS ( SELECT * FROM sys.databases WHERE name = @databaseName AND is_read_only=1)
        THROW 50000, 'Clonning of not readonly database is not supported', 1;
    IF NOT EXISTS ( SELECT * FROM sys.databases WHERE name = @databaseName AND user_access=1 /*single user*/)
        THROW 50000, 'Clonning of nor single_user database is not supported', 1;

    -- collect file info
    DECLARE @Files TABLE 
    (
        [type] int, /*0,1,2,3,4*/
        type_desc nvarchar(60), /*ROWS,LOG,FILESTREAM,FULLTEXT*/
        name sysname,
        physical_name nvarchar(260)
    )

    INSERT INTO @Files ([type], type_desc, name, physical_name)
    SELECT [type], type_desc, f.name, physical_name
    FROM sys.master_files f INNER JOIN sys.databases d ON f.database_id=d.database_id
    WHERE d.name=@databaseName

    -- test files
    DECLARE @filesCount int
    SELECT @filesCount =  count(*) from @Files
    IF (@filesCount<>2)
        THROW 50000, 'The procedure doesn''t support complex file structures', 1;

    DECLARE @mdfFileName nvarchar(260), @ldfFileName nvarchar(260)
    SELECT @mdfFileName = physical_name FROM @Files WHERE type_desc='ROWS'
    SELECT @ldfFileName = physical_name FROM @Files WHERE type_desc='LOG'

    DECLARE @newMdfFileName nvarchar(260), @newLdfFileName nvarchar(260)
    exec adv.alterateFileNames @mdfFileName, @ldfFileName, @newMdfFileName=@newMdfFileName OUTPUT, @newLdfFileName=@newLdfFileName OUTPUT

    DECLARE @cmd1 nvarchar(4000)= 'copy /Y "@mdfFileName" "@newMdfFileName"'
    DECLARE @cmd2 nvarchar(4000)= 'copy "@ldfFileName" "@newLdfFileName"'
    SET @cmd1=replace(@cmd1,'@mdfFileName',@mdfFileName)
    SET @cmd1=replace(@cmd1,'@newMdfFileName',@newMdfFileName)
    SET @cmd2=replace(@cmd2,'@ldfFileName',@ldfFileName)
    SET @cmd2=replace(@cmd2,'@newLdfFileName',@newLdfFileName)

    DECLARE @OUTPUT TABLE (line text)
    DECLARE @result INT

    BEGIN TRY
        INSERT INTO @OUTPUT (line) VALUES ('> '+@cmd1)
        INSERT INTO @OUTPUT
        exec @result =xp_cmdshell @cmd1
        INSERT INTO @OUTPUT (line) VALUES ('> '+@cmd2)
        IF (@result <> 0)
           THROW 50000, 'Error copying mdf file',1

        INSERT INTO @OUTPUT
        exec @result =xp_cmdshell @cmd2
        IF (@result <> 0)
           THROW 50000, 'Error copying ldf file',1
    END TRY
    BEGIN CATCH
        SELECT * FROM @OUTPUT WHERE line is not null;
        THROW
    END CATCH
    DECLARE @createDatabaseSql nvarchar(max)
    SET @createDatabaseSql = '
    CREATE DATABASE '+@newDatabaseName+'
      ON (FILENAME = '''+@newMdfFileName+'''),
         (FILENAME = '''+@newLdfFileName+''')
   FOR ATTACH;'
   exec sp_executesql  @stmt = @createDatabaseSql

END
GO