RDS-SQLSERVER索引优化扫描
2014年6月26日?
ALIYUN1
?
10,054 次阅读
最近在处理客户咨询时,有大量咨询,说我的索引健不健康,我们没有专业的DBA,不知道哪里该加索引,加索引又不知道该加到那个字段上?基于这些问题,我来基于微软给出缺失索引统计信息,来总结一个数据库缺失索引的扫描和处理的方法。这种方法虽然不能做到完全准确,但是能对数据库的索引有效性,可行性的做到基础保障。索引的增加必然造成写入速度的下降,好处是提高查询速度,所以增加与否最终实际是需要用户根据自己的业务来权衡利弊来获得一个平衡。下面的方法会给出一些量化值,供您参考,这将对索引增减更有方向性。
1. 索引的选择是根据数据库产生的统计信息来选择,那首先要把保证这些统计信息是最新的。RDS—sqlserver是设置成自动更新。查看数据库是否设置成自动更新看下图:

2. 查看统计信息的最后更新时间
|
----查看表dbo.Table_1下所有索引更新统计数据的时间
SELECT name AS stats_name,
??? STATS_DATE(object_id,stats_id) AS statistics_update_date
FROM sys.stats
WHERE object_id = OBJECT_ID('dbo.Table_1');
GO
3. 更新统计信息(该操作RDS暂时不支持,因为RDS已经为用户的所有数据库默认为自动更新了,另外一个原因就是更新操作需要较高的权限,所以RDS暂时不支持)
--更新数据库testdb 的统计信息Use? testdbEXEC sp_updatestats;
?
4. 如果发现数据库没有设置成自动更新
--开启数据库testdb 自动同步统计信息ALTER DATABASE testdbSET AUTO_UPDATE_STATISTICS ON
?
5. 以上4步主要为了同步统计信息,保障选取索引时,能够选到最优的索引。接下来扫描整个库的缺失索引,脚本如下:
--扫描数据库testdb的缺失索引及参考指标
use testdb
SELECT
??? c.name AS databasename,
??? c.equality_columns,
??? c.inequality_columns,
??? c.included_columns,
??? c.statement AS tablename,
??? c.avg_total_user_cost AS ReducingTheAverageCost,
??? c.avg_user_impact AS PercentageOfRevenue,
??? c.last_user_seek AS TheLastTimeTheEffectAfterUse,
??? c.unique_compiles
FROM (SELECT
??? a.name,
??? b.*
FROM?? (SELECT
?????????? d.*,
?????????? s.avg_total_user_cost,
?????????? s.avg_user_impact,
?????????? s.last_user_seek,
?????????? s.unique_compiles
?????? FROM?? sys.dm_db_missing_index_group_stats s,
????????????? sys.dm_db_missing_index_groups g,
????????????? sys.dm_db_missing_index_details d
?????? WHERE s.group_handle = g.index_group_handle
?????? AND d.index_handle = g.index_handle
?????? AND s.avg_user_impact > 90
?????? --AND s.unique_compiles > 10
?????? --order by s.avg_user_impact desc
?????? ) b,
?????? sys.databases a
WHERE a.database_id = b.database_id) c
WHERE c.name = 'testdb'
ORDER BY PercentageOfRevenue DESC,unique_compiles DESC
?
该结果集每条记录为一条缺失索引,结果集字段意义描述:
Databasename: 数据库名称,多数据库并且where 子句中不指定数据库名称时,该字段就比较有用了。
equality_columns:构成相等谓词的列的逗号分隔列表 即哪个字段缺失了索引会在这里列出来(简单来讲就是where 后面的筛选字段),谓词的形式如下:table.column =constant_value5。
inequality_columns:构成不等谓词的列的逗号分隔列表,例如以下形式的谓词:table.column > constant_value “=”之外的任何比较运算符都表示不相等。
included_columns:该列是包含的列,上面两个列建立索引的列,在建立索引同时还可以指定其他列作为包含列,来覆盖更大的查询范围。比如通过索引定位到的列正好在包含列中,那么就不要去表里取数据了,而是直接从包含列里取出即可。提高查询速度。
Tablename:需要建立索引的表名。
ReducingTheAverageCost:平均减少成本 ,就是增加该索引后在现有受到影响的所有查询计划减少的平均成本。当然是减少的越多越需要建立该索引。
PercentageOfRevenue:百分比收益,就是实现此缺失索引后,用户查询可能获得的平均百分比收益。收益的越多越需要建立该索引。
last_user_seek:使用后影响上次结果的时间,就是用户查询所导致的上次查找日期和时间。该字段主要是看出用户最后一次使用该索引进行的seek时间。
unique_compiles:增加该确失索引后,会受益的查询计划数和重新编译数
举个列子说明根据结果集来建立索引:
假设结果集为:

图可能有点小,我把第一条记录各个字段内容罗列出来,用于生成索引的脚本:
Databasename: ydt2012sjk,
Equality_columns: [Status]
Inequality_columns:[ProName],[HSCode]
Included_columns:[ID],[GuestID]
Tablename: [ydt2012sjk].[dbo].[CustomsProducts]
ReducingTheAverageCost: 11.2928905868223
PercetageOfRevenue: 100
TheLasTimeTheEffectAfterUse: 2014-06-25 11:18:04.027
Unique_compiles:26
从这条记录可以看出,数据库ydt2012sjk的表CustomsProducts需要建立一个索引,该索引建立后会影响26个当前已有的查询计划,每个查询计划平均可以减少执行成为11.2928905868223,获得的收益是100%的。因此我们应该建立该索引,那么建立索引的脚本为:
USE [ydt2012sjk]
GO
CREATE NONCLUSTERED INDEX ?indexName
ON [dbo].[ CustomsProducts] ([Status],[ ProName],[ HSCode])
INCLUDE ([ID],[ GuestID])
With (online=on)
GO
--indexName 需要自己起一个没有用户过的索引名称
-- With (online=on)在线增加索引,减少因加索引而影响业务。
?
?
注意:
1. 索引的增加一定要根据业务权衡,到底需要读快更重要还是写快更重要。
2. 建议先增加收益率比较高,并且影响查询计划数多的索引。随着索引的增加,最优查询计划的改变,有些索引将不再使用,或者使用较少。我们可以将其删除(需要删除的索引下一步给出方法判断),以提高写入性能。
3. 扫描没有在使用或者使用较少的索引。
use databasename
--没有使用的索引
SELECT? ind.index_id,
??????? obj.name AS TableName,
??????? ind.name AS IndexName,
??????? ind.type_desc,
??????? indUsage.user_seeks,
??????? indUsage.user_scans,
??????? indUsage.user_lookups,
??????? indUsage.user_updates,
??????? indUsage.last_system_seek,
??????? indUsage.last_user_scan,
??????? 'drop index [' + ind.name + '] ON [' + obj.name + ']' AS DropIndexCommand
FROM??? sys.indexes AS ind
??????? INNER JOIN sys.objects AS obj ON ind.object_id = obj.object_id
??????? LEFT JOIN sys.dm_db_index_usage_stats indUsage ON ind.object_id = indUsage.object_id
????????????????????????????????????????????????????????? AND ind.index_id = indUsage.index_id
WHERE?? ind.type_desc <> 'HEAP'
??????? AND obj.type <> 'S'
??????? AND obj.create_date < '2014-4-5'
??????? And ind.name not like 'PK_%'
??????? And ind.name not like 'AK_%'
??????? And ind.name not like 'UK%'
??????? AND OBJECTPROPERTY(obj.object_id,'isusertable') = 1
??????? AND ( ISNULL(indUsage.user_seeks,0) = 0
????????????? AND ISNULL(indUsage.user_scans,0) = 0
????????????? AND ISNULL(indUsage.user_lookups,0) = 0
??????????? )
??????? AND user_updates > 10
ORDER BY?? ind.name
GO
-- 更新次数太多,而使用次数很少,下面的3000和100 这个两个数值,是定义使用少的标准,100是用户使用索引进行的扫描、查找的总次数,这个数值越小说明使用的次数越少。而3000是指为数据发生改变需要维护索引,对索引数据修改次数。用户可以定义自己的标准。来描述使用少的标准。
SELECT? ind.index_id,
????? ??'drop index [' + ind.name + '] ON [' + obj.name + ']' AS DropIndexCommand
FROM??? sys.indexes AS ind
??????? INNER JOIN sys.objects AS obj ON ind.object_id = obj.object_id
??????? LEFT JOIN sys.dm_db_index_usage_stats indUsage ON ind.object_id = indUsage.object_id
????????????????????????????????????????????????????????? AND ind.index_id = indUsage.index_id
WHERE?? ind.type_desc <> 'HEAP'
??????? AND obj.type <> 'S'
??????? And ind.name not like 'PK_%'
??????? And ind.name not like 'AK_%'
??????? And ind.name not like 'UK%'
??????? AND OBJECTPROPERTY(obj.object_id,'isusertable') = 1
??????? AND user_scans + user_seeks + user_lookups < indUsage.user_updates
??????? AND user_scans + user_seeks + user_lookups < 100
??????? AND indUsage.user_updates > 3000
ORDER BY indUsage.user_updates desc
GO
?
索引优化是一个长期不间断的过程,随着业务的变化、数据量的变化,访问量的变化,索引也需要变化。
通过长时间的优化,找到读写平衡点,使数据库运行在一个相对稳定的索引状态下即可。
?
?
?
? 转载请注明来源:
ALIRDS官方技术支持博客??
《rds-sqlserver索引优化扫描》
http://blog.rds.aliyun.com/2014/06/26/rds-sqlserver%e7%b4%a2%e5%bc%95%e4%bc%98%e5%8c%96%e6%89%ab%e6%8f%8f/
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!