原文地址:http://tech.it168.com/a2009/1125/814/000000814758_all.shtmlhtml
我之因此先从索引谈起是由于采用正确的索引会使生产系统的性能获得质的提高,另外一个缘由是建立或修改索引是在数据库上进行的,不会涉及到修改程序,并能够当即见到成效。数据库
咱们仍是温习一下索引的基础知识吧,我相信你已经知道什么是索引了,但我见到不少人都还不是很明白,我先给你们将一个故事吧。服务器
好久之前,在一个古城的的大图书馆中珍藏有成千上万本书籍,但书架上的书没有按任何顺序摆放,所以每当有人询问某本书时,图书管理员只有挨个寻找,每一次都要花费大量的时间。app
[这就比如数据表没有主键同样,搜索表中的数据时,数据库引擎必须进行全表扫描,效率极其低下。]函数
更糟的是图书馆的图书愈来愈多,图书管理员的工做变得异常痛苦,有一天来了一个聪明的小伙子,他看到图书管理员的痛苦工做后,想出了一个办法,他建议将每本书都编上号,而后按编号放到书架上,若是有人指定了图书编号,那么图书管理员很快就能够找到它的位置了。工具
[给图书编号就象给表建立主键同样,建立主键时,会建立汇集索引树,表中的全部行会在文件系统上根据主键值进行物理排序,当查询表中任一行时,数据库首先使用汇集索引树找到对应的数据页(就象首先找到书架同样),而后在数据页中根据主键键值找到目标行(就象找到书架上的书同样)。]性能
因而图书管理员开始给图书编号,而后根据编号将书放到书架上,为此他花了整整一天时间,但最后通过测试,他发现找书的效率大大提升了。测试
[在一个表上只能建立一个汇集索引,就象书只能按一种规则摆放同样。]优化
但问题并未彻底解决,由于不少人记不住书的编号,只记得书的名字,图书管理员无赖又只有扫描全部的图书编号挨个寻找,但此次他只花了20分钟,之前未给图书编号时要花2-3小时,但与根据图书编号查找图书相比,时间仍是太长了,所以他向那个聪明的小伙子求助。spa
[这就好像你给Product表增长了主键ProductID,但除此以外没有创建其它索引,当使用Product Name进行检索时,数据库引擎又只要进行全表扫描,逐个寻找了。]
聪明的小伙告诉图书管理员,以前已经建立好了图书编号,如今只须要再建立一个索引或目录,将图书名称和对应的编号一块儿存储起来,但这一次是按图书名称进行排序,若是有人想找“Database Management System”一书,你只须要跳到“D”开头的目录,而后按照编号就能够找到图书了。
因而图书管理员兴奋地花了几个小时建立了一个“图书名称”目录,通过测试,如今找一本书的时间缩短到1分钟了(其中30秒用于从“图书名称”目录中查找编号,另外根据编号查找图书用了30秒)。
图书管理员开始了新的思考,读者可能还会根据图书的其它属性来找书,如做者,因而他用一样的办法为做者也建立了目录,如今能够根据图书编号,书名和做者在1分钟内查找任何图书了,图书管理员的工做变得轻松了,故事也到此结束。
到此,我相信你已经彻底理解了索引的真正含义。假设咱们有一个Products表,建立了一个汇集索引(根据表的主键自动建立的),咱们还须要在ProductName列上建立一个非汇集索引,建立非汇集索引时,数据库引擎会为非汇集索引自动建立一个索引树(就象故事中的“图书名称”目录同样),产品名称会存储在索引页中,每一个索引页包括必定范围的产品名称和它们对应的主键键值,当使用产品名称进行检索时,数据库引擎首先会根据产品名称查找非汇集索引树查出主键键值,而后使用主键键值查找汇集索引树找到最终的产品。
下图显示了一个索引树的结构
图 1 索引树结构
它叫作B+树(或平衡树),中间节点包含值的范围,指引SQL引擎应该在哪里去查找特定的索引值,叶子节点包含真正的索引值,若是这是一个汇集索引树,叶子节点就是物理数据页,若是这是一个非汇集索引树,叶子节点包含索引值和汇集索引键(数据库引擎使用它在汇集索引树中查找对应的行)。
一般,在索引树中查找目标值,而后跳到真实的行,这个过程是花不了什么时间的,所以索引通常会提升数据检索速度。下面的步骤将有助于你正确应用索引。
确保每一个表都有主键
这样能够确保每一个表都有汇集索引(表在磁盘上的物理存储是按照主键顺序排列的),使用主键检索表中的数据,或在主键字段上进行排序,或在where子句中指定任意范围的主键键值时,其速度都是很是快的。
在下面这些列上建立非汇集索引:
1)搜索时常用到的;
2)用于链接其它表的;
3)用于外键字段的;
4)高选中性的;
5)ORDER BY子句使用到的;
6)XML类型。
假设你在Sales表(SelesID,SalesDate,SalesPersonID,ProductID,Qty)的外键列(ProductID)上建立了一个索引,假设ProductID列是一个高选中性列,那么任何在where子句中使用索引列(ProductID)的select查询都会更快,若是在外键上没有建立索引,将会发生所有扫描,但还有办法能够进一步提高查询性能。
假设Sales表有10,000行记录,下面的SQL语句选中400行(总行数的4%):
咱们来看看这条SQL语句在SQL执行引擎中是如何执行的:
1)Sales表在ProductID列上有一个非汇集索引,所以它查找非汇集索引树找出ProductID=112的记录;
2)包含ProductID = 112记录的索引页也包括全部的汇集索引键(全部的主键键值,即SalesID);
3)针对每个主键(这里是400),SQL Server引擎查找汇集索引树找出真实的行在对应页面中的位置;
SQL Server引擎从对应的行查找SalesDate和SalesPersonID列的值。
在上面的步骤中,对ProductID = 112的每一个主键记录(这里是400),SQL Server引擎要搜索400次汇集索引树以检索查询中指定的其它列(SalesDate,SalesPersonID)。
若是非汇集索引页中包括了汇集索引键和其它两列(SalesDate,,SalesPersonID)的值,SQL Server引擎可能不会执行上面的第3和4步,直接从非汇集索引树查找ProductID列速度还会快一些,直接从索引页读取这三列的数值。
幸运的是,有一种方法实现了这个功能,它被称为“覆盖索引”,在表列上建立覆盖索引时,须要指定哪些额外的列值须要和汇集索引键值(主键)一块儿存储在索引页中。下面是在Sales 表ProductID列上建立覆盖索引的例子:
应该在那些select查询中常使用到的列上建立覆盖索引,但覆盖索引中包括过多的列也不行,由于覆盖索引列的值是存储在内存中的,这样会消耗过多内存,引起性能降低。
建立覆盖索引时使用数据库调整顾问
咱们知道,当SQL出问题时,SQL Server引擎中的优化器根据下列因素自动生成不一样的查询计划:
1)数据量
2)统计数据
3)索引变化
4)TSQL中的参数值
5)服务器负载
这就意味着,对于特定的SQL,即便表和索引结构是同样的,但在生产服务器和在测试服务器上产生的执行计划可能会不同,这也意味着在测试服务器上建立的索引能够提升应用程序的性能,但在生产服务器上建立一样的索引却未必会提升应用程序的性能。由于测试环境中的执行计划利用了新建立的索引,但在生产环境中执行计划可能不会利用新建立的索引(例如,一个非汇集索引列在生产环境中不是一个高选中性列,但在测试环境中可能就不同)。
所以咱们在建立索引时,要知道执行计划是否会真正利用它,但咱们怎么才能知道呢?答案就是在测试服务器上模拟生产环境负载,而后建立合适的索引并进行测试,若是这样测试发现索引能够提升性能,那么它在生产环境也就更可能提升应用程序的性能了。
虽然要模拟一个真实的负载比较困难,但目前已经有不少工具能够帮助咱们。
使用SQL profiler跟踪生产服务器,尽管不建议在生产环境中使用SQL profiler,但有时没有办法,要诊断性能问题关键所在,必须得用,在http://msdn.microsoft.com/en-us/library/ms181091.aspx有SQL profiler的使用方法。
使用SQL profiler建立的跟踪文件,在测试服务器上利用数据库调整顾问建立一个相似的负载,大多数时候,调整顾问会给出一些能够当即使用的索引建议,在http://msdn.microsoft.com/en-us/library/ms166575.aspx有调整顾问的详细介绍。
你可能已经建立好了索引,而且全部索引都在工做,但性能却仍然很差,那极可能是产生了索引碎片,你须要进行索引碎片整理。
什么是索引碎片?
因为表上有过分地插入、修改和删除操做,索引页被分红多块就造成了索引碎片,若是索引碎片严重,那扫描索引的时间就会变长,甚至致使索引不可用,所以数据检索操做就慢下来了。
有两种类型的索引碎片:内部碎片和外部碎片。
内部碎片:为了有效的利用内存,使内存产生更少的碎片,要对内存分页,内存以页为单位来使用,最后一页每每装不满,因而造成了内部碎片。
外部碎片:为了共享要分段,在段的换入换出时造成外部碎片,好比5K的段换出后,有一个4k的段进来放到原来5k的地方,因而造成1k的外部碎片。
如何知道是否发生了索引碎片?
执行下面的SQL语句就知道了(下面的语句能够在SQL Server 2005及后续版本中运行,用你的数据库名替换掉这里的AdventureWorks):
执行后显示AdventureWorks数据库的索引碎片信息。
图 3 索引碎片信息
使用下面的规则分析结果,你就能够找出哪里发生了索引碎片:
1)ExternalFragmentation的值>10表示对应的索引起生了外部碎片;
2)InternalFragmentation的值<75表示对应的索引起生了内部碎片。
如何整理索引碎片?
有两种整理索引碎片的方法:
1)重组有碎片的索引:执行下面的命令
ALTER INDEX ALL ON TableName REORGANIZE
2)重建索引:执行下面的命令
ALTER INDEX ALL ON TableName REBUILD WITH (FILLFACTOR=90,ONLINE=ON)
也可使用索引名代替这里的“ALL”关键字重组或重建单个索引,也可使用SQL Server管理工做台进行索引碎片的整理。
图 4 使用SQL Server管理工做台整理索引碎片
何时用重组,何时用重建呢?
当对应索引的外部碎片值介于10-15之间,内部碎片值介于60-75之间时使用重组,其它状况就应该使用重建。
值得注意的是重建索引时,索引对应的表会被锁定,但重组不会锁表,所以在生产系统中,对大表重建索引要慎重,由于在大表上建立索引可能会花几个小时,幸运的是,从SQL Server 2005开始,微软提出了一个解决办法,在重建索引时,将ONLINE选项设置为ON,这样能够保证重建索引时表仍然能够正常使用。
虽然索引能够提升查询速度,但若是你的数据库是一个事务型数据库,大多数时候都是更新操做,更新数据也就意味着要更新索引,这个时候就要兼顾查询和更新操做了,由于在OLTP数据库表上建立过多的索引会下降总体数据库性能。
我给你们一个建议:若是你的数据库是事务型的,平均每一个表上不能超过5个索引,若是你的数据库是数据仓库型,平均每一个表能够建立10个索引都没问题。
在前面咱们介绍了如何正确使用索引,调整索引是见效最快的性能调优方法,但通常而言,调整索引只会提升查询性能。除此以外,咱们还能够调整数据访问代码和TSQL,本文就介绍如何以最优的方法重构数据访问代码和TSQL。
也许你不喜欢个人这个建议,你或你的团队可能已经有一个默认的潜规则,那就是使用ORM(Object Relational Mapping,即对象关系映射)生成全部SQL,并将SQL放在应用程序中,但若是你要优化数据访问性能,或须要调试应用程序性能问题,我建议你将SQL代码移植到数据库上(使用存储过程,视图,函数和触发器),缘由以下:
一、使用存储过程,视图,函数和触发器实现应用程序中SQL代码的功能有助于减小应用程序中SQL复制的弊端,由于如今只在一个地方集中处理SQL,为之后的代码复用打下了良好的基础。
二、使用数据库对象实现全部的TSQL有助于分析TSQL的性能问题,同时有助于你集中管理TSQL代码。
三、将TS QL移植到数据库上去后,能够更好地重构TSQL代码,以利用数据库的高级索引特性。此外,应用程序中没了SQL代码也将更加简洁。
虽然这一步可能不会象前三步那样立竿见影,但作这一步的主要目的是为后面的优化步骤打下基础。若是在你的应用程序中使用ORM(如NHibernate)实现了数据访问例行程序,在测试或开发环境中你可能发现它们工做得很好,但在生产数据库上却可能遇到问题,这时你可能须要反思基于ORM的数据访问逻辑,利用TSQL对象实现数据访问例行程序是一种好办法,这样作有更多的机会从数据库角度来优化性能。
我向你保证,若是你花1-2人月来完成迁移,那之后确定不止节约1-2人年的的成本。
OK!假设你已经照个人作的了,彻底将TSQL迁移到数据库上去了,下面就进入正题吧!