在工作中,經常為測試或開發部門搭建數據庫環境,每一次搭建環境,需要還原某一個目錄下的所有數據庫備份文件,也就是,一次需要還原一百多個數據庫。
每碰到類似的情況,需要編寫一個SQL腳本來實現還原數據庫的功能。這裡就簡單介紹如何編寫存儲過程來實現,還原某一個目錄下的所有數據庫備份文件。
要實現還原某一個目錄下的所有數據庫備份文件,首先要實現還原某一個目錄下的某一個備份文件。真實世界中,只提供數據庫備份路徑,和備份數據庫文件名,及還原到的本地路徑。接下來,將講述以下內容:
- 根據備份文件找出數據庫名
- 根據備份文件找出數據、日志目錄列表
- 構造還原數據庫SQL語句
- 還原單個數據庫的存儲過程代碼
- 還原多個數據庫的存儲過程代碼(一個目錄下)
●根據備份文件找出數據庫名
在還原數據庫的開始,我們先要知道把備份數據庫文件還原到哪一個數據庫上。在真實世界中,我們無法保證備份數據庫文件都為”數據庫名+.bak”格式 (如:myDB.bak,其中myDB就是數據庫名)。通常情況,只知道備份文件,需要知道數據庫名,我們可以通過SQL Server提供的T-SQL語句“Restore HeaderOnly”來找出原來的數據庫名,簡單語法這樣:Restore HeaderOnly From
返回的結果集中,有一列”DatabaseName”描述備份的數據名。通過”DatabaseName”列,就可以找出我們需要的原來數據庫名了。
e.g.
- Restore HeaderOnly From Disk = 'E:\DBBackup\dbA2011-09-05.bak'
【注:】在結果集中, SQL Server 2008/R2 比 SQL Server 2005 多一個列”CompressedBackupSize”。
●根據備份文件找出數據、日志列表
接下來,我們需要知道備份文件中包含哪些數據、日志文件,及要知道原來的文件路徑是什麼。要是原來的數據庫文件存儲路徑與新路徑不一致,我們需要在”Restore Database …” 裡面使用” Move”選項。這裡我們可以通過使用SQL Server 提供的T-SQL語句”Restore FileListOnly”,返回數據庫和日志文件列表組成的結果集,簡單語法這樣:Restore FileListOnly From <backup_device>
e.g.
- Restore FileListOnly From Disk = 'E:\DBBackup\dbA2011-09-05.bak'
【注:】在結果集中, SQL Server 2008/R2 比 SQL Server 2005 多一個列”TDEThumbprint”,應用於顯示數據庫加密密鑰的指紋。
●構造還原數據庫SQL語句
上面兩點內容,我們根據備份文件,知道了要還原的數據庫名和數據庫文件列表,那麼,我們基本可以構造出還原數據庫的SQL語句了。
e.g.
●還原單個數據庫的存儲過程代碼
根據上面分析的方法,這裡演示我寫的一個還原單個數據庫的存儲過程代碼,因為代碼是之前寫的,中間因真實世界的特殊情況,修改成幾個版本的存儲過程。當然,如有可能,你可以根據自己所在的真實環境,修改對應部分的代碼,以便滿足自己的需要。
存儲過程sp_RestoreDataBase代碼:
- Use master
- Go
- if object_ID('[sp_restoredatabase]') is not null
- Drop Procedure [sp_restoredatabase]
- Go
- /*
- --還原數據庫(v3.2) Andy 2011-2-22
- @DatabBaseBakPath nvarchar(260), --數據庫備份路徑(包含備份文件名)
- @DatabBaseNewPath nvarchar(260) --新數據庫路徑
- @NewDataBaseName nvarchar(128) --新數據庫名
- e.g:
- --Exec sp_RestoreDataBase 'E:\DatabaseBackup\DE\Support_2008722_14_33_39.bak','D:\SQL2005\DE'
- V3.0版本修改說明:
- 1.修正了之前版本在還原包含全文索引文件的時候發生的錯誤。
- 2.修正了之前版本在還原包含多個數據庫文件和日志文件時發生的錯誤。
- V3.1版本說明,增加了參數@Keep_Replication,表示是否保存復制設定
- V3.2版本說明,增加了FILE ={ file_number | @file_number } 邏輯判斷,在包含多個備份組,還原最後一個備份組
- V3.3 版本說明 ,還原發生錯誤時返回 1
- */
- CREATE Proc sp_RestoreDataBase
- (
- @DatabBaseBakPath nvarchar(260),
- @DatabBaseNewPath nvarchar(260),
- @NewDataBaseName nvarchar(128)=null,
- @Keep_Replication bit=0
- )
- As
- Set Nocount On
- Begin Try
- Declare
- @DataBaseName nvarchar(128),
- @Sql nvarchar(max),
- @SqlDatabaseRename nvarchar(max),
- @Enter nvarchar(10)
- --檢查文件路徑是否正確
- Declare
- @Dir nvarchar(4000),
- @i int
- -- Set @Dir='Dir '+@DatabBaseBakPath
- -- Exec @i=xp_cmdshell @Dir,no_output
- -- If @i<>0
- Exec master.dbo.xp_fileexist @DatabBaseBakPath,@i Output
- If @i=0
- Begin
- Raiserror 50001 N'無效的備份數據庫路徑/文件名!'
- Return 1
- End
- If Charindex('\\',@DatabBaseNewPath)>0
- Begin
- Raiserror 50001 N'數據庫還原路徑中不能含有''\\''!'
- Return 1
- End
- If Right(Rtrim(@DatabBaseNewPath),1)='\'
- Begin
- Raiserror 50001 N'數據庫還原路徑的最後一位能含有''\''!'
- Return 1
- End
- Set @Dir='Dir '+@DatabBaseNewPath
- Exec @i=xp_cmdshell @Dir,no_output
- If @i<>0
- Begin
- Raiserror 50001 N'無效的數據庫還原路徑!'
- Return 1
- End
- set @DatabBaseNewPath=replace(@DatabBaseNewPath,'"','')
- /*
- --SQL Server 2005
- Declare @BakFileList Table(LogicalName nvarchar(128),PhysicalName nvarchar(260),Type char(1),FileGroupName nvarchar(128),Size numeric(20,0),MaxSize numeric(20,0),FileID bigint,CreateLSN numeric(25,0),DropLSN numeric(25,0) NULL,UniqueID uniqueidentifIEr,ReadOnlyLSN numeric(25,0) NULL,ReadWriteLSN numeric(25,0) NULL,BackupSizeInBytes bigint,SourceBlockSize int,FileGroupID int,LogGroupGUID uniqueidentifIEr NULL,DifferentialBaseLSN numeric(25,0) NULL,DifferentialBaseGUID uniqueidentifIEr,IsReadOnly bit,IsPresent bit)
- */
- --SQL Server 2008
- Declare @BakFileList Table(LogicalName nvarchar(128),PhysicalName nvarchar(260),Type char(1),FileGroupName nvarchar(128),Size numeric(20,0),MaxSize numeric(20,0),FileID bigint,CreateLSN numeric(25,0),DropLSN numeric(25,0) NULL,UniqueID uniqueidentifIEr,ReadOnlyLSN numeric(25,0) NULL,ReadWriteLSN numeric(25,0) NULL,BackupSizeInBytes bigint,SourceBlockSize int,FileGroupID int,LogGroupGUID uniqueidentifIEr NULL,DifferentialBaseLSN numeric(25,0) NULL,DifferentialBaseGUID uniqueidentifIEr,IsReadOnly bit,IsPresent bit,TDEThumbprint varbinary(32))
- Insert Into @BakFileList
- Exec sp_executesql N'Restore FileListOnly From Disk=@DatabBaseBakPath',N'@DatabBaseBakPath nvarchar(260)',@DatabBaseBakPath
- /*
- --SQL Server 2005
- Declare @BakHeaderInfo Table(BackupName nvarchar(128),BackupDescription nvarchar(255),BackupType smallint,ExpirationDate datetime,Compressed tinyint,Position smallint,DeviceType tinyint,UserName nvarchar(128),ServerName nvarchar(128),DatabaseName nvarchar(128),DatabaseVersion int,DatabaseCreationDate datetime,BackupSize numeric(20,0),FirstLSN numeric(25,0),LastLSN numeric(25,0),CheckpointLSN numeric(25,0),DatabaseBackupLSN numeric(25,0),BackupStartDate datetime,BackupFinishDate datetime,SortOrder smallint,CodePage smallint,UnicodeLocaleId int,UnicodeComparisonStyle int,CompatibilityLevel tinyint,SoftwareVendorId int,SoftwareVersionMajor int,SoftwareVersionMinor int,SoftwareVersionBuild int,MachineName nvarchar(128),Flags int,BindingID uniqueidentifier,RecoveryForkID uniqueidentifier,Collation nvarchar(128),FamilyGUID uniqueidentifIEr,HasBulkLoggedData bit,IsSnapshot bit,IsReadOnly bit,IsSingleUser bit,HasBackupChecksums bit,IsDamaged bit,BeginsLogChain bit,HasIncompleteMetaData bit,IsForceOffline bit,IsCopyOnly bit,FirstRecoveryForkID uniqueidentifIEr,ForkPointLSN numeric(25,0) NULL,RecoveryModel nvarchar(60),DifferentialBaseLSN numeric(25,0) NULL,DifferentialBaseGUID uniqueidentifier,BackupTypeDescription nvarchar(60),BackupSetGUID uniqueidentifIEr NULL)
- */
- --SQL Server 2008
- Declare @BakHeaderInfo Table(BackupName nvarchar(128),BackupDescription nvarchar(255),BackupType smallint,ExpirationDate datetime,Compressed tinyint,Position smallint,DeviceType tinyint,UserName nvarchar(128),ServerName nvarchar(128),DatabaseName nvarchar(128),DatabaseVersion int,DatabaseCreationDate datetime,BackupSize numeric(20,0),FirstLSN numeric(25,0),LastLSN numeric(25,0),CheckpointLSN numeric(25,0),DatabaseBackupLSN numeric(25,0),BackupStartDate datetime,BackupFinishDate datetime,SortOrder smallint,CodePage smallint,UnicodeLocaleId int,UnicodeComparisonStyle int,CompatibilityLevel tinyint,SoftwareVendorId int,SoftwareVersionMajor int,SoftwareVersionMinor int,SoftwareVersionBuild int,MachineName nvarchar(128),Flags int,BindingID uniqueidentifier,RecoveryForkID uniqueidentifier,Collation nvarchar(128),FamilyGUID uniqueidentifIEr,HasBulkLoggedData bit,IsSnapshot bit,IsReadOnly bit,IsSingleUser bit,HasBackupChecksums bit,IsDamaged bit,BeginsLogChain bit,HasIncompleteMetaData bit,IsForceOffline bit,IsCopyOnly bit,FirstRecoveryForkID uniqueidentifIEr,ForkPointLSN numeric(25,0) NULL,RecoveryModel nvarchar(60),DifferentialBaseLSN numeric(25,0) NULL,DifferentialBaseGUID uniqueidentifier,BackupTypeDescription nvarchar(60),BackupSetGUID uniqueidentifIEr NULL,CompressedBackupSize numeric(20,0))
- Insert Into @BakHeaderInfo
- Exec sp_executesql N'Restore HeaderOnly From Disk=@DatabBaseBakPath',N'@DatabBaseBakPath nvarchar(260)',@DatabBaseBakPath
- If Isnull(@NewDataBaseName,'')>'' --使用新的數據庫名,要是沒有指定就使用原來的數據庫名
- Set @DataBaseName=@NewDataBaseName
- Else
- Begin
- Select @DataBaseName=DatabaseName From @BakHeaderInfo
- End
- Set @Enter=char(13)+Char(10)
- Select @Sql=Isnull(@Sql+@Enter,'')+'Kill '+Rtrim(spid) From master.sys.sysprocesses Where dbid=db_id(@DataBaseName)
- Exec(@Sql)
- Set @Sql=N'Restore DataBase @DataBaseName From Disk=@DatabBaseBakPath With File=??,' --+(Select 'File='+rtrim(max(Position))+', ' From @BakHeaderInfo)
- Select @Sql=@Sql+'Move '''+LogicalName+''' To '''+@DatabBaseNewPath+'\'+@DataBaseName+
- Case
- When [Type]='D' And Not Exists(Select 1 From @BakFileList Where [Type]=a.[Type] And FileID<a.FileID) Then ''
- When [Type]='D' Then '_'+(Select Rtrim(Count(*)) From @BakFileList Where [Type]=a.[Type] And FileID<a.FileID)
- When [Type]='L' And Not Exists(Select 1 From @BakFileList Where [Type]=a.[Type] And FileID<a.FileID) Then '_Log'
- When [Type]='L' Then '_Log'+(Select Rtrim(Count(*)) From @BakFileList Where [Type]=a.[Type] And FileID<a.FileID)
- Else ''
- End+Right(PhysicalName,Charindex('.',Reverse(PhysicalName)))+''',',
- @SqlDatabaseRename=Isnull(@SqlDatabaseRename+@Enter,'')+
- Case
- When [Type]='D' And LogicalName=@DataBaseName Then ''
- When [Type]='D' And LogicalName Like @DataBaseName+'[_]%' Then ''
- When [Type]='L' And LogicalName Like @DataBaseName+'[_]Log%' Then ''
- When [Type]='F' Then ''
- Else
- 'Alter DataBase '+Quotename(@DataBaseName)+' Modify File(Name='''+LogicalName+''',NewName='''+@DataBaseName+
- Case
- When [Type]='D' And Not Exists(Select 1 From @BakFileList Where [Type]=a.[Type] And FileID<a.FileID) Then ''
- When [Type]='D' Then '_'+(Select Rtrim(Count(*)) From @BakFileList Where [Type]=a.[Type] And FileID<a.FileID)
- When [Type]='L' And Not Exists(Select 1 From @BakFileList Where [Type]=a.[Type] And FileID<a.FileID) Then '_Log'
- When [Type]='L' Then '_Log'+(Select Rtrim(Count(*)) From @BakFileList Where [Type]=a.[Type] And FileID<a.FileID)
- Else ''
- End+''')'
- End
- From @BakFileList As a
- Set @Sql=@Sql+'Replace'
- If @Keep_Replication=1
- Set @Sql=@Sql+'Keep_Replication'
- Declare @sql1 nvarchar(max),
- @MaxPosition int
- Select @MaxPosition=Position
- From @BakHeaderInfo As a
- Where a.BackupType=1
- And Not Exists(Select 1 From @BakHeaderInfo Where BackupType=a.BackupType And Position>a.Position)
- Select @sql1=isnull(@sql1+char(13)+char(10),'')+replace(@sql,'??',rtrim(Position)) +
- Case When Exists(Select 1 From @BakHeaderInfo Where Position>a.Position) Then N',Norecovery' Else N',Recovery' End
- From @BakHeaderInfo As a
- Where Position>=@MaxPosition
- Order By Position
- Print '還原數據庫: '+@DataBaseName
- -- Print @sql1
- Exec sp_executesql @sql1,N'@DataBaseName nvarchar(128),@DatabBaseBakPath nvarchar(260)',@DataBaseName,@DatabBaseBakPath
- If @SqlDatabaseRename>''
- Exec(@SqlDatabaseRename)
- End Try
- Begin Catch
- Declare @Error nvarchar(1024)
- Set @Error=ERROR_MESSAGE()
- Raiserror 50001 @Error
- Return 1
- End Catch
- Set Nocount Off
- Go
存儲過程測試:
- use master
- Go
- Exec dbo.sp_RestoreDataBase 'E:\DBBackup\dbA2011-09-05.bak','E:\DATA\SQL2008DE01'
- go
●還原多個數據庫的存儲過程代碼(一個目錄下)
當一個目錄下,存放這很多個備份文件的時候,我們需要還原整個目錄,或者部分數據庫備份文件,這時我們另外一個存儲過程能實現這樣的操作。這裡我寫一個存儲過程sp_RestoreDataBase2:
- if object_id('sp_restoredatabase2') Is Not Null
- Drop proc sp_restoredatabase2
- Go
- create proc sp_restoredatabase2
- (
- @Path_bak nvarchar(1024),
- @Path_new nvarchar(1024)=null,
- @DataBaseList nvarchar(max)=null
- )
- As
- /*
- @DataBaseList 數據庫列表,可留空,或數據庫之間使用回車、空格、逗號分隔都ok
- */
- Set Nocount On
- Declare @subdirectory nvarchar(1024),@ErrorMsg nvarchar(1024),@flag smallint
- if isnull(@Path_new,'')=''
- exec master.dbo.xp_instance_regread N'HKEY_LOCAL_MacHINE', N'Software\Microsoft\MSSQLServer\MSSQLServer', N'DefaultData', @Path_new Output
- If Not Exists(Select 1 From master.sys.procedures Where name='sp_RestoreDataBase')
- Begin
- Raiserror 50001 N'找不到存儲過程 sp_RestoreDataBase '
- Goto ExitFLag
- End
- Declare @Dir Table(subdirectory nvarchar(1024),depth int,[file] int)
- Declare @DBList table (DatabaseName nvarchar(128))
- Declare @DBListNull table (DatabaseName nvarchar(128))
- /*
- --SQL Server 2005
- Declare @BakHeaderTMP Table(BackupName nvarchar(128),BackupDescription nvarchar(255),BackupType smallint,ExpirationDate datetime,Compressed tinyint,Position smallint,DeviceType tinyint,UserName nvarchar(128),ServerName nvarchar(128),DatabaseName nvarchar(128),DatabaseVersion int,DatabaseCreationDate datetime,BackupSize numeric(20,0),FirstLSN numeric(25,0),LastLSN numeric(25,0),CheckpointLSN numeric(25,0),DatabaseBackupLSN numeric(25,0),BackupStartDate datetime,BackupFinishDate datetime,SortOrder smallint,CodePage smallint,UnicodeLocaleId int,UnicodeComparisonStyle int,CompatibilityLevel tinyint,SoftwareVendorId int,SoftwareVersionMajor int,SoftwareVersionMinor int,SoftwareVersionBuild int,MachineName nvarchar(128),Flags int,BindingID uniqueidentifier,RecoveryForkID uniqueidentifier,Collation nvarchar(128),FamilyGUID uniqueidentifIEr,HasBulkLoggedData bit,IsSnapshot bit,IsReadOnly bit,IsSingleUser bit,HasBackupChecksums bit,IsDamaged bit,BeginsLogChain bit,HasIncompleteMetaData bit,IsForceOffline bit,IsCopyOnly bit,FirstRecoveryForkID uniqueidentifIEr,ForkPointLSN numeric(25,0) NULL,RecoveryModel nvarchar(60),DifferentialBaseLSN numeric(25,0) NULL,DifferentialBaseGUID uniqueidentifier,BackupTypeDescription nvarchar(60),BackupSetGUID uniqueidentifIEr NULL)
- */
- --SQL Server 2008
- Declare @BakHeaderTMP Table(BackupName nvarchar(128),BackupDescription nvarchar(255),BackupType smallint,ExpirationDate datetime,Compressed tinyint,Position smallint,DeviceType tinyint,UserName nvarchar(128),ServerName nvarchar(128),DatabaseName nvarchar(128),DatabaseVersion int,DatabaseCreationDate datetime,BackupSize numeric(20,0),FirstLSN numeric(25,0),LastLSN numeric(25,0),CheckpointLSN numeric(25,0),DatabaseBackupLSN numeric(25,0),BackupStartDate datetime,BackupFinishDate datetime,SortOrder smallint,CodePage smallint,UnicodeLocaleId int,UnicodeComparisonStyle int,CompatibilityLevel tinyint,SoftwareVendorId int,SoftwareVersionMajor int,SoftwareVersionMinor int,SoftwareVersionBuild int,MachineName nvarchar(128),Flags int,BindingID uniqueidentifier,RecoveryForkID uniqueidentifier,Collation nvarchar(128),FamilyGUID uniqueidentifIEr,HasBulkLoggedData bit,IsSnapshot bit,IsReadOnly bit,IsSingleUser bit,HasBackupChecksums bit,IsDamaged bit,BeginsLogChain bit,HasIncompleteMetaData bit,IsForceOffline bit,IsCopyOnly bit,FirstRecoveryForkID uniqueidentifIEr,ForkPointLSN numeric(25,0) NULL,RecoveryModel nvarchar(60),DifferentialBaseLSN numeric(25,0) NULL,DifferentialBaseGUID uniqueidentifier,BackupTypeDescription nvarchar(60),BackupSetGUID uniqueidentifIEr NULL,CompressedBackupSize numeric(20,0))
- While charindex(char(13)+Char(10),@DataBaseList)>0
- Set @DataBaseList=Replace(@DataBaseList,char(13)+Char(10),',')
- While charindex(char(13),@DataBaseList)>0
- Set @DataBaseList=Replace(@DataBaseList,char(13),',')
- While charindex(char(9),@DataBaseList)>0
- Set @DataBaseList=Replace(@DataBaseList,char(9),',')
- While charindex(char(32),@DataBaseList)>0
- Set @DataBaseList=Replace(@DataBaseList,Char(32),',')
- Set @DataBaseList='Select '''+Replace(@DataBaseList,',',''' Union All Select ''')+''''
- Insert Into @DBList Exec(@DataBaseList)
- Delete @DBList Where DatabaseName =''
- Insert Into @Dir Exec xp_dirtree @Path_bak,1,1
- If Not Exists(Select subdirectory From @Dir Where Charindex('.bak',subdirectory)>0)
- Begin
- Set @ErrorMsg= N'無效的數據庫路徑: '+ rtrim(@Path_bak)
- Raiserror 50001 @ErrorMsg
- Goto ExitFlag
- End
- Declare cur_x cursor For Select subdirectory From @Dir Where Charindex('.bak',subdirectory)>0
- Open cur_x
- Fetch Next From cur_x Into @subdirectory
- While @@Fetch_status=0
- Begin
- Set @subdirectory=Case When Right(@Path_bak,1)='\' Then @Path_bak Else @Path_bak+'\' End+@subdirectory
- Delete From @BakHeaderTMP
- Insert Into @BakHeaderTMP
- Exec sp_executesql N'Restore HeaderOnly From Disk=@DatabBaseBakPath',N'@DatabBaseBakPath nvarchar(260)',@subdirectory
- IF @@ERROR <> 0
- Break
- Else if Exists(Select 1 From @BakHeaderTMP As a Where Exists(Select 1 From @DBList Where DatabaseName=a.DatabaseName) Or Not Exists(Select 1 From @DBList))
- Begin
- Exec @flag=sp_RestoreDataBase @subdirectory,@Path_new
- If @flag <>0 Break
- End
- Insert Into @DBListNull(DatabaseName)
- Select DatabaseName From @BakHeaderTMP
- Fetch Next From cur_x Into @subdirectory
- End
- CLose cur_x
- Deallocate cur_x
- If Exists(Select 1 From @DBList a Where Not Exists(Select 1 From @DBListNull Where DatabaseName=a.DatabaseName))
- Select DatabaseName As [無效的數據庫] From @DBList a Where Not Exists(Select 1 From @DBListNull Where DatabaseName=a.DatabaseName)
- print replicate('=',60)
- Print N'@Path_new : '+@Path_new
- ExitFLag:
- Go
存儲過程測試:
- use master
- Go
- Exec dbo.sp_RestoreDataBase2 'E:\DBBackup'
- go
小結
上面還原數據庫的存儲過程,它們給我們在工作中還原數據庫的時候,帶來許多便捷,如,不用我們一個個通過Microsoft SQL Server Management Studio(MSSMS)中的還原數據庫向導去還原數據庫,或也不用我們一個個執行”Restore Database”SQL語句去還原數據庫。當然,在上面的代碼中,我沒有對每一個存儲過程的每一個具體位置,進行解釋。沒有全部應用到”Restore Database”中”WITH”選項,我編寫的主要目的是,存儲過程參數盡可能的少,操作起來更方便,盡可能滿足真實環境中的需要。如果你應用到以上的代碼,可以根據自己所在的真實環境,進行修改補充。