将两个查询合并为一个与SQL Server中缺少索引有关的查询

时间:2019-03-14 21:20:36

标签: sql-server

我一直在使用https://sqlperformance.com/2018/10/sql-performance/three-easy-sql-server-performance-wins上的一些查询 具体来说,就是缺少索引查询和缺少索引警告查询。

我没有尝试在两个结果集之间来回移动,而是尝试将它们组合成一个查询,因此我可以直接查看sys.dm_exec_query_plan()中缓存的查询计划与sys中缺少的索引相对应.dm_db_missing_index_details。

查询的当前迭代如下:

SELECT CONVERT(decimal(18,2), user_seeks * avg_total_user_cost * (avg_user_impact * 0.01)) AS [index_advantage], 
migs.last_user_seek, mid.[statement] AS [Database.Schema.Table], qps.ProcName, qps.objtype, qps.usecounts,
mid.equality_columns, mid.inequality_columns, mid.included_columns,
migs.unique_compiles, migs.user_seeks, migs.avg_total_user_cost, migs.avg_user_impact,
OBJECT_NAME(mid.[object_id]) AS [Table Name], p.rows AS [Table Rows]
,qps.query_plan
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]
Left Outer Join (
    Select top 50 OBJECT_NAME(qp.objectid) ProcName, cp.objtype, qp.query_plan, cp.usecounts, d.referenced_id
    From sys.dm_exec_cached_plans cp With (NOLOCK)
    Cross Apply sys.dm_exec_query_plan(cp.plan_handle) qp
    Left Outer Join sys.sql_expression_dependencies d With (NOLOCK) on d.referencing_id = qp.objectid
    Where qp.dbid = DB_ID()
        And cast(query_plan as nvarchar(max))  like N'%MissingIndex Database="#[' + db_name() + '#]" Schema="#[dbo#]" Table="#[' + d.referenced_entity_name +N'#]"%' escape '#'
    Order By cp.usecounts desc
      ) qps on cast(qps.query_plan as nvarchar(max))  like N'%MissingIndex%'
        + Case When mid.equality_columns is null then ''
                else 'Column Name="' + Replace(Replace(Replace(mid.equality_columns, ', ', 'Column Name="'), '[', '#['), ']', '#]%') end
        + Case When mid.inequality_columns is null then ''
                else 'Column Name="' + Replace(Replace(Replace(mid.inequality_columns, ', ', 'Column Name="'), '[', '#['), ']', '#]%') end
        + Case When mid.included_columns is null then ''
                else 'Column Name="' + Replace(Replace(Replace(mid.included_columns, ', ', 'Column Name="'), '[', '#['), ']', '#]%') end
      escape '#'
        And mid.object_id = qps.referenced_id
WHERE mid.database_id = DB_ID()
AND p.index_id < 2 
ORDER BY index_advantage DESC OPTION (RECOMPILE);

我的第一次尝试使用外部应用而不是左联接,但是在生产数据库上执行时间很长(45分钟以上),因此我尝试了左联接。我不确定要花多长时间,但是我在15分钟后停止执行。

是否有可能从这两个中进行这样的查询?

1 个答案:

答案 0 :(得分:0)

我知道这有点晚了,我还有很多紧迫的项目需要完成,而现在这些项目已经完成。

我最终编写了一个脚本,该脚本将遍历建议的最高索引并获取与它们相关联的所有缓存的查询计划。

/*********************************************************************************/
/**   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 

以防其他任何人想要将这两个捆绑在一起。