我编写了一个存储过程,可以恢复为数据库备份集。它需要两个参数 - 源目录和还原目录。该过程在源目录中查找所有.bak文件(递归)并恢复所有数据库。
存储过程按预期工作,但它有一个问题 - 如果我取消注释try-catch语句,该过程将终止并出现以下错误:
error_number = 3013
error_severity = 16
error_state = 1
error_message = DATABASE is terminating abnormally.
奇怪的部分有时(不一致)即使发生错误也会完成恢复。程序:
create proc usp_restore_databases
(
@source_directory varchar(1000),
@restore_directory varchar(1000)
)
as
begin
declare @number_of_backup_files int
-- begin transaction
-- begin try
-- step 0: Initial validation
if(right(@source_directory, 1) <> '\') set @source_directory = @source_directory + '\'
if(right(@restore_directory, 1) <> '\') set @restore_directory = @restore_directory + '\'
-- step 1: Put all the backup files in the specified directory in a table --
declare @backup_files table ( file_path varchar(1000))
declare @dos_command varchar(1000)
set @dos_command = 'dir ' + '"' + @source_directory + '*.bak" /s/b'
/* DEBUG */ print @dos_command
insert into @backup_files(file_path) exec xp_cmdshell @dos_command
delete from @backup_files where file_path IS NULL
select @number_of_backup_files = count(1) from @backup_files
/* DEBUG */ select * from @backup_files
/* DEBUG */ print @number_of_backup_files
-- step 2: restore each backup file --
declare backup_file_cursor cursor for select file_path from @backup_files
open backup_file_cursor
declare @index int; set @index = 0
while(@index < @number_of_backup_files)
begin
declare @backup_file_path varchar(1000)
fetch next from backup_file_cursor into @backup_file_path
/* DEBUG */ print @backup_file_path
-- step 2a: parse the full backup file name to get the DB file name.
declare @db_name varchar(100)
set @db_name = right(@backup_file_path, charindex('\', reverse(@backup_file_path)) -1) -- still has the .bak extension
/* DEBUG */ print @db_name
set @db_name = left(@db_name, charindex('.', @db_name) -1)
/* DEBUG */ print @db_name
set @db_name = lower(@db_name)
/* DEBUG */ print @db_name
-- step 2b: find out the logical names of the mdf and ldf files
declare @mdf_logical_name varchar(100),
@ldf_logical_name varchar(100)
declare @backup_file_contents 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
)
insert into @backup_file_contents
exec ('restore filelistonly from disk=' + '''' + @backup_file_path + '''')
select @mdf_logical_name = LogicalName from @backup_file_contents where [Type] = 'D'
select @ldf_logical_name = LogicalName from @backup_file_contents where [Type] = 'L'
/* DEBUG */ print @mdf_logical_name + ', ' + @ldf_logical_name
-- step 2c: restore
declare @mdf_file_name varchar(1000),
@ldf_file_name varchar(1000)
set @mdf_file_name = @restore_directory + @db_name + '.mdf'
set @ldf_file_name = @restore_directory + @db_name + '.ldf'
/* DEBUG */ print 'mdf_logical_name = ' + @mdf_logical_name + '|' +
'ldf_logical_name = ' + @ldf_logical_name + '|' +
'db_name = ' + @db_name + '|' +
'backup_file_path = ' + @backup_file_path + '|' +
'restore_directory = ' + @restore_directory + '|' +
'mdf_file_name = ' + @mdf_file_name + '|' +
'ldf_file_name = ' + @ldf_file_name
restore database @db_name from disk = @backup_file_path
with
move @mdf_logical_name to @mdf_file_name,
move @ldf_logical_name to @ldf_file_name
-- step 2d: iterate
set @index = @index + 1
end
close backup_file_cursor
deallocate backup_file_cursor
-- end try
-- begin catch
-- print error_message()
-- rollback transaction
-- return
-- end catch
--
-- commit transaction
end
有人有任何想法可能会发生这种情况吗?
另一个问题:交易代码有用吗?即,如果有2个要恢复的数据库,如果第二次恢复失败,SQL Server是否会撤消一个数据库的恢复?
答案 0 :(得分:1)
基本上,发生的事情是需要恢复的其中一个文件出现问题,并且恢复过程引发了错误,但错误不足以中止proc。这就是没有try-catch就没有问题的原因。但是,添加try-catch会捕获严重性大于10的任何错误,因此控制流切换到catch块,该块显示错误消息并中止proc。
答案 1 :(得分:1)
另外,如果你没有从文件列表中删除NULL记录(因为它已被注释掉),那么从你的循环开始为0,它最终处理一个不存在的文件进行最后一次迭代。
而不是@index=0
而不是@index=1
或取消注释
delete from @backup_files where file_path IS NULL
答案 2 :(得分:0)
我注意到的问题:
尝试此修改后的代码。它将证明您的代码工作正常..
ALTER proc usp_restore_databases
(
@source_directory varchar(1000),
@restore_directory varchar(1000)
)
as
begin
declare @number_of_backup_files int
begin transaction
begin try
print 'Entering TRY...'
-- step 0: Initial validation
if(right(@source_directory, 1) <> '\') set @source_directory = @source_directory + '\'
if(right(@restore_directory, 1) <> '\') set @restore_directory = @restore_directory + '\'
-- step 1: Put all the backup files in the specified directory in a table --
declare @backup_files table ( file_path varchar(1000))
declare @dos_command varchar(1000)
set @dos_command = 'dir ' + '"' + @source_directory + '*.bak" /s/b'
/* DEBUG */ print @dos_command
insert into @backup_files(file_path) exec xp_cmdshell @dos_command
--delete from @backup_files where file_path IS NULL
select @number_of_backup_files = count(1) from @backup_files
/* DEBUG */ select * from @backup_files
/* DEBUG */ print @number_of_backup_files
-- step 2: restore each backup file --
declare backup_file_cursor cursor for select file_path from @backup_files
open backup_file_cursor
declare @index int; set @index = 0
while(@index < @number_of_backup_files)
begin
declare @backup_file_path varchar(1000)
fetch next from backup_file_cursor into @backup_file_path
/* DEBUG */ print @backup_file_path
-- step 2a: parse the full backup file name to get the DB file name.
declare @db_name varchar(100)
set @db_name = right(@backup_file_path, charindex('\', reverse(@backup_file_path)) -1) -- still has the .bak extension
/* DEBUG */ print @db_name
set @db_name = left(@db_name, charindex('.', @db_name) -1)
/* DEBUG */ print @db_name
set @db_name = lower(@db_name)
/* DEBUG */ print @db_name
-- step 2b: find out the logical names of the mdf and ldf files
declare @mdf_logical_name varchar(100),
@ldf_logical_name varchar(100)
declare @backup_file_contents 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
)
insert into @backup_file_contents
exec ('restore filelistonly from disk=' + '''' + @backup_file_path + '''')
select @mdf_logical_name = LogicalName from @backup_file_contents where [Type] = 'D'
select @ldf_logical_name = LogicalName from @backup_file_contents where [Type] = 'L'
/* DEBUG */ print @mdf_logical_name + ', ' + @ldf_logical_name
-- step 2c: restore
declare @mdf_file_name varchar(1000),
@ldf_file_name varchar(1000)
set @mdf_file_name = @restore_directory + @db_name + '.mdf'
set @ldf_file_name = @restore_directory + @db_name + '.ldf'
/* DEBUG */ print 'mdf_logical_name = ' + @mdf_logical_name + '|' +
'ldf_logical_name = ' + @ldf_logical_name + '|' +
'db_name = ' + @db_name + '|' +
'backup_file_path = ' + @backup_file_path + '|' +
'restore_directory = ' + @restore_directory + '|' +
'mdf_file_name = ' + @mdf_file_name + '|' +
'ldf_file_name = ' + @ldf_file_name
print @index
-- restore database @db_name from disk = @backup_file_path
-- with
-- move @mdf_logical_name to @mdf_file_name,
-- move @ldf_logical_name to @ldf_file_name
-- step 2d: iterate
set @index = @index + 1
end
close backup_file_cursor
deallocate backup_file_cursor
end try
begin catch
print 'Entering Catch...'
print error_message()
rollback transaction
return
end catch
commit transaction
end
拉吉
答案 3 :(得分:0)
这里的实际问题是try和catch仅为您提供了最后一条错误消息3013“备份异常终止”,但由于触发了3013错误,因此没有给出较低级别的错误。
如果执行备份命令(例如数据库名不正确),则会出现2个错误。 将数据库errors_database_name备份到disk ='drive:\ path \ filename.bak'
Msg 911, Level 16, State 11, Line 1
Could not locate entry in sysdatabases for database 'incorrect_database_name'. No entry found with that name. Make sure that the name is entered correctly.
Msg 3013, Level 16, State 1, Line 1
BACKUP DATABASE is terminating abnormally.
如果你想知道备份在try a catch中失败的实际错误,那么存储过程就会掩盖它。
现在,关于你的问题..我要做的是当恢复成功时,我会立即删除或将.bak移动到新位置,从而将其从您在参数中声明的目录中删除。一旦失败,你的catch语句可以包含一个GOTO,它会在BEGIN TRY之前返回到它并从它停止的地方开始执行,因为它不会递归地检测你从目录移动的文件。
RUN_AGAIN:
BEGIN TRY
RECURSIVE DIR FOR FILENAMES
RESTORE DATABASE...
ON SUCCEED, DELETE .BAK FILE
END TRY
BEGIN CATCH
ON FAILURE, MOVE .BAK to A SAFE LOCATION FOR LATER ANALYSIS
GOTO RUN_AGAIN
END CATCH
我不是说它很漂亮,但它会起作用。您不能在TRY / CATCH块中放置GOTO引用,因此它必须在它之外。
无论如何,我只是想我会加入我的想法,即使这个问题很老,只是为了帮助其他人处于同样的境地。