SQLServerCentral Article

Analyzing Data Storage - Ten SHOWCONTIG Queries

,

DBCC SHOWCONTIG allows you to report on fragmentation information for the data and indexes of a specified table.

If neither an index nor table is specified, DBCC SHOWCONTIG will report on all tables within the database. You can capture this output by adding "WITH TABLERESULTS" to the DBCC SHOWCONTIG call. SHOWCONTIG allows you to grab all sorts of useful information, so that you can report or act upon it with your Transact-SQL code.

In this article, we will be reviewing how you can use Transact-SQL queries to probe your database's potential trouble areas...

Prerequisites: The basics of SHOWCONTIG are not reviewed here, and if you're in need of this background, check out SQL Server Books Online's topic "DBCC SHOWCONTIG".


Query #1

List all the tables that don't have clustered indexes (excluding system tables).

Prior to running this first query, we first need to create the #SHOWCONTIG temporary table. This table will be used to hold the results of the DBCC SHOWCONTIG result set.

CREATE TABLE #SHOWCONTIG (
   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)

Next, populate the #SHOWCONTIG table with all heaps and clustered indexes within the database you wish to analyze. (If you wish to report on all indexes within the database, add the ALL_INDEXES keyword to your DBCC SHOWCONTIG execution).

INSERT #ShowContig
EXEC ('DBCC SHOWCONTIG WITH TABLERESULTS')

Now we are ready to list all the tables that don't have clustered indexes (excluding system tables). Within the #ShowContig table, if a specific row has an index name, this means the row is for a clustered index. If the index name is blank, the row represents a heap (no clustered index).

SELECT ObjectName, 
ObjectID 
FROM #ShowContig
WHERE LEN(IndexName)=0 AND
ObjectName NOT LIKE 'dt%' AND
 ObjectName NOT LIKE 'sys%'
ORDER BY ObjectName

For tables that appear in this result set, consider adding a clustered index. Some DBAs argue that it isn't necessary for all tables; however I think that clustered indexes should be added by default - and only removed if you have a very good reason to do so.


Query #2

Show the top ten tables with the most data pages (does not include non-clustered index pages).

SELECT  TOP 10
ObjectName, 
IndexName,
countpages
FROM #ShowContig
WHERE ObjectName NOT LIKE 'dt%' AND
 ObjectName NOT LIKE 'sys%'
ORDER BY  countpages DESC

This query is mostly for informational purposes. You may be surprised by the results.


Query #3

Show the top ten tables with the highest row counts.

SELECT  TOP 10
ObjectName, 
IndexName,
CountRows
FROM #ShowContig
WHERE ObjectName NOT LIKE 'dt%' AND
 ObjectName NOT LIKE 'sys%'
ORDER BY  CountRows DESC

Once again, this query is mostly for informational purposes. If the row counts surprise you - investigate.


Query #4

List the top ten tables with the largest average record size.

SELECT  TOP 10
ObjectName, 
IndexName,
AvgRecSize
FROM #ShowContig
WHERE ObjectName NOT LIKE 'dt%' AND
 ObjectName NOT LIKE 'sys%'
ORDER BY  AvgRecSize DESC

The smaller the average record size, the more rows you can fit on a single page. The more you can fit on a page (assuming a high index fill factor), the fewer pages you have to read in order to fulfill the query requirements. In other words, smaller average record sizes mean less I/O.

If the average is high, see if all the table columns are being used, and that the column data types are appropriate.


Query #5

List the top ten tables with the largest record sizes.

SELECT  TOP 10
ObjectName, 
IndexName,
MaxRecSize
FROM #ShowContig
WHERE ObjectName NOT LIKE 'dt%' AND
 ObjectName NOT LIKE 'sys%'
ORDER BY  MaxRecSize DESC

Like the average record size, this query can help you determine which tables have at least one large row. Keep in mind that all it takes is ONE large row within a table to get on the top 10 of this list - so consider this in the context of the average row width. If you see tables falling into both lists (average row width and largest record size), see if all the table columns are being used, and that the column data types are appropriate.


Query #6

Show the top ten tables with the highest average bytes free per page.

SELECT  TOP 10
ObjectName, 
IndexName,
AvgFreeBytes
FROM #ShowContig
WHERE ObjectName NOT LIKE 'dt%' AND
 ObjectName NOT LIKE 'sys%'
ORDER BY  AvgFreeBytes DESC

Generally, the average bytes free value should be low, so investigate high values (for larger tables). "Low" and "high" are relative terms to your database , but be sure to check your index fill-factor. Is your index fill-factor too low? If you're not performing that many insert/update operations, consider increasing the fill-factor and rebuilding your indexes - thus decreasing I/O.


Query #7

Show the top ten tables with the LOWEST average page density.

SELECT  TOP 10
ObjectName, 
IndexName,
AvgPageDensity
FROM #ShowContig
WHERE ObjectName NOT LIKE 'dt%' AND
 ObjectName NOT LIKE 'sys%'
ORDER BY  AvgPageDensity ASC

Generally, average page density should be high. Investigate low densities (for larger tables) closely. If you determine that fragmentation is an issue, recreating/rebuilding the clustered index will reorganize the data, resulting in full data pages (depending on the index fill-factor). If rebuilding the indexes or using DBCC DBREINDEX is not possible (the index is offline during the drop/re-create cycle ), consider the less effective DBCC INDEXDEFRAG.


Query #8

List the top ten tables with the highest amount of logical fragmentation.

SELECT  TOP 10
ObjectName, 
IndexName,
logicalfrag
FROM #ShowContig
WHERE ObjectName NOT LIKE 'dt%' AND
 ObjectName NOT LIKE 'sys%'
ORDER BY  logicalfrag DESC

Investigate larger tables that appear on this list. Like the previous query, if you determine that fragmentation is an issue, adjust the fill-factor if necessary, recreate/rebuild the clustered index or execute the less effective DBCC INDEXDEFRAG.


Query #9

List the top ten tables with the highest extent fragmentation.

SELECT  TOP 10
ObjectName, 
IndexName,
ExtentFrag
FROM #ShowContig
WHERE ObjectName NOT LIKE 'dt%' AND
 ObjectName NOT LIKE 'sys%'
ORDER BY   ExtentFrag DESC

Extent fragmentation should be as low as possible. Investigate larger tables that appear on this list. Like the previous query, if you determine that fragmentation is an issue, recreate/rebuild the clustered index or execute the less effective DBCC INDEXDEFRAG.


Query #10

List the top ten tables with the lowest scan density.

SELECT  TOP 10
ObjectName, 
IndexName,
 ScanDensity 
FROM #ShowContig
WHERE ObjectName NOT LIKE 'dt%' AND
 ObjectName NOT LIKE 'sys%'
ORDER BY    ScanDensity ASC

Scan density should be as high as possible. If low, consider recreating/rebuilding the clustered index or executing the less effective DBCC INDEXDEFRAG.


Conclusion

After examining the output from #ShowContig, don't forget to issue a "DROP TABLE #ShowContig" as a final step.

DROP TABLE #SHOWCONTIG

Use these ten queries to get to know your database better. You may be surprised by what you find. For example: gigantic tables that you were not aware of, tables without clustered indexes, tables with wide row lengths, and tables with significant fragmentation.

Run these queries periodically to help manage both the size of your database and the performance of your queries.

Rate

4.64 (11)

You rated this post out of 5. Change rating

Share

Share

Rate

4.64 (11)

You rated this post out of 5. Change rating