トランザクションでの複数のデータベースバックアップの復元
-
06-07-2019 - |
質問
データベースバックアップのセットとして復元するストアドプロシージャを作成しました。ソースディレクトリと復元ディレクトリの2つのパラメータを取ります。このプロシージャは、ソースディレクトリ内のすべての.bakファイルを(再帰的に)検索し、すべてのデータベースを復元します。
ストアドプロシージャは期待どおりに動作しますが、1つの問題があります。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つある場合、2番目の復元が失敗すると、SQL Serverは1つのデータベースの復元を取り消しますか?
解決
本質的に、復元が必要なファイルの1つに問題があり、復元プロセスでエラーがスローされていましたが、そのエラーはprocを中止するほど深刻ではありませんでした。これが、try-catchなしでは問題ない理由です。ただし、try-catchを追加すると、重大度が10を超えるエラーがトラップされるため、制御フローはcatchブロックに切り替わり、エラーメッセージが表示され、procが中止されます。
他のヒント
また、ファイルリストからNULLレコードを削除していない場合(コメントアウトされているため)、0から始まるループで、最後の反復で存在しないファイルを処理します。
@ index = 0
ではなく、 @ index = 1
またはコメントを外します
file_pathがNULLの@backup_filesから削除
気づいた問題:
- トランザクションをコミットする必要があります BEGIN TRY .... END TRYブロック
- カーソルが閉じられない、または エラーが発生した場合は割り当て解除 遭遇し、制御はBEGINに行きます CATCH ... END CATCHブロック
この変更されたコードを試してください。コードが正常に機能していることを示します。
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
Raj
ここでの実際の問題は、try and catchが最後のエラーメッセージ3013「バックアップが異常終了しました」のみを提供するが、3013エラーがトリガーされた理由により低いレベルのエラーを提供しないことです。
誤ったデータベース名などでバックアップコマンドを実行すると、2つのエラーが発生します。 ディスクへのデータベースの誤ったデータベース名のバックアップ= '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ステートメントには、BEGIN TRYの前に戻り、ディレクトリから移動したファイルを再帰的に検出しないため、中断したところから実行を開始するGOTOを含めることができます。
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参照を配置することはできません。そのため、GOTO参照はその外部になければなりません。
とにかく、質問が古い場合でも、同じ状況で他の人を助けるために、これに自分の考えを追加すると思いました。