Question

J'ai écrit une procédure stockée qui restaure en tant qu'ensemble des sauvegardes de la base de données. Il faut deux paramètres - un répertoire source et un répertoire de restauration. La procédure recherche (de manière récursive) tous les fichiers .bak dans le répertoire source et restaure toutes les bases de données.

La procédure stockée fonctionne comme prévu, mais il y a un problème: si je retire le commentaire des instructions try-catch, la procédure se termine avec l'erreur suivante:

error_number = 3013  
error_severity = 16  
error_state = 1  
error_message = DATABASE is terminating abnormally.

La partie étrange est parfois (ce n'est pas cohérent) la restauration est effectuée même si l'erreur se produit. La procédure:

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

Quelqu'un at-il une idée de la raison pour laquelle cela pourrait se produire?

Autre question: le code de transaction est-il utile? En d’autres termes, si deux bases de données doivent être restaurées, SQL Server annulera-t-il la restauration d’une base de données si la deuxième restauration échoue?

Était-ce utile?

La solution

Essentiellement, un des fichiers à restaurer présentait un problème et le processus de restauration générait une erreur, mais cette erreur n’était pas assez grave pour annuler le processus. C'est la raison pour laquelle il n'y a pas de problème sans le try-catch. Cependant, l’ajout de try-catch intercepte toute erreur dont la gravité est supérieure à 10; le flux de contrôle bascule donc vers le bloc catch, qui affiche les messages d’erreur et abandonne le processus.

Autres conseils

En outre, si vous ne supprimez pas les enregistrements NULL de votre liste de fichiers (depuis sa mise en commentaire), alors avec votre boucle commençant à 0, il finit par traiter un fichier non existant pour sa dernière itération.

Au lieu de @ index = 0 , il devrait être @ index = 1

ou décommentez le supprimer de @backup_files où file_path IS NULL

Problèmes constatés:

  • La transaction doit être à l'intérieur le bloc BEGIN TRY .... END TRY
  • le curseur ne sera pas fermé ou libéré si une erreur est rencontré et le contrôle va commencer CATCH ... END CATCH block

Essayez ce code modifié. Cela démontrera que votre code fonctionne bien.

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

Le problème actuel est que try and catch ne vous donne que le dernier message d'erreur 3013 "La sauvegarde se termine anormalement", mais ne vous donne pas l'erreur de niveau inférieur pour la raison pour laquelle l'erreur 3013 a été déclenchée.

Si vous exécutez une commande de sauvegarde, par exemple avec un nom de base de données incorrect, vous obtiendrez 2 erreurs. sauvegarde de la base de données incorrect_nom_base_données sur le disque = 'lecteur: \ chemin \ nom_fichier.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.

Si vous souhaitez connaître l’erreur réelle expliquant l’échec de la sauvegarde lors d’une tentative d’attrape, la procédure stockée la masque.

Maintenant, passons à votre question. Ce que je ferais, c’est qu’une restauration réussie, j’effacerais immédiatement ou déplacerais le .bak vers un nouvel emplacement, le retirant ainsi du répertoire que vous avez indiqué dans votre paramètre. En cas d'échec, votre instruction catch peut contenir un GOTO qui vous ramène avant BEGIN TRY et commence à s'exécuter là où elle s'est arrêtée car elle ne détectera pas de manière récursive les fichiers que vous avez déplacés du répertoire.

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

Je ne dis pas que c'est joli, mais ça va marcher. Vous ne pouvez pas mettre une référence GOTO dans un bloc TRY / CATCH, elle doit donc être en dehors de celui-ci.

Quoi qu’il en soit, j’ai juste pensé y ajouter ma pensée, même si la question est ancienne, pour aider les autres dans la même situation.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top