甚至数月,除了让缓存能充分表现系统行为之外,也可以加大覆盖系统行为的可能性,因为某些功能确实只在特定时间(如月结及其报表)才会发生,或者在异常时才会触发,如果系统运行了几个小时就开始收集信息,那么信息的准确度可能不足以支撑系统分析。
网上有类似的文章,但是我觉得个人的方法也不错,所以这里我不打算根据网上的方法来介绍,而是介绍本人自己的方法,如有不妥或者漏洞,欢迎指出和分享你们的方法。
?
实操:
对于索引问题,我要思考的是:现在的索引是否合理?如果合理,那么性能问题可能是别的地方,当然,写这篇文章证明是不合理的,那么如何发现和定义呢?需要监控和分析。由于本人负责的系统是SQL 2008 R2,虽然已经支持扩展事件(Extent Events,xEvents),但是由于从SQL 2012开始才有图形化界面,而且2008听说还存在一定的bug,所以在这里并没有使用,个人还是挺看好这个功能,后续我会尝试使用,也欢迎大家分享。
既然xEvents不可用,那么还是来点传统方式吧——计划缓存(Plan cache)和DMO(DMVs 和DMFs,动态管理对象)。需要注意的是计划缓存存储的是预估执行计划,有些程序的实际行为是不同的。所以预估执行计划只能作为入门。
在确定工具之后,接下来就要思考如何使用。前面提到的指标中,除了“索引被使用的具体情况”之外,其他都能用各种DMO获取。但是基于连贯性原因,我边描述操作边简要介绍各种DMO。
通常来说,一个系统有大量的对象(存储过程、动态SQL、函数、视图等),除非问题非常特殊,一眼就能定位,否则我会按照下面原则来检查:
1、 从SSMS中的报表获取LongRunning 。
2、 用语句获取LongRunning对象。(1、2两个我将单独起文介绍)
3、 通过与开发人员的沟通获取可能的性能瓶颈。
4、 对大表和索引很多的表进行优先分析。
5、 当然还有其他,不过这些多多少少跟运气有关,说不定误打正着碰对了瓶颈。
在本次Troubleshooting中,我按上面顺序进行操作,最后发现第四个原则的效果明显,所以我重点讨论第四个原则。
?
查找索引定义:
在这次维护索引中,我选择了对大表进行优先分析,当然对于很多系统来说,这些表一点都不大,不过别在意细节。首先我从最大的表开始,逐个分析每个表的索引。找表的行数太容易了,这里就不说了。当我找到最大表时,我们可以很轻易地从SSMS中找到表上有多少索引,然后呢?
在SQL 2000时代,很多sp_xxxx系统存储过程都能获取一定的信息,比如sp_helpindex 表名这种方式可以获取表上索引的定义,但是这个系统存储过程并不支持SQL 2005及后续版本出现的新功能,如包含索引的描述,所以你只能看到索引名、定义在INCLUDE关键字前的那些列(假设它们是包含索引),对于包含索引中的包含列,却没有显示。这种方式有一个风险,很多人通过这个存储过程看到某些索引的前面几列完全相同,就直接删除其中重复索引,其实这些索引是包含索引,对某些程序的支持有作用,这种鲁莽行为可能导致系统性能突然猛降,所以要“大胆假设、小心求证!!!”。
我们可以使用DMO来实现这种需要,注意替换表名:
?
DECLARE @tblnvarchar(265)
SELECT @tbl = '表名'
SELECT o.name,i.index_id, i.name, i.type_desc,
substring(ikey.cols, 3, len(ikey.cols))AS key_cols,
substring(inc.cols, 3, len(inc.cols)) ASincluded_cols,
stats_date(o.object_id, i.index_id) ASstats_date,
i.filter_definition
FROM sys.objects o
JOIN sys.indexes i ON i.object_id = o.object_id
CROSS APPLY (SELECT ', ' + c.name +
CASE ic.is_descending_key
WHEN 1 THEN ' DESC'
ELSE ''
END
FROM sys.index_columns ic
JOIN sys.columns c ON ic.object_id = c.object_id
ANDic.column_id = c.column_id
WHERE ic.object_id = i.object_id
AND ic.index_id = i.index_id
AND ic.is_included_column = 0
ORDER BY ic.key_ordinal
FOR XML PATH('')) AS ikey(cols)
OUTER APPLY (SELECT ', ' + c.name
FROM sys.index_columns ic
JOIN sys.columns c ON ic.object_id = c.object_id
ANDic.column_id = c.column_id
WHERE ic.object_id = i.object_id
AND ic.index_id = i.index_id
AND ic.is_included_column = 1
ORDER BY ic.index_column_id
FOR XML PATH('')) AS inc(cols)
WHERE o.name = @tbl
AND i.type IN (1, 2)
ORDER BY o.name, i.index_id
?
结果如下:

?
可以比较直观地看到索引定义及其统计信息更新时间(这个极其重要,但是不是本文的重点,所以也不详细描述)。获取索引定义是为了分析设计是否合理、是否可修改,如果不知道你要操作的对象是什么样子的,也就不可能有下面的步骤。
?
每个表上索引的使用情况:
当你知道有多少个索引,索引是怎样的时候,就可以开始收集索引使用情况,这里分两步,但是可以同时进行:
1.获取索引的读写情况:
2.获取索引的被使用信息,这里的被使用是指:从服务器启动开始(这个很重要,因为你读的是缓存),这个索引在系统中被什么对象(动态SQL、存储过程、函数等,包含了对象的文本信息)使用过,使用了多少次,对应的计划缓存是怎样的。
对于第一步,我们可以用简单的DMV来得到:
?
SELECT OBJECT_NAME(ddius.[object_id]) AS [Table Name] ,
i.name AS [Index Name] ,
i.index_id ,
user_updatesAS [Total Writes],
user_seeks+ user_scans + user_lookups AS [Total Reads] ,
user_updates-( user_seeks + user_scans + user_lookups ) AS [Difference]
FROM sys.dm_db_index_usage_stats ASddius WITH ( NOLOCK )
INNER JOIN sys.indexes AS i WITH ( NOLOCK ) ON ddius.[object_id] = i.[object_id]
A