Я знаю, что уже поздно, у меня было больше неотложных проектов, которые сейчас завершены.
Я закончил тем, что написал скрипт, который будет проходить по циклам самых популярных предлагаемых индексов и получать любые кэшированные планы запросов, связанные с ними.
/*********************************************************************************/
/** This script will take the top X missing indexes by advantage and get the **/
/** corresponding query plan(s) that generated it. The script excludes **/
/** the Include columns when finding the query plan. There is an optional **/
/** table parameter that will look at indexes for that table. This should be **/
/** executed against the specific database. **/
/*********************************************************************************/
Declare @table nvarchar(80),
@range int,
@verbose bit
/*******************************/
/* Enter desired values here. */
/*******************************/
Set @range = 15
Set @table = ''
Set @verbose = 1
-- The verbose parameter currently only outputs the XML search string when enabled
-- Begin script guts
Set NoCount On
-- This script goes through the following series of steps:
-- 1) Cache the current cached query plans with missing index warnings.
-- 2) Gets the top X missing indexes by index advantage.
-- 3) Loops through each of the missing indexes
-- a) Builds an XML-formatted string based on the data provided by the missing index
-- b) Gets the query plans that have that XML string in the query plan.
-- 4) Outputs the top missing indexes and the corresponding query plans
--
-- Note: Due to the nature of the query plan cache, some missing indexes may not
-- be associated with a query plan that is currently in the cache.
Declare @topMissingIndexes table (
id int identity,
index_advantage decimal(18,2),
[Database.Schema.Table] nvarchar(100),
equality_columns nvarchar(180),
inequality_columns nvarchar(180),
user_seeks bigint,
avg_total_user_cost decimal(18,2),
avg_user_impact float,
table_name nvarchar(128)
)
Declare @topQueries table (
id int identity,
missing_index_id int,
[object_name] nvarchar(128),
obj_type nvarchar(60),
usecounts int,
query_plan xml
)
Declare @queriesMissingIndex table (
[object_name] nvarchar(128),
obj_type nvarchar(60),
usecounts int,
query_plan xml,
query_plan_text nvarchar(max)
)
Declare @xmlText nvarchar(4000),
@loopCounter int,
@equality nvarchar(180),
@inequality nvarchar(180),
@table_name nvarchar(128),
@column_name nvarchar(80),
@column_id bigint
-- Cache list of query plans with missing indexes, since it takes a while for them to come up if they are requeried completely for each suggested index.
Insert @queriesMissingIndex
Select
Object_Name(objectid),
cp.objtype,
cp.usecounts,
query_plan,
Cast(query_plan AS nvarchar(max))
From sys.dm_exec_cached_plans cp With (NOLOCK)
Cross Apply sys.dm_exec_query_plan(cp.plan_handle) qp
Where Cast(query_plan AS nvarchar(max)) Like N'%MissingIndex%'
And dbid = DB_ID() Option (RECOMPILE);
-- Get the list of suggested indexes we want to work with
If IsNull(@table, N'') = N''
Insert @topMissingIndexes (
index_advantage,
[Database.Schema.Table],
equality_columns,
inequality_columns,
user_seeks,
avg_total_user_cost,
avg_user_impact,
table_name
)
Select Top (@range)
Convert(decimal(18,2), user_seeks * avg_total_user_cost * (avg_user_impact * 0.01)) As index_advantage,
mid.[statement],
mid.equality_columns,
mid.inequality_columns,
migs.user_seeks,
Convert(decimal(18,2), migs.avg_total_user_cost),
migs.avg_user_impact,
Object_Name(mid.[object_id])
From sys.dm_db_missing_index_group_stats migs With (NOLOCK)
Inner Join sys.dm_db_missing_index_groups mig With (NOLOCK) On migs.group_handle = mig.index_group_handle
Inner Join sys.dm_db_missing_index_details mid WITH (NOLOCK) On mig.index_handle = mid.index_handle
Inner Join sys.partitions p With (NOLOCK) On p.[object_id] = mid.[object_id]
Where mid.database_id = DB_ID()
And p.index_id < 2
Order by index_advantage Desc Option (RECOMPILE);
Else
Insert @topMissingIndexes (
index_advantage,
[Database.Schema.Table],
equality_columns,
inequality_columns,
user_seeks,
avg_total_user_cost,
avg_user_impact,
table_name
)
Select Top (@range)
Convert(decimal(18,2), user_seeks * avg_total_user_cost * (avg_user_impact * 0.01)) As index_advantage,
mid.[statement],
mid.equality_columns,
mid.inequality_columns,
migs.user_seeks,
Convert(decimal(18,2), migs.avg_total_user_cost),
migs.avg_user_impact,
Object_Name(mid.[object_id])
From sys.dm_db_missing_index_group_stats migs With (NOLOCK)
Inner Join sys.dm_db_missing_index_groups mig With (NOLOCK) On migs.group_handle = mig.index_group_handle
Inner Join sys.dm_db_missing_index_details mid WITH (NOLOCK) On mig.index_handle = mid.index_handle
Inner Join sys.partitions p With (NOLOCK) On p.[object_id] = mid.[object_id]
Where mid.database_id = DB_ID()
And p.index_id < 2
And mid.[object_id] = Object_ID(@table)
Order by index_advantage Desc Option (RECOMPILE);
Set @loopCounter = 0
While Exists (Select * From @topMissingIndexes Where id > @loopCounter)
Begin
-- To reduce overhead when searching the cached query plan text, we will construct the missing index
-- xml based on the data we have so we won't need wild cards inside the xml search string.
Select
@loopCounter = id,
@table_name = table_name,
@equality = equality_columns,
@inequality = inequality_columns
From @topMissingIndexes
Where id = @loopCounter + 1
Set @xmlText = N'<MissingIndex Database="[' + DB_Name() + N']" Schema="[dbo]" Table="[' + @table_name + N']">'
-- Add the xml for equality columns
If Len(@equality) > 0
Begin
Set @xmlText += N'<ColumnGroup Usage="EQUALITY">'
-- Get rid of the brackets. We will need to add them back in when constructing the xml
Set @equality = Replace(Replace(Replace(@equality, '[', ''), ']', ''), ' ', '')
-- The logic will be different if we have to parse out multiple columns or not
If CharIndex(',', @equality) = 0
Set @column_name = @equality
Else
Set @column_name = Substring(@equality, 1, CharIndex(',', @equality) - 1)
Select @column_id = ColumnProperty(Object_ID(@table_name), @column_name, 'ColumnId')
Set @xmlText += N'<Column Name="[' + @column_name + N']" ColumnId="' + Convert(nvarchar, @column_id) + N'"/>'
While CharIndex(',', @equality) > 0
Begin
Set @equality = Substring(@equality, CharIndex(',', @equality) + 1, Len(@equality))
If CharIndex(',', @equality) = 0
Begin
Set @column_name = @equality
End
Else
Set @column_name = Substring(@equality, 1, CharIndex(',', @equality) - 1)
Select @column_id = ColumnProperty(Object_ID(@table_name), @column_name, 'ColumnId')
Set @xmlText += N'<Column Name="[' + @column_name + N']" ColumnId="' + Convert(nvarchar, @column_id) + N'"/>'
End
Set @xmlText += N'</ColumnGroup>'
End
-- Add the xml for inequality columns
If Len(@inequality) > 0
Begin
Set @xmlText += N'<ColumnGroup Usage="INEQUALITY">'
-- Get rid of the brackets. We will need to add them back in when constructing the xml
Set @inequality = Replace(Replace(Replace(@inequality, '[', ''), ']', ''), ' ', '')
-- The logic will be different if we have to parse out multiple columns or not
If CharIndex(',', @inequality) = 0
Set @column_name = @inequality
Else
Set @column_name = Substring(@inequality, 1, CharIndex(',', @inequality) - 1)
Select @column_id = ColumnProperty(Object_ID(@table_name), @column_name, 'ColumnId')
Set @xmlText += N'<Column Name="[' + @column_name + N']" ColumnId="' + Convert(nvarchar, @column_id) + N'"/>'
While CharIndex(',', @inequality) > 0
Begin
Set @inequality = Substring(@inequality, CharIndex(',', @inequality) + 1, Len(@inequality))
If CharIndex(',', @inequality) = 0
Set @column_name = @inequality
Else
Set @column_name = Substring(@inequality, 1, CharIndex(',', @inequality) - 1)
Select @column_id = ColumnProperty(Object_ID(@table_name), @column_name, 'ColumnId')
Set @xmlText += N'<Column Name="[' + @column_name + N']" ColumnId="' + Convert(nvarchar, @column_id) + N'"/>'
End
Set @xmlText += N'</ColumnGroup>'
End
If @verbose = 1
Begin
Print 'XML Text: '
Print ' ' + @xmlText
End
Set @xmlText = Replace(Replace(@xmlText, '[', '#['), ']', '#]')
Insert @topQueries (
missing_index_id,
[object_name],
obj_type,
usecounts,
query_plan
)
Select
@loopCounter,
[object_name],
obj_type,
usecounts,
query_plan
From @queriesMissingIndex
Where query_plan_text Like N'%' + @xmlText + N'%' Escape '#'
End
Select * From @topMissingIndexes
Order by index_advantage Desc
Select missing_index_id, [object_name], obj_type, usecounts, query_plan From @topQueries
Order by missing_index_id Asc, usecounts Desc
Set NoCount Off
На случай, если кто-то еще захочет связать их вместе.