ys.dm_exec_cached_plans的说明中的objtype。Query_plan:使用索引的对象的执行计划,点开后是图形化执行计划。这是语句中最重要的信息之一。Use_counts:对象的执行次数。Plan_handle:在这里它的作用不大。Sql_text:这也是XML片段,它是引用到索引的那部分代码,特别是对动态SQL,我们可以一下子就找到它。
?
?
以上图为例,下面来看看如何使用这些结果。
首先,我们抓重点,看use_counts列,对于那些运行了几个月的系统,这列还是2、3次的,其实没有多大关注必要,除非你要做极限优化。所以我们的切入点是这个列的数据,先挑执行次数最多的来看。注意脚本中已经对use_counts做了排序操作,读者可以按需要修改排序。
找到需要分析的对象之后,点一下最后一列,看看语句情况:注意由于某些存储过程可能多个地方引用或者存储过程本身比较小,所以这个并不是必要步骤,不过看一下大概语句也没坏处,毕竟语句的写法直接影响性能。
如果语句看不出什么问题,再点开执行计划,有些存储过程执行计划的内容很大、很多步骤,所以直接读也不见得是高效。基于本主题,我们希望找到的是索引使用不合理的地方,所以我们还是直接定位索引使用情况。如何定位?查XML。
右键图形化存储过程,选择【显示执行计划XML(X)】,会在新窗口打开执行计划的XML文本。

?
第一次打开XML格式的执行计划时可能会被吓一跳,不过不要紧,我们并不是做深入研究,此时只要用普通的查找文本方法找到索引出现的地方即可。使用CTRL+F快捷键,然后把索引名贴进去就可以收缩到索引所在的地方:

?
如果你还是读不懂XML执行计划,那就返回图形化里面找:

?
把鼠标移到这个图标上即可看到一些我们所需的信息。
按照上面的方式把需要分析的索引分析一遍(分析办法下面会介绍),就可以知道这个索引是否合理,是否可以删除,是否可以合并。
提醒:有些核心表的核心索引可能被几千个对象应用,这些对象主要是动态SQL,只是不同参数而已,在研究参数嗅探时是有价值的,关于这部分另起文章讨论,文章完成后会加上链接。对于使用上面脚本查出来的结果中,若有成百上千行时,一个一个分析显然不合理,此时use_counts又起了一定的作用——找次数足够多的来研究,同时结合sql_text列,找出语句几乎一样仅参数不一样的那些,可以只挑一个研究。一个索引往往就被几个单独的对象应用。如果有大量应用,考虑是否要按业务拆分(所谓的垂直拆分表和业务)。
?
其他信息收集:
下面给出几个收集其他信息的脚本:
缺少索引:
?
--丢失索引
SELECT user_seeks * avg_total_user_cost *( avg_user_impact *0.01 ) AS [index_advantage] ,
dbmigs.last_user_seek ,
dbmid.[statement] AS [Database.Schema.Table],
dbmid.equality_columns ,
dbmid.inequality_columns ,
dbmid.included_columns ,
dbmigs.unique_compiles ,
dbmigs.user_seeks ,
dbmigs.avg_total_user_cost ,
dbmigs.avg_user_impact
FROM sys.dm_db_missing_index_group_stats AS dbmigs WITH ( NOLOCK )
INNER JOIN sys.dm_db_missing_index_groupsAS dbmig WITH ( NOLOCK ) ON dbmigs.group_handle = dbmig.index_group_handle
INNER JOIN sys.dm_db_missing_index_detailsAS dbmid WITH ( NOLOCK ) ON dbmig.index_handle = dbmid.index_handle
WHERE dbmid.[database_id] = DB_ID()
ORDER BY index_advantage DESC;
?
索引碎片:
?
--索引上的碎片超过%并且索引体积较大(超过页)的索引。
SELECT '[' + DB_NAME() + '].[' + OBJECT_SCHEMA_NAME(ddips.[object_id],
DB_ID())+ '].['
+ OBJECT_NAME(ddips.[object_id], DB_ID()) + ']' AS [statement] ,
i.[name] AS [index_name] ,
ddips.[index_type_desc] ,
ddips.[partition_number] ,
ddips.[alloc_unit_type_desc],
ddips.[index_depth] ,
ddips.[index_level] ,
CAST(ddips.[avg_fragmentation_in_percent]AS SMALLINT) AS [avg_frag_%] ,
CAST(ddips.[avg_fragment_size_in_pages]AS SMALLINT) AS [avg_frag_size_in_pages] ,
ddips.[fragment_count] ,
ddips.[page_count]
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'limited') ddips
INNER JOIN sys.[indexes] i ON ddips.[object_id] = i.[object_id]
AND ddips.[index_id] = i.[index_id]
WHERE ddips.[avg_fragmentation_in_percent] > 15
AND ddips.[page_count] > 500
ORDER BY ddips.[avg_fragmentation_in_percent] ,
OBJECT_NAME(ddips.[object_id], DB_ID()) ,
i.[name]
?
注意:检查碎片的前提是表有一定的规模,对于那些小表,即使99%的碎片也不影响什么,还是那句:挑重点。另外本脚本针对碎片率15%的索引做检索,这个比例没有绝对值,但是作为建议,10%~15%以上的碎片率就需要开始重视。
?
?
索引分析:
根据前文所述,我把索引问题主要拆分为三类:索引不合理、索引不足、索引过多。通过上面的信息收集,我们已经得到了足够的信息。
?
索引不合理:
首先我们检查索引定义,如下图:

?
从定义中,我们发现几个问题:
1.索引个数很多:加上聚集索引总共有15个!!!!
2.索引命名:这索引命名足够让人奔溃。不多说。
3.看key_cols列中红圈和黄圈部分,我们一般集中注意力在索引的首列,我们可以看到这里有四个索引是可以列入“可合并”的范畴。对于这个列表,我们需要挑出三类索引:
a)第一种是上图所示首