How to Find Size of All the Indexes on the Database – Interview Question of the Week #097

Question: How can I list the space used by each index in a database?

Ledgers of different thickness wait to be measured one at a time on a balance scale

A client asked me for this during a SQL Server performance tuning session. I like questions like that because they show what the team is trying to understand. But the size of an index is not a verdict on whether it’s useful. First measure it; then look at workload and maintenance cost.

Answer: Use sys.dm_db_partition_stats. It counts the used and reserved pages for every partition of every index. Joining sys.allocation_units on partition_id alone is a common mistake, because in-row data uses the partition’s HoBT ID and that join can miss space. Partition statistics avoid the problem:

SELECT SCHEMA_NAME(t.schema_id) AS SchemaName,
       t.name AS TableName,
       i.name AS IndexName,
       i.index_id AS IndexID,
       SUM(ps.used_page_count) * 8 AS UsedKB,
       SUM(ps.reserved_page_count) * 8 AS ReservedKB
FROM sys.dm_db_partition_stats AS ps
JOIN sys.indexes AS i
  ON i.object_id = ps.object_id
 AND i.index_id = ps.index_id
JOIN sys.tables AS t
  ON t.object_id = ps.object_id
WHERE i.index_id > 0
  AND i.is_hypothetical = 0
GROUP BY t.schema_id, t.name, i.name, i.index_id
ORDER BY SchemaName, TableName, IndexID;
First nine index rows from the query, showing complete index names, used KB and reserved KB in AdventureWorks2025
The first nine index rows in AdventureWorks2025, with used and reserved KB.

Run it in the database you want to inspect. SQL Server 2022 and later require VIEW DATABASE PERFORMANCE STATE and VIEW SECURITY DEFINITION for this DMV; older versions require VIEW DATABASE STATE and VIEW DEFINITION. One row is returned per nonhypothetical index on a user table. Heaps and indexed views are outside this query’s scope.

The DMV has one row per partition, so SUM gives an index total even when it’s partitioned. Each SQL Server page is 8 KB. UsedKB includes in-row, large-object and row-overflow used pages; ReservedKB includes allocated pages that may not yet be used. The clustered index row includes the base table’s clustered storage, so don’t add it to a separate table-size figure as though it were an independent copy of every row.

Don’t read this list as a deletion list. A large index that prevents an expensive scan may earn its space, while a smaller index may add write cost without helping the workload. Size answers the first question, not the final decision.

As I tell clients in an index review, size is a starting observation. Before removing anything, examine query plans, reads, writes, uniqueness, and whether the index supports a constraint. If you want a guided review of your indexes, see my SQL Server performance tuning work.

Index size: Measure, then decide

Index size is not a verdict, it is the first measurement, and the workload decides what stays.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

SQL Index, SQL Scripts, SQL Server
Previous Post
How Many Foreign Key Can You Have on A Single Table? – Interview Question of the Week #096
Next Post
How to Find Longest Running Query With Execution Plan – Interview Question of the Week #098

Related Posts

10 Comments. Leave new

  • Wilfred van Dijk
    November 13, 2016 3:22 pm

    Although deprecated, the famous [sysindexes] contains both [dpages] and [rowcount] value which makes the query much easier

    Reply
  • I believe there is something wrong. If I go to Index Properties -> Fragmentation -> General -> Pages. And multiply the total pages by 8, that number doesn’t match with the Indexsize(KB) of this query

    Reply
  • Hans Prestat
    May 22, 2018 6:20 am

    First, @roncansan, you used the term query instead of index which is not correct.
    Second, @Pinal Dave, I think @roncansan is right about there is a something wrong.
    I run your script on my database it says that the PK of one of my table related to attachments is about 41 GB.
    I checked the table properties and under storage I noticed the Index space is 0.055 MB and the Data space is 40 387 523 MB. There is no included columns in the index however it appears that the fragmentation is really huge (>30%) and take more than 17min to rebuild…any update for this script?

    Reply
    • Is the PK also a clustered index? If so, then that is why it shows the index as being so large. Clustered indexes are in actuality all of the data in the table. Otherwise, without a clustered index, you would just have a heap. This script needs to exclude clustered indexes if you are wanting to see just the other types of indexes such as non-clustered.

      Reply
  • Hans Prestat
    July 27, 2018 4:19 am

    @AMax : My PK is a default clustered index created by SQL Server. So you mean that because each row have one PK column, the size of the PK should include the size of other columns? I can understand the relation but it doesn’t make sens for me to say the index is the same size than his relative table…but well…I will do as you mentionned and will exclude the clustered indexes. Thank you!

    Reply
    • You will also need to exclude the heaps themselves. Below is the query modified to exclude heaps and clustered indexes.

      SELECT
      OBJECT_SCHEMA_NAME(i.OBJECT_ID) AS SchemaName,
      OBJECT_NAME(i.OBJECT_ID) AS TableName,
      i.name AS IndexName,
      i.index_id AS IndexID,
      8 * SUM(a.used_pages) AS ‘Indexsize(KB)’
      FROM sys.indexes AS i
      JOIN sys.partitions AS p ON p.OBJECT_ID = i.OBJECT_ID AND p.index_id = i.index_id
      JOIN sys.allocation_units AS a ON a.container_id = p.partition_id
      WHERE i.index_id NOT IN (0,1) –exclude heaps and clustered indexes
      GROUP BY i.OBJECT_ID,i.index_id,i.name
      ORDER BY 5 DESC

      Reply
  • I have 1 million records in my table. how to create quickly nonclustered index…?If Any one know let me know

    Reply
  • Rebecca Harter
    October 5, 2020 10:07 pm

    I ran into the heap issue as well. It does show NULL as the IndexName but that wasn’t before I told the developer they had a 368 GB index on their “backup” table (my fault to not double check). Maybe you should update to exclude “NULL” in the IndexName or put a *warning* message the blog post to watch out for this. Thanks for all you do for the community Pinal!

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.