【译】SQL Server索引进阶第十五篇:索引的最佳实践
索引设计是数据库设计中比较重要的一个环节,对数据库的性能其中至关重要的作用,但是索引的设计却又不是那么容易的事情,性能也不是那么轻易就获取到的,很多的技术人员因为不恰当的创建索引,最后使得其效果适得其反,可以说“成也索引,败也索引”
本系列文章来自Stairway to SQL Server Indexes,翻译和整理发布在agilesharp和博客园,希望对广大的技术朋友在如何使用索引上有所帮助。
在本篇文章中,我们在学习了之前的知识之后,推荐14条指导方针。这14条指导方针可以帮助你更好的为数据库构建索引。
本篇文章的格式使用了由Addison Wesley出版社出版的<Framework Design Guidelines>中使用的格式。每一个最佳实践之前都使用了如下4个动词:要,考虑,避免、不要,分别代表如下意思:
要(Do):这个原则要坚决遵守
考虑(Consider):通常情况下都要遵循这个原则,但如果你对原则背后的原理有了深入了理解,可以根据实际情况不采用这个原则
避免(Avoid):考虑的反义词,意味着避免做某这类事,但同样,如果你了解了背后的原理,则可以根据实际情况做这类事。
不要(Do Not):避免的增强版,意思是无论什么时候都不要做这类事。
指导方针
要了解跑在数据库上的应用程序/用户
使用索引的主要目的是为了提高跑在数据库上应用程序读取和操作数据的速度,如果你不知道程序主要对数据库进行什么操作,索引优化就无从谈起。
当然,如果你全程参与了程序的设计和开发,那再好不过。但这种情况少之又少,大多数情况都是你直接接手数据库和应用程序,这时你就需要两步走的了解你所接手的东西-通过外部和内部。
外部方法包括从用户那里了解程序相关的信息,观察他们使用程序的过程,阅读用户文档和交接文档。
内部方法是去看程序本身对数据库产生的操作。比如说Activity Monitor, Profiler等工具,也可以使用sys.dm_db_index usage_stats和sys.dm_db_missing_index_XXX系列DMV中找到所需信息,这些信息包括用的最多的查询,用的最多的索引,用的少的索引以及本应建却没有建的索引。
通过找到拖累系统性能的查询,比如报表服务中用到的语句,agent中执行的T-SQL,SSIS中执行的T-SQL以及存储过程。找到这类信息就可以知道优化该从何处下手。
得到上面的信息后,就可以知道哪些索引应当存在,哪些索引应该删除。
不要过度创建索引
过多的索引和太少的索引都不是好事。表中该有多少索引可不是一个固定的数字。当你为主键,候选键和外键建立了索引之后,剩下的索引该怎么建就需要谨慎分析后再做定夺了。
要明白这点:同样的数据库在不同的环境下要有不同的索引
在忙时或是闲时;在OLTP环境或是OLAP环境下,所需要的索引是不同的。
比如每天晚上一次性大量更新数据的报表数据库在这时只需要少量索引,而在日间忙时则需要大量索引。数据库上跑少量查询要比数据库跑大量查询需要更少的索引。
要给每个表设置主键
虽然SQL Server并不强制要求设置主键。但一个没有主键的表无论在OLTP还是OLAP环境下都是一件危险的事,因为没有主键就不能保证每行是唯一的。这时你就无法知道同一行数据是否在表中存在两条,尤其是在你还没有足够的信息去分析这点时。
尽管SQL Server不强制要求设置主键,但主键是关系数据库的一个关键理论。如果没有主键约束,那么与之关联的唯一索引或是连接操作就有可能产生意料之外的性能问题。
除此之外,很多第三方开发工具或插件也要求表有主键,比如说吧,ADO.Net的SqlCommandBuilder和Entity Data Modeler都依赖表中存在主键约束。另外,主键约束会创建一个同名的唯一索引来保证主键的唯一性。
考虑给每个表设置聚集索引
本系列第三篇关于聚集索引的文章阐述了聚集索引带来的好处。使用聚集索引表中的数据就是按聚集索引键的顺序存在而不再以堆存放。使用聚集索引的好处是使得数据按照聚集索引键的顺序存放,并使得后插入的元素依然保持这个顺序。
如果你遵循了上一个建议,那么每个表都应该有主键,因此,每一个表都应该有一个或多个索引,让其中的一个索引成为聚集索引。聚集索引本身并不会使得表上多了一个索引,而是让表的结构更好的组织。
选择聚集索引键时,要记住第六篇文章中所说的,聚集索引键应该唯一,短和尽量不需要改动。
考虑使用外键作为聚集索引键的最左列
将外键设置为聚集索引的最左列就是将表中的数据按照这列的值进行汇总和组织,这也是查询所需。比如说你用信用卡消费这个行为是和卡关联最强的的,而不是和你刷卡的商场以及处理这笔消费的银行。则将信用卡号作为消费记录表中聚集索引的最左列,使得所有同一张卡的消费信息就会存在连续的页中。
当然了,你还需要另外一个很少变动的列和这个信用卡号列组合起来保证聚集索引键的唯一性。
考虑为索引添加包含列
(译者注:这里作者文章有BUG,这段和上段一样,我就大胆的写一下原因吧。)为索引添加包含列的原因是减少对索引所在表的书签查找。因为包含列不会占用索引的非叶子节点空间,所以不会影响B树的高度,通过在叶子节点附加上一些列,使得索引更容易的“覆盖”所请求的查询,从而减少了书签查找,降低了查询成本。
但同样,使用包含列使得非聚集索引占用的空间增加了,所以使用包含列时要综合考虑。
避免为重复值很多的未过滤列创建非聚集索引
更早之前的一条建议“永远不要索引性别列”,是由于这列只会存在男性和女性两个值。当遇到WHERE Gender=的语句时使用表扫描要远远好于书签查找,查询优化器无法从这个索引中获益。
考虑为列中重复值最多的值创建过滤索引
如果某列大量的行中都存在相同的值,这个值可以是NULL,那么使用过滤索引将这个值过滤掉,剩下的值所生成的索引就会更小,小索引使得查询优化器选择书签查找而不是表扫描,SQL Server也就更容易使用索引。
考虑使用填充因子来面对未来的数据增长
假如一个表中只有几个月的数据,但这个表年底的数据已经可以估算出来时,重建索引的过程中将填充因子设置为7或8,这将使索引占用的页和年底占用的页大致相同,这可以更早的暴漏性能问题,比如说表扫描时IO的占用。
考虑使用填充因子来减少页分裂
加入表中的数据已经达到了页所能容纳的最大值。那么再插入数据就会导致页分裂了。因此重建索引时可以使用填充因子,如果数据库写大于读的话,设置填充因子为75,如果读写大致相等的话,设置填充因子为90到95.
要在创建非聚集索引之前,先创建聚集索引
与之对应的指导方针是:在删除聚集索引之前,先删除非聚集索引。如果你不按照这条方针做,则会导致无意义的重建非聚集索引。将表由聚集索引变为堆会使得表上的非聚集索引重建,因为非聚集索引的书签由聚集索引键变为RID。
要根据索引的使用频率定期整理索引碎片或是重建索引
如果一个索引经常用于扫描,正如我们在第11篇文章中所说,那么外部碎片对于性能的影响就变得非常大。这时你就需要考虑在外部碎片到达10%的时候整理索引了。当外部碎片达到30%时就需要重建索引。对于OLTP环境来说,上面的值是一个分界点,这个点就是整理或重建索引的代价小于其带来的性能提升。
要经常更新索引的统计信息
关键字是“经常”,也就是多久更新一次。通过了解跑在数据库上面的程序的负载之后,就知道该多久更新一次统计信息了。我已经在第14篇文章中讲述了为什么要定期更新索引。
总结
上面的这些最佳实践是来自多个DBA多年来实践于不同环境所产生的。遵循这些指导方针可以帮助你创建更好的的索引。