sp_helpindexUsage

The suite's diagnostic companion. While SQL Server's built-in sp_helpindex shows you what indexes exist, sp_helpindexUsage shows you how they're actually being used — combining index structure (keys, includes, filters, tree/leaf columns) with live usage statistics (seeks, scans, lookups, writes) and size data, all in one result set.

This is the proc you run before acting on any suite recommendation. Every tool's output includes a commented --EXEC sp_helpindexUsage 'schema.table' line so you can see the full picture for any table before creating, dropping, or modifying an index.

Why sp_helpindex Isn't Enough

SQL Server's built-in sp_helpindex tells you what indexes exist and what columns they contain. That's it. For a DBA making index decisions, the critical questions it can't answer are:

Questionsp_helpindexsp_helpindexUsage
Is this index actually being used?—Seeks, scans, lookups since restart
How much write overhead does it cause?—User writes count
Is it worth the storage?—Size in KB
Which columns are keys vs includes?Partial (mixed)Separate key and include columns
Which columns are at tree vs leaf level?—Tree and leaf column breakdown
Is the index filtered?—Filter definition
Is the index disabled?—Disabled flag
When was it last used?—Last seek/scan/lookup/update timestamps
Read vs write ratio?—ReadWriteDiff (negative = write-heavy)

Dependency Chain

Three procedures work together, each building on the previous. All are installed in master and marked as system objects via sp_MS_marksystemobject so they execute in the caller's database context — just like sp_help and sp_helpindex.

sp_helpindexUsage
  |
  +--> sp_helpindex2                      Index structure: keys, includes, filters, tree/leaf
  |      |
  |      +--> sp_ExposeColsInIndexLevels  Tree vs leaf column analysis for NC indexes
  |
  +--> sys.sp_MSindexspace                Built-in: index sizes (always available)
  |
  +--> sys.dm_db_index_usage_stats        DMV: seeks, scans, lookups, writes
  |
  +--> DBA_Utilities.dbo.IndexUsageTemp   Only for @Output = 2 (batch collection)

sp_ExposeColsInIndexLevels

The foundation layer. Analyzes each nonclustered index to determine which columns are stored at the tree level (non-leaf pages used for navigation) versus the leaf level (where the actual data pointers live). This distinction matters for understanding index coverage — a column in the tree level can satisfy seeks, while a column only at the leaf level can avoid key lookups but won't help with seeks.

sp_helpindex2

Kimberly Tripp's enhanced replacement for sp_helpindex. Adds critical information the built-in version omits: INCLUDE columns listed separately from key columns, filter definitions for filtered indexes, disabled status, and the tree/leaf column breakdown from sp_ExposeColsInIndexLevels.

sp_helpindexUsage

The top-level wrapper that brings it all together. Takes the structural data from sp_helpindex2, adds index size from sys.sp_MSindexspace, and joins usage statistics from sys.dm_db_index_usage_stats. The result is a single comprehensive view of every index on a table.

Output Columns

ColumnDescription
index_idSQL Server index ID (1 = clustered, 2+ = nonclustered)
IndexNameIndex name
index_keysKey columns with sort direction (ASC/DESC)
included_columnsINCLUDE columns (separated from keys)
SizeKBIndex size in KB
UserSeeksIndex seek operations since last restart
UserScansFull index scan operations
UserLookupsKey lookup operations (clustered index only)
UserReadsTotal reads (seeks + scans + lookups)
UserWritesWrite operations (every INSERT/UPDATE/DELETE touches the index)
ReadWriteDiffUserReads − UserWrites. Negative = write-heavy (candidate for removal)
Fill_FactorIndex fill factor setting
filter_definitionWHERE clause for filtered indexes (NULL for standard indexes)
LastUserSeekTimestamp of last seek operation
LastUserScanTimestamp of last scan operation
LastUserLookupTimestamp of last lookup operation
LastUserUpdateTimestamp of last write operation
is_disabledWhether the index is currently disabled
index_descriptionType + properties (clustered, unique, primary key, nonclustered, etc.)
columns_in_treeColumns stored at non-leaf (tree) level for navigation
columns_in_leafColumns stored at leaf level (data + pointers)

Interpreting the Results

The usage stats tell you how SQL Server actually uses each index. Here's how to read the key patterns:

Healthy index

High seeks, low scans, moderate writes: The optimizer is using this index for targeted lookups. This is exactly what a nonclustered index is for.

Unused index (removal candidate)

Zero seeks + zero scans + zero lookups, but active writes: Every INSERT, UPDATE, and DELETE on the table has to maintain this index, but no query ever benefits from it. The Unused Index Detection tool flags these automatically.

Scan-heavy index (possible key order issue)

High scans, low seeks: The optimizer is scanning the entire index rather than seeking to specific rows. This often means the leading key column doesn't match the query's WHERE clause, or the index is being used as a "narrow table scan" (cheaper than scanning the clustered index when fewer columns are needed).

Write-heavy index (negative ReadWriteDiff)

ReadWriteDiff is negative: This index costs more to maintain than it saves in reads. Not necessarily bad (a unique constraint must exist regardless of read count), but worth investigating if it's not a unique or primary key.

Key lookup magnet

High lookups on the clustered index: Nonclustered index seeks are frequently followed by key lookups back to the clustered index for missing columns. The Key Lookup Elimination tool identifies which columns to add as INCLUDEs to eliminate these.

Output Modes

ModeValueDescription
Default@Output = 0Full detail for one table in the current database: structure, usage stats, sizes, tree/leaf columns
Cross-database@Output = 1Usage stats for the same table across all user databases on the server. Useful for multi-tenant environments where the same schema exists in every database
Batch insert@Output = 2Same as mode 0 but inserts into DBA_Utilities.dbo.IndexUsageTemp instead of returning a result set. Used for automated collection across databases

Parameters

ParameterDefaultDescription
@TableName(required)Table name. Can include schema prefix ('dbo.MyTable')
@IndexName''Filter to a specific index name. Empty string = all indexes on the table
@Output0Output mode: 0 = full detail, 1 = cross-database, 2 = batch insert to staging table

IndexUsageTemp Staging Table

The DBA_Utilities.dbo.IndexUsageTemp table is used only with @Output = 2 for batch collection scenarios. It mirrors the output columns of mode 0, with an additional DatabaseName column and an InsertDate timestamp. This is useful when:

The table is optional — it only needs to exist if you use @Output = 2.

Installation

All three procs must be installed in master and marked as system objects. This is what allows them to be called from any database context without a three-part name — the same way sp_help and sp_helpindex work.

Install order matters — each proc depends on the previous one:

  1. sp_ExposeColsInIndexLevels — no dependencies
  2. sp_helpindex2 — depends on #1
  3. sp_helpindexUsage — depends on #2
-- Install in master (run in order)
USE master;
GO

-- 1. Foundation: tree/leaf column analysis
:r sp_exposecolsinindexlevels.sql
EXEC sp_MS_marksystemobject 'sp_ExposeColsInIndexLevels';

-- 2. Enhanced sp_helpindex
:r sp_helpindex2.sql
EXEC sp_MS_marksystemobject 'sp_helpindex2';

-- 3. Usage stats wrapper
:r sp_helpindexUsage.sql
EXEC sp_MS_marksystemobject 'sp_helpindexUsage';

-- 4. Optional: staging table for batch mode
USE DBA_Utilities;
:r IndexUsageTemp.sql

Integration with the Suite

Every recommendation tool in the suite includes sp_helpindexUsage references in its output. The standard workflow is:

  1. Run the suite — get recommendations
  2. Review each table — run the commented sp_helpindexUsage line to see the full index picture
  3. Make an informed decision — the recommendation plus the full index context tells you whether to proceed

For example, the suite might recommend adding [status] as an INCLUDE to an existing index. Running sp_helpindexUsage on that table might reveal that another index already has [status] as a key column — meaning the recommendation might be unnecessary, or the two indexes should be consolidated instead.

When @RunnableUnder is set: The suite comments out the sp_helpindexUsage calls so the output script is executable without stopping. When @RunnableUnder is NULL, the calls are active — you can run each one inline as you review the script.

Usage Examples

-- Full index detail for a table (run from target database)
USE MyDatabase;
EXEC sp_helpindexUsage 'dbo.orders';

-- Filter to a specific index
EXEC sp_helpindexUsage 'dbo.orders', @IndexName = 'IX_orders_customer_id';

-- Cross-database: see how the same table is indexed across all databases
-- Useful in multi-tenant environments
EXEC sp_helpindexUsage 'dbo.orders', @Output = 1;

-- Batch collect into staging table for offline analysis
EXEC sp_helpindexUsage 'dbo.orders', @Output = 2;
SELECT * FROM DBA_Utilities.dbo.IndexUsageTemp ORDER BY InsertDate DESC;

-- Quick check: find indexes with zero reads and high writes (removal candidates)
EXEC sp_helpindexUsage 'dbo.orders';
-- Look for rows where UserReads = 0 and UserWrites > 1000

-- Quick check: find indexes with high scans (possible key order issue)
EXEC sp_helpindexUsage 'dbo.orders';
-- Look for rows where UserScans >> UserSeeks

Attribution

sp_helpindex2 and sp_ExposeColsInIndexLevels are by Kimberly L. Tripp and Paul S. Randal of SQLskills.com. sp_helpindexUsage is a custom wrapper built on top of their work, adding DMV usage stats and size data.