SQL Server Indexing Strategies, Heap Analysis, and Diagnostic Scripts

Identifying Table Structures

Understanding the distinction between heap tables and those with clustered indexes is fundamental for performance tuning. This can be achieved by inspecting the index_id property within system partition views.

-- Retrieve table structure type based on index identifier
SELECT 
    t.name AS TableName,
    i.type_desc AS IndexTypeDesc,
    CASE 
        WHEN i.index_id = 0 THEN 'Heap Table'
        WHEN i.index_id = 1 THEN 'Clustered Index Table'
    END AS StructureClassification
FROM sys.partitions p
JOIN sys.tables t ON p.object_id = t.object_id
JOIN sys.indexes i ON p.object_id = i.object_id AND p.partition_id = i.partition_id
WHERE p.index_id < 2;

Alternatively, query the index catalog directly where heap names are typically null:

SELECT 
    OBJECT_NAME(i.object_id) AS TableName,
    i.name AS IndexName,
    i.type_desc AS Classification
FROM sys.indexes i
WHERE i.index_id <= 1;

Comprehensive Index Inventory

Generating a report of all database indexes, including column composition and uniqueness attributes, assists in schema review and missing index analysis.

SELECT 
    T.schema_name + '.' + T.object_name AS FullyQualifiedObjectName,
    I.index_name AS IndexDisplayName,
    STUFF((
        SELECT ',' + C.column_name
        FROM sys.index_columns IC
        JOIN sys.columns C ON IC.object_id = C.object_id AND IC.column_id = C.column_id
        WHERE IC.index_id = I.index_id AND IC.object_id = I.object_id
        ORDER BY IC.key_ordinal
        FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS IncludedColumns,
    CASE I.type 
        WHEN 1 THEN 'Clustered' 
        WHEN 2 THEN 'Non-Clustered' 
        WHEN 3 THEN 'XML' 
        WHEN 4 THEN 'Spatial' 
        ELSE 'Other' 
    END AS IndexCategory,
    CASE I.is_unique 
        WHEN 1 THEN 'Unique' 
        ELSE 'Non-Unique' 
    END AS UniquenessStatus
FROM sys.objects O
JOIN sys.schemas S ON O.schema_id = S.schema_id
JOIN sys.indexes I ON O.object_id = I.object_id
CROSS APPLY (
    SELECT SCHEMA_NAME(O.schema_id) AS schema_name,
           O.name AS object_name
) T
WHERE O.type = 'U'
  AND O.is_ms_shipped = 0
ORDER BY ObjectFullName DESC;

Statistics and Maintenance Monitoring

Tracking when statistics were last updated helps identify potential query plan issues caused by stale cardinality estimates.

SELECT 
    OBJECT_NAME(SI.object_id) AS TargetTable,
    SI.name AS StatisticDefinition,
    STATS_DATE(SI.object_id, SI.index_id) AS LastRefreshTimestamp,
    SSI.rowmodctr AS RowModificationCount,
    SP.rows AS EstimatedTotalRows,
    'EXEC sp_updatestatistics [' + SCHEMA_NAME(SO.schema_id) + '].[' + OBJECT_NAME(SI.object_id) + '] ;' AS RemediationQuery
FROM sys.stats SI
JOIN sys.objects SO ON SI.object_id = SO.object_id
JOIN sys.sysindexes SSI ON SI.object_id = SSI.id AND SI.index_id = SSI.indid
JOIN sys.partitions SP ON SI.object_id = SP.object_id
WHERE SSI.rowmodctr > 0
  AND STATS_DATE(SI.object_id, SI.index_id) IS NOT NULL
  AND SO.type = 'U'
ORDER BY RowModCTR DESC;

Storage Space Utilization Analysis

Analyzing physical disk consumption provides insight into growth trends and potential cleanup opportunities.

SELECT 
    T.SchemaName + '.' + T.TableName AS ObjectIdentifier,
    SUM(T.TotalSpaceKB) / 1024.0 AS TotalSizeInMB,
    SUM(T.UsedSpaceKB) / 1024.0 AS UsedSizeInMB,
    SUM(T.UnusedSpaceKB) / 1024.0 AS FreeSpaceInMB
FROM (
    SELECT 
        sch.name AS SchemaName,
        tab.name AS TableName,
        SUM(au.total_pages) * 8 AS TotalSpaceKB,
        SUM(au.used_pages) * 8 AS UsedSpaceKB,
        (SUM(au.total_pages) - SUM(au.used_pages)) * 8 AS UnusedSpaceKB
    FROM sys.tables tab
    INNER JOIN sys.schemas sch ON tab.schema_id = sch.schema_id
    INNER JOIN sys.indexes idx ON tab.object_id = idx.object_id
    INNER JOIN sys.partitions par ON idx.object_id = par.object_id AND idx.index_id = par.index_id
    INNER JOIN sys.allocation_units au ON par.partition_id = au.container_id
    GROUP BY sch.name, tab.name
) AS T
GROUP BY T.SchemaName, T.TableName
ORDER BY TotalSizeInMB DESC;

Concurrency Control Configuration

High concurrency scenarios often lead to locking issues or deadlocks. Enabling Snapshot Isolation at the database level mitigates read-write conflicts by allowing readers to access row versions instead of exclusive locks.

To apply these settings, switch to single-user mode temporarily, adjust configuration, and return to multi-user access:

BEGIN TRANSACTION;

ALTER DATABASE [TargetDatabase] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
ALTER DATABASE [TargetDatabase] SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE [TargetDatabase] SET READ_COMMITTED_SNAPSHOT ON;
ALTER DATABASE [TargetDatabase] SET MULTI_USER;

COMMIT TRANSACTION;

Enabling these options reduces blocking during read operations but requires consideration regarding tempdb usage and version store growth.

Tags: SQL Server Index Optimization Heap Tables Deadlock Prevention Performance Tuning

Posted on Tue, 03 Nov 2026 16:17:38 +0000 by Orpheus13