SQL Server Missing Indexes Report: dm_db_missing_index
SQL Server tracks index recommendations automatically as queries execute, storing them in the missing index DMVs. This T-SQL script queries those views to produce a ranked report of missing indexes, ordered by the estimated improvement they would provide, along with a ready-to-run CREATE INDEX statement for each recommendation.
Purpose and Overview
Every time SQL Server executes a query that would benefit from an index that does not exist, it records the recommendation in sys.dm_db_missing_index_details. Over time these recommendations accumulate and can reveal the highest-impact indexing changes you can make to improve query performance.
The data is reset when SQL Server restarts, so the longer your instance has been running, the more reliable the recommendations become. This script calculates an improvement_measure score β the product of estimated cost savings and usage frequency β to rank recommendations by their real-world impact rather than showing every suggestion with equal weight.
Use this report as a starting point for index tuning. Always review the generated CREATE INDEX statement in context before running it in production.
Code Breakdown
1SELECT TOP 20
2 DB_NAME(mid.database_id) AS database_name,
3 OBJECT_SCHEMA_NAME(mid.object_id,
4 mid.database_id) AS schema_name,
5 OBJECT_NAME(mid.object_id,
6 mid.database_id) AS table_name,
7 mid.equality_columns,
8 mid.inequality_columns,
9 mid.included_columns,
10 migs.user_seeks,
11 migs.user_scans,
12 migs.avg_total_user_cost AS avg_query_cost,
13 migs.avg_user_impact AS avg_pct_benefit,
14 ROUND(
15 migs.avg_total_user_cost
16 * migs.avg_user_impact
17 * (migs.user_seeks + migs.user_scans),
18 2
19 ) AS improvement_measure,
20 'CREATE INDEX [IX_' +
21 OBJECT_NAME(mid.object_id, mid.database_id) +
22 '_' +
23 REPLACE(
24 ISNULL(mid.equality_columns, '') +
25 ISNULL('_' + mid.inequality_columns, ''),
26 ', ', '_'
27 ) +
28 '] ON ' +
29 OBJECT_SCHEMA_NAME(mid.object_id, mid.database_id) +
30 '.' +
31 OBJECT_NAME(mid.object_id, mid.database_id) +
32 ' (' +
33 ISNULL(mid.equality_columns, '') +
34 CASE
35 WHEN mid.inequality_columns IS NOT NULL
36 AND mid.equality_columns IS NOT NULL
37 THEN ', '
38 ELSE ''
39 END +
40 ISNULL(mid.inequality_columns, '') +
41 ')' +
42 ISNULL(
43 ' INCLUDE (' + mid.included_columns + ')',
44 ''
45 ) + ';' AS create_index_statement
46FROM
47 sys.dm_db_missing_index_details AS mid
48 INNER JOIN sys.dm_db_missing_index_groups AS mig
49 ON mid.index_handle = mig.index_handle
50 INNER JOIN sys.dm_db_missing_index_group_stats AS migs
51 ON mig.index_group_handle = migs.group_handle
52WHERE
53 mid.database_id = DB_ID()
54ORDER BY
55 improvement_measure DESC;
The Three Missing Index DMVs
The missing index system uses three related views that must be joined together:
sys.dm_db_missing_index_detailsβ one row per unique missing index recommendation, containing the table and column informationsys.dm_db_missing_index_groupsβ groups related index recommendations together (SQL Server may consolidate similar suggestions)sys.dm_db_missing_index_group_statsβ contains the usage statistics and cost estimates for each group
Calculating the Improvement Measure
The improvement_measure column is a composite score:
1improvement_measure = avg_total_user_cost Γ avg_user_impact Γ (user_seeks + user_scans)
avg_total_user_costβ the average cost of the queries that would benefit from this indexavg_user_impactβ the estimated percentage improvement (0β100) if the index existeduser_seeks + user_scansβ how many times queries needed this index since the last restart
A high score means an expensive query ran many times and would benefit significantly from the index. Low scores may not be worth creating.
Equality vs Inequality Columns
The recommendation splits index columns into two groups:
- Equality columns β columns used in
WHERE col = valuepredicates; these become the leading key columns of the index - Inequality columns β columns used in range predicates (
>,<,BETWEEN,LIKE); these go after equality columns in the key
The CREATE INDEX statement in the output follows this ordering automatically.
Generating the CREATE INDEX Statement
The script builds a CREATE INDEX statement using string concatenation. This is provided as a starting point only β review the name, column order, and INCLUDE columns before running it. Consider whether the new index duplicates an existing one or whether an existing index could be modified instead.
Filtering to the Current Database
WHERE mid.database_id = DB_ID() limits results to the database you are connected to. Remove this filter and replace DB_NAME(mid.database_id) as the first column to see recommendations across all databases on the instance.
Key Benefits and Use Cases
- Ranked recommendations β the
improvement_measurescore guides you to the highest-impact indexes first - Ready-to-run DDL β the generated
CREATE INDEXstatement reduces manual work - Post-deployment review β run after deploying new application code to see what new queries are requesting
- Recurring maintenance β add to a weekly DBA checklist to catch emerging performance issues
- Query tuning context β combine with execution plan analysis to confirm index recommendations match actual query patterns
Performance Considerations
This script reads only in-memory DMVs and has no meaningful overhead. Keep these points in mind when acting on the results:
- Data resets on restart β missing index statistics are lost when SQL Server restarts. On instances that restart frequently, the data may not reflect long-term patterns
- Do not create every recommended index β each index adds overhead to INSERT, UPDATE, and DELETE operations. Evaluate each recommendation against write workload impact
- Check for overlapping indexes β before creating a new index, compare against existing indexes on the same table using
sys.indexesandsys.index_columns. A small modification to an existing index is often better than a new one - Missing index DMVs have limits β SQL Server stores at most 500 missing index recommendations per database. On heavily-queried systems, low-priority recommendations may be pushed out
- Use with execution plans β the DMVs show aggregate recommendations; execution plans show the recommendation for a specific query. Use both together for the best analysis
Practical Tips
- Run during peak hours β the data is most representative when captured under normal production load
- Save a baseline β insert the results into a history table weekly to track whether new missing indexes appear after deployments
- Compare with index usage β join to
sys.dm_db_index_usage_statsto see whether indexes you previously created from these recommendations are actually being used - Review the INCLUDE columns β the
included_columnslist covers columns referenced in the SELECT clause. Including too many columns increases index size; be selective - Consider filtered indexes β if a recommendation targets a specific subset of rows, a filtered index (
WHERE status = 'Active') may be smaller and faster than a full table index
Conclusion
The missing index DMVs give you a built-in, continuously updated list of index recommendations ranked by real workload data. This script surfaces the top 20 highest-impact suggestions for the current database and generates the CREATE INDEX statement for each. Use it regularly as part of performance tuning and after application deployments to keep query performance optimized.
References
- sys.dm_db_missing_index_details (Microsoft Docs) β DMV containing missing index recommendations with table and column details
- sys.dm_db_missing_index_group_stats (Microsoft Docs) β DMV with usage statistics and cost estimates for missing index groups
- MichelleUfford/sql-scripts on GitHub β SQL Server scripts including missing and unused index queries
- ktaranov/sqlserver-kit on GitHub β Community collection of SQL Server DBA and performance scripts