1

I have many DBs that are currently used for insert and delete. For maintenance i create a procedure that:

-Shrink Database file (if is enabled)

-Shrink Database log file

-Search all indexes of a table that have 5% or more of fragmentation and page_count >= 1000 (According this Microsoft Web about page_count)

-Rebuild indexes > 30% fragmentation and Reorganize indexes between 5% and 30% (According to this Microsoft Web)

-Shrink Database log file of the DB

I execute the procedure between trimester and annual depends use of the DB.

The database file is shrink only when specified with exec spIndexDefrag 1

Value 0 always shrink database log file at begin and finish process.

For now I use 1 in @DB_SHRINK ONLY IN SOME CASES THAT ARE REQUIRED FOR DISK SPACE. Only Shrink the log file.

Is safe do that?

This is my procedure, is applicable to any DB, only need to select the DB in management.

CREATE PROCEDURE dbo.spIndexDefrag @DB_SHRINK bit
AS

DECLARE @COUNT_INDEXES INT = 1
DECLARE @TOTALTEMP_INDEXES INT
DECLARE @COUNT_TABLES INT = 1
DECLARE @TOTALTEMP_TABLES INT
DECLARE @QUERY VARCHAR (MAX) = ''
DECLARE @INDEXNAME VARCHAR (50)
DECLARE @TABLENAME VARCHAR (50)
DECLARE @FRAGMENTATION DECIMAL(18,2)
DECLARE @DBNAME VARCHAR (50) = DB_NAME()
DECLARE @LOGICALDBNAME VARCHAR (50)
DECLARE @LOGICALDBNAME_LOG VARCHAR (50)
DECLARE @QUERYSHRINK VARCHAR (300)
DECLARE @QUERYSHRINK_DB_ENABLED VARCHAR (100) = ''
DECLARE @NEWLINE AS CHAR(2) = CHAR(13) + CHAR(10)

--Nombre logico de la base de datos
SELECT @LOGICALDBNAME = name
FROM sys.master_files
WHERE database_id = db_id()
AND type = 0

--Nombre logico del log de la base de datos
SELECT @LOGICALDBNAME_LOG = name
FROM sys.master_files
WHERE database_id = db_id()
AND type = 1

--Si esta habilitado el shrink de la DB se incluye el codigo en la variable
IF @DB_SHRINK = 1
BEGIN
    SET @QUERYSHRINK_DB_ENABLED = 'DBCC SHRINKFILE('''+@LOGICALDBNAME+''', 2)'
END

--Se hace un shrink a la base de datos (Si esta disponible) y posteriormente al log
SET @QUERYSHRINK = '
ALTER DATABASE ['+@DBNAME+'] SET RECOVERY SIMPLE WITH NO_WAIT
'+@QUERYSHRINK_DB_ENABLED+'
DBCC SHRINKFILE('''+@LOGICALDBNAME_LOG+''', 2)
ALTER DATABASE ['+@DBNAME+'] SET RECOVERY FULL WITH NO_WAIT'

EXEC (@QUERYSHRINK)

/*
EL LOG DE LA BASE DE DATOS SE TRUNCA ANTES Y DESPUES 
DE REORGANIZAR Y RECONSTRUIR INDICES DE LA TABLA
*/

--Selecciono todas las tablas y vistas en la base de dato y la inserto en tabla temporal
SELECT ROW_NUMBER() OVER (Order by TABLE_NAME) AS ID, TABLE_NAME
INTO #TEMPTABLES
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE IN ('BASE TABLE','VIEW') AND TABLE_CATALOG = @DBNAME

SELECT @TOTALTEMP_TABLES = COUNT(ID) FROM #TEMPTABLES --Total de tablas y vistas

--Si existen tablas
IF @TOTALTEMP_TABLES > 0
BEGIN

    --Mientras haya tablas a verificar
    WHILE @COUNT_TABLES <= @TOTALTEMP_TABLES
    BEGIN

        --Obtengo nombre de la tabla
        SELECT @TABLENAME = TABLE_NAME FROM #TEMPTABLES WHERE ID = @COUNT_TABLES

        /*
        Busco todos los indices de la tabla que tengan 
        mas de un 5% de fragmentacion y que tengan 1000
        o mas en page_count y las inserto en tabla temporal
        */
        SELECT ROW_NUMBER() OVER (Order by avg_fragmentation_in_percent) AS ID, 
        Name, avg_fragmentation_in_percent as Fragmentation
        INTO #TEMPINDEXES 
        FROM sys.dm_db_index_physical_stats (DB_ID(), OBJECT_ID(@TABLENAME), NULL, NULL, NULL) AS a
        JOIN sys.indexes AS b ON a.object_id = b.object_id AND a.index_id = b.index_id
        WHERE name IS NOT NULL
        AND avg_fragmentation_in_percent > 5.00
        AND page_count >= 1000

        SELECT @TOTALTEMP_INDEXES = COUNT(ID) FROM #TEMPINDEXES --Total de indices en tabla

        --Si la tabla tiene indices
        IF @TOTALTEMP_INDEXES > 0
        BEGIN

            SET @COUNT_INDEXES = 1 --Reseteo contador de indices

            --Bucle que crea el query por cada indice de la tabla
            WHILE @COUNT_INDEXES <= @TOTALTEMP_INDEXES
            BEGIN
                SELECT @INDEXNAME = Name FROM #TEMPINDEXES WHERE ID = @COUNT_INDEXES
                SELECT @FRAGMENTATION = Fragmentation FROM #TEMPINDEXES WHERE ID = @COUNT_INDEXES

                /*
                Si la fragmentacion del indice es menor o igual a 30%
                entonces el indice sera reorganizado, de lo contrario
                sera reconstruido
                */
                IF @FRAGMENTATION <= 30.00 BEGIN
                    IF EXISTS (SELECT Name FROM sys.indexes WHERE name = @INDEXNAME AND object_id = OBJECT_ID(@TABLENAME))
                       BEGIN
                            SET @QUERY += 'ALTER INDEX ' +  @INDEXNAME + ' ON ' + @TABLENAME + ' REORGANIZE'  + @NEWLINE
                       END
                END
                ELSE
                BEGIN
                    IF EXISTS (SELECT Name FROM sys.indexes WHERE name = @INDEXNAME AND object_id = OBJECT_ID(@TABLENAME))
                       BEGIN
                            SET @QUERY += 'ALTER INDEX ' +  @INDEXNAME + ' ON ' + @TABLENAME + ' REBUILD' + @NEWLINE
                       END
                END

                SET @COUNT_INDEXES = @COUNT_INDEXES + 1
            END
        END

        DROP TABLE #TEMPINDEXES --Elimino tabla de indices
        SET @COUNT_TABLES = @COUNT_TABLES + 1 --Vuelvo con otra tabla el mismo proceso
    END

    EXEC (@QUERY) --Ejecuto el query creado para defragmentar indices

    DROP TABLE #TEMPTABLES --Elimino tabla de Tablas

    SET @QUERYSHRINK = REPLACE(@QUERYSHRINK,@QUERYSHRINK_DB_ENABLED,'')

    EXEC (@QUERYSHRINK) --Ejecuto el shrink del log por ultima vez pero sin shrink DB
END

GO

Thanks!

Myonara
  • 1,197
  • 1
  • 14
  • 33
Santiago
  • 1,744
  • 15
  • 23

0 Answers0