Best practice for Optimizing large databases

  • I have inherited a 250 gig sql2000 database that has only 3 hours a night downtime. I need to find out how I can possibly cycle thru all the tables and do a indexdefrag or dbreindex on a schedule?  Doing it is not the problem I have the procs but I need some pointers.  Can I just let it rip or will I see excessive locking, tran logs  etc.

    Input from other folks in a similar situation would be appreciated.

    ALso whats your thoughts on dbreindex vs. indexdefrag?

     

  • Try this script from BOL:

    /********************************************************/

    /*Perform a 'USE ' to select the database in which to run the script.*/

    -- Declare variables

    SET NOCOUNT ON

    DECLARE @tablename VARCHAR (128)

    DECLARE @execstr VARCHAR (255)

    DECLARE @objectid INT

    DECLARE @indexid INT

    DECLARE @frag DECIMAL

    DECLARE @maxfrag DECIMAL

    -- Decide on the maximum fragmentation to allow

    SELECT @maxfrag = 30.0

    -- Declare cursor

    DECLARE tables CURSOR FOR

    SELECT TABLE_NAME

    FROM INFORMATION_SCHEMA.TABLES

    WHERE TABLE_TYPE = 'BASE TABLE'

    -- Create the table

    CREATE TABLE #fraglist (

    ObjectName CHAR (255),

    ObjectId INT,

    IndexName CHAR (255),

    IndexId INT,

    Lvl INT,

    CountPages INT,

    CountRows INT,

    MinRecSize INT,

    MaxRecSize INT,

    AvgRecSize INT,

    ForRecCount INT,

    Extents INT,

    ExtentSwitches INT,

    AvgFreeBytes INT,

    AvgPageDensity INT,

    ScanDensity DECIMAL,

    BestCount INT,

    ActualCount INT,

    LogicalFrag DECIMAL,

    ExtentFrag DECIMAL)

    -- Open the cursor

    OPEN tables

    -- Loop through all the tables in the database

    FETCH NEXT

    FROM tables

    INTO @tablename

    WHILE @@FETCH_STATUS = 0

    BEGIN

    -- Do the showcontig of all indexes of the table

    INSERT INTO #fraglist

    EXEC ('DBCC SHOWCONTIG (''' + @tablename + ''')

    WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS')

    FETCH NEXT

    FROM tables

    INTO @tablename

    END

    -- Close and deallocate the cursor

    CLOSE tables

    DEALLOCATE tables

    -- Declare cursor for list of indexes to be defragged

    DECLARE indexes CURSOR FOR

    SELECT ObjectName, ObjectId, IndexId, LogicalFrag

    FROM #fraglist

    WHERE LogicalFrag >= @maxfrag

    AND INDEXPROPERTY (ObjectId, IndexName, 'IndexDepth') > 0

    -- Open the cursor

    OPEN indexes

    -- loop through the indexes

    FETCH NEXT

    FROM indexes

    INTO @tablename, @objectid, @indexid, @frag

    WHILE @@FETCH_STATUS = 0

    BEGIN

    PRINT 'Executing DBCC INDEXDEFRAG (0, ' + RTRIM(@tablename) + ',

    ' + RTRIM(@indexid) + ') - fragmentation currently '

    + RTRIM(CONVERT(varchar(15),@frag)) + '%'

    SELECT @execstr = 'DBCC INDEXDEFRAG (0, ' + RTRIM(@objectid) + ',

    ' + RTRIM(@indexid) + ')'

    EXEC (@execstr)

    FETCH NEXT

    FROM indexes

    INTO @tablename, @objectid, @indexid, @frag

    END

    -- Close and deallocate the cursor

    CLOSE indexes

    DEALLOCATE indexes

    -- Delete the temporary table

    DROP TABLE #fraglist

    GO

    /********************************************************/

    It will defrag anything it finds with a fragmentation greater then 30%

    In the cursor you could always insert a check that verifies what time it is and optionally could exit the loop if it would work for too long. This way you could defragment as many tables you have time for every night (and not Defragment the same one again, next night).

    Happy hunting!

    Hanslindgren

  • Thanks Alot. I was  aware of that stored procedure but I like the thought for testing the time between cursor loops. I will do that?

    Second question.  The dbase is in full recovery. Should I be concerned with transaction log management in respect to disk space?  Should I change the recovery mode to builk logged or simple during the job or run it first and see then decide?

  • In the long run it might be beneficial to become familiar with what hits that database.  There might be tables that are relatively static and don't need to be hit.  There a likely others that need regular attention.  You can always modify the BOL script to produce just the fragmentation output, run that and see where you are at.

    Another question is how firm is that time frame specified.  Do the business processes dictate that or did someone arbitrarily set that amount of time?

     

  • Yes. That could rise a big concern. Since all datamovement (even during defragmentation) is logged and will be kept in the transaction log, something really needs to be done either during or after your defrag process.

    Maybe the easiest way would just to do a full backup after you have defragmented?

    Big DBs and defragmentation is a problem that needs to be thought carefully about...

    I hope I could be of some help.

    //Hanslindgren

  • You also might choose to split up the tables on different periods. Heavily hit tables might be in a script that runs every night. Less used might be scheduled every other night, or even 2-3 nights a week.

  • I agree with splitting up the tables to be defragged on different days on a VLDB.  I've tried the above defrag script on a large database and it runs a very long time and the transaction log was very large as well (Sorry, but I don't recall the exact numbers).  What I did was find the 6 largest tables and defrag them separately on a different day, then on the 7th day I defrag the rest of the tables.  Works for me 🙂

  • I modified the above script quite a bit... I noticed that the above script could not deal with non-dbo table owners, nor could it deal with different collations from Information_schema, nor could it deal with databases that had weird named tables(if you have used SQL Best Practices Analyzer, you know the table names I am talking about).... Also, I wanted to do offline defrags, and I also wanted a way to record what indexes/tables/databases were being defragged each eve..

    My script will offline tables to defrag the individual indexes, and then it writes an entry to another database detailing what databases/tables/indexes were defragged, what their fragmentation and scan densities were at, and finally how long in milliseconds the defrag took to run...

    Currently I have to schedule a job and setup a step for each database w/the script below.. I tried to make one step that recursed through a list of the databases, but I seem to remember the SQL Job system didnt like all the swapping databases I was doing, so I ended up going to individual steps for each database. Wish List: is to make one job step that recurses all databases and reindexes per below... not sure if/when I will get around to looking at it again though.. hehe

    Side Note: If you are working on a database with non server-default collation you will need to modify the column collation in red below to something like: ObjectName nCHAR (255) COLLATE Latin1_General_CI_AS_KS_WS NULL.

    Side Note2: If you go with multiple steps per job for each database, yuo will need to code in the @dbname variable so when you record what transpired in your seperate database, it will be able to code in the correct database name...

    Side Note3: you will prolly need to specify your own database/table for the section that records history...

    Hope this helps, If anyone has any fixes/recommendations, I would love to see/hear about them... I aint the bestest dba, so always looking for ways to improve...

     

    SET NOCOUNT ON

    DECLARE @tablename VARCHAR (128)

    DECLARE @execstr   VARCHAR (255)

    DECLARE @objectid  INT

    DECLARE @indexid   INT

    DECLARE @frag      DECIMAL

    DECLARE @maxfrag   DECIMAL

    declare @maxscan   DECIMAL

    declare @scan    DECIMAL

    declare @dbname    VARCHAR (128)

    declare @strttime  DATETIME

    declare @runtime   decimal

    declare @indexname CHAR (255)

    -- Decide on the maximum fragmentation to allow

    SELECT @dbname = 'Accounting' --Set DBName up here

    SELECT @maxfrag = 10.0 -- max fragmentation

    SELECT @maxscan = 75.0  --maxscan density

    -- Declare cursor

    DECLARE tables CURSOR FOR

       SELECT '['+Table_schema+'].['+Table_name+']'

       FROM INFORMATION_SCHEMA.TABLES

       WHERE TABLE_TYPE = 'BASE TABLE'

    -- Create the table

    CREATE TABLE #fraglist (

       ObjectName CHAR (255),

       ObjectId INT,

       IndexName CHAR (255),

       IndexId INT,

       Lvl INT,

       CountPages INT,

       CountRows INT,

       MinRecSize INT,

       MaxRecSize INT,

       AvgRecSize INT,

       ForRecCount INT,

       Extents INT,

       ExtentSwitches INT,

       AvgFreeBytes INT,

       AvgPageDensity INT,

       ScanDensity DECIMAL,

       BestCount INT,

       ActualCount INT,

       LogicalFrag DECIMAL,

       ExtentFrag DECIMAL)

    -- Open the cursor

    OPEN tables

    -- Loop through all the tables in the database

    FETCH NEXT

       FROM tables

       INTO @tablename

    WHILE @@FETCH_STATUS = 0

    BEGIN

    -- Do the showcontig of all indexes of the table

       INSERT INTO #fraglist

       EXEC ('DBCC SHOWCONTIG (''' + @tablename + ''')

          WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS')

       FETCH NEXT

          FROM tables

          INTO @tablename

    END

    Update #fraglist

     set objectname = (SELECT '['+table_schema+'].['+TABLE_NAME+']'

            FROM INFORMATION_SCHEMA.TABLES

         WHERE TABLE_TYPE = 'BASE TABLE' AND OBJECTNAME = TABLE_NAME)

    -- Close and deallocate the cursor

    CLOSE tables

    DEALLOCATE tables

    -- Declare cursor for list of indexes to be defragged

    DECLARE indexes CURSOR FOR

       SELECT ObjectName, ObjectId, IndexName, LogicalFrag, scandensity

       FROM #fraglist

       WHERE (LogicalFrag >= @maxfrag or scandensity <= @maxscan)

          AND INDEXPROPERTY (ObjectId, IndexName, 'IndexDepth') > 0

    -- Open the cursor

    OPEN indexes

    -- loop through the indexes

    FETCH NEXT

       FROM indexes

       INTO @tablename, @objectid, @indexname, @frag, @scan

    WHILE @@FETCH_STATUS = 0

    BEGIN

       SELECT @execstr = 'DBCC DBREINDEX (''' + RTRIM(@tablename) +''', '''+RTRIM(@indexname)+''', 0)'

       select @strttime = getdate()

       EXEC (@execstr)

       select @runtime = convert(decimal, DATEDIFF(ms, @strttime, getdate()))

       Print @execstr+'indexname: ' + '('+RTRIM(@indexname) + ') '

       INSERT INTO [DB_HISTORY].dbo.NghtlyReindx values (@dbname, RTRIM(@tablename),RTRIM(@indexname), RTRIM(@frag),RTRIM(@scan),@strttime, @runtime)

       FETCH NEXT

          FROM indexes

          INTO @tablename, @objectid, @indexname, @frag, @scan

    END

    -- Close and deallocate the cursor

    CLOSE indexes

    DEALLOCATE indexes

    -- Delete the temporary table

    DROP TABLE #fraglist

    GO

  • If this is 2005 db, there's an online rebuild of index that you can use.  The only down fall is that it uses a lot of tempdb space so be prepared.  You can also partition your DB to multiple drives and possibly split the indexes also into separate drive.  That should make your rebuild of indexes more faster. 

Viewing 9 posts - 1 through 8 (of 8 total)

You must be logged in to reply to this topic. Login to reply