mardi 1 décembre 2009

sp_force_shrink_log pour Sql Serveur 2005

Déja relaté dans l'article "Utilitaires pour SQL Server 7", la stored procedure sp_force_shrink_log  de Andrew Zanevsky (AZ databases) permet de tronquer le transaction log de façon nettement plus efficace que DBCC SHRINKFILE.
En effet, DBCC SHRINKFILE ne fait que tronquer la partie inactive en fin du fichier de transaction.
Si le transaction log fait 25Go et que "le pointeur de transaction" se trouve actuellement en fin de fichier (ex: à l'offset 24.7 Go, ce qui est assez fréquent); dans ce cas, DBCC SHRINKFILE ne libèrera que 300Mo.
Il faudra attendre un roll-over vers le début du fichier de transaction pour avoir un résultat probant.

Lorsque que le transaction log fait 25 Go et qu'il étouffe littéralement le système de fichier, il n'est pas forcement possible d'attendre patiemment l'opération de Roll-over sur le transaction log. Libérer de l'espace disque peut être impératif pour la bonne conduite d'autres opérations sql.
C'est là qu'intervient précisément sp_force_shrink_log. Cette store procédure va déplacer le pointeur de transaction dans le transaction log en simulant des activités sql sur une table temporaire. Une fois le roll-over effectué (ce qui arrivera tôt ou tard), le transaction log sera efficacement tronqué et la stored procedure pourra interrompre son traitement.
Malheureusement, la store procedure d'origine prévue pour SQL 7 ne fonctionne pas correctement sur SQL Serveur 2005. En effet, le résultat retourné par DBCC LogInfo (utilisé par sp_force_Shrink_log) a été modifié depuis SQL 7.
Voici donc une petite mise à jour (faite par mes soins) du script sp_force_shrink_log pour SQL Serveur 2005 (sp_force_shrink_log_2005.sql).

use master
go
if object_id( 'sp_force_shrink_log' ) is not null drop proc sp_force_shrink_log
go
create proc sp_force_shrink_log 
/*
*************************************************************
Name: sp_force_shrink_log
Description:
  Shrink transaction log of the current database in SQL Server 2005.
  Switch context to proper db to execute.
Usage: exec sp_force_shrink_log <target_percent>, <target MB>, <iterations>, <backup options>
   exec pubs..sp_force_shrink_log

Author:  Andrew Zanevsky, AZ Databases, Inc., 12/25/1999, v5 - 08/18/2000
   zanevsky@azdatabases.com

Note:  D Meurisse, *no_company_info* , 12/01/2009, v5.1 - 12/01/2009
   Updated this wonderfull script to make it running on SQL 2005. 
   Updated the description and change version to v5.1 
   
Input Params:
-------------
@target_percent  tinyint. default = 0. Target percentage of remaining shrinkable
      space. Defaults to max possible.
@target_size_MB  int. default = 10. Target size of final log in MB.
@max_iterations  int. default = 1000. Number of loops (max) to run proc through.
@backup_log_opt  nvarchar(1000). default = 'with truncate_only'. Backup options.

Output Params:
--------------

Return:

Results:
---------

Locals:
--------
@err  Holds error value

Modifications:
--------------
  V5.1 - Meurisse D. - 1 dec 2009 - small changes to make it working with Sql 2005

************************************************************* */
    @target_percent tinyint = 0,
    @target_size_MB int = 10,
    @max_iterations int = 1000,
    @backup_log_opt nvarchar(1000) = 'with truncate_only'
as 

set nocount on

declare @db         sysname, 
        @last_row   int,
        @log_size   decimal(15,2),
        @unused1    decimal(15,2),
        @unused     decimal(15,2),
        @shrinkable decimal(15,2),
        @iteration  int,
 @file_max   int,
 @file     int,
 @fileid     varchar(5)

select  @db = db_name(),
        @iteration = 0
/* drop table #LogInfo
   drop table #LogFiles
*/
create table #loginfo ( 
    id          int identity, 
    FileId      int, 
    FileSize    numeric(22,0), 
    StartOffset numeric(22,0), 
    FSeqNo      int, 
    Status      int, 
    Parity      smallint, 
    /* CreateTime  datetime */
 CreateLSN varchar(50)
)

create unique clustered index loginfo_FSeqNo on #loginfo ( FSeqNo, StartOffset )

create table #logfiles ( id int identity(1,1), fileid varchar(5) not null )

insert #logfiles ( fileid ) select convert( varchar, fileid ) from sysfiles where status & 0x40 = 0x40        
select @file_max = @@rowcount
if object_id( 'table_to_force_shrink_log' ) is null
 exec( 'create table table_to_force_shrink_log ( x nchar(3000) not null )' )
insert  #loginfo ( FileId, FileSize, StartOffset, FSeqNo, Status, Parity, /*CreateTime*/ CreateLSN ) exec ( 'dbcc loginfo' )
select  @last_row = @@rowcount
select  @log_size = sum( FileSize ) / 1048576.00,
        @unused = sum( case when Status = 0 then FileSize else 0 end ) / 1048576.00,
        @shrinkable = sum( case when id < @last_row - 1 and Status = 0 then FileSize else 0 end ) / 1048576.00
from    #loginfo
select  @unused1 = @unused -- save for later

select  'iteration'          = @iteration,
        'log size, MB'       = @log_size,
        'unused log, MB'     = @unused,
        'shrinkable log, MB' = @shrinkable,
        'shrinkable %'       = convert( decimal(6,2), @shrinkable * 100 / @log_size )
print 'xx.10'
while @shrinkable * 100 / @log_size > @target_percent 
  and @shrinkable > @target_size_MB 
  and @iteration < @max_iterations begin
    select  @iteration = @iteration + 1 -- this is just a precaution

    exec( 'insert table_to_force_shrink_log select name from sysobjects
           delete table_to_force_shrink_log')
    select @file = 0
    while @file < @file_max begin
        select @file = @file + 1
        select @fileid = fileid from #logfiles where id = @file
        exec( 'dbcc shrinkfile( ' + @fileid + ' )' )
    end

    exec( 'backup log [' + @db + '] ' + @backup_log_opt )

    truncate table #loginfo 
    insert  #loginfo ( FileId, FileSize, StartOffset, FSeqNo, Status, Parity, /* CreateTime */ CreateLSN ) exec ( 'dbcc loginfo' )
    select  @last_row = @@rowcount

    select  @log_size = sum( FileSize ) / 1048576.00,
            @unused = sum( case when Status = 0 then FileSize else 0 end ) / 1048576.00,
     @shrinkable = sum( case when id < @last_row - 1 and Status = 0 then FileSize else 0 end ) / 1048576.00
    from    #loginfo

    select  'iteration'          = @iteration,
            'log size, MB'       = @log_size,
            'unused log, MB'     = @unused,
            'shrinkable log, MB' = @shrinkable,
            'shrinkable %'       = convert( decimal(6,2), @shrinkable * 100 / @log_size )
end

if @unused1 < @unused 
select  'After ' + convert( varchar, @iteration ) + 
        ' iterations the unused portion of the log has grown from ' +
        convert( varchar, @unused1 ) + ' MB to ' +
        convert( varchar, @unused ) + ' MB.'
union all
select 'Since the remaining unused portion is larger than 10 MB,' where @unused > 10
union all
select 'you may try running this procedure again with a higher number of iterations.' where @unused > 10
union all
select 'Sometimes the log would not shrink to a size smaller than several Megabytes.' where @unused <= 10

else
select  'It took ' + convert( varchar, @iteration ) + 
        ' iterations to shrink the unused portion of the log from ' +
        convert( varchar, @unused1 ) + ' MB to ' +
        convert( varchar, @unused ) + ' MB'

exec( 'drop table table_to_force_shrink_log' )
 go
if object_id( 'sp_force_shrink_log') Is Null
 select 'sp_force_shrink_log Not Created'
else
 select 'sp_force_shrink_log Created'
go 

ressources:

Aucun commentaire: