SQL Server索引进阶第一篇:索引介绍
索引设计是数据库设计中比较重要的一个环节,对数据库的性能其中至关重要的作用,但是索引的设计却又不是那么容易的事情,性能也不是那么轻易就获取到的,很多的技术人员因为不恰当的创建索引,最后使得其效果适得其反,可以说“成也索引,败也索引”。
本系列文章来自Stairway to SQL Server Indexes,然后经过我们团队的理解和整理发布在agilesharp,希望对广大的技术朋友在如何使用索引上有所帮助。
系列文章索目录:
言归正传,其实索引就是数据库中的对象,这一点和数据库中的其他对象一样(如表,视图等),索引的作用就是使得SQL Server在寻找或者修改数据的时候所花的时间最少,所使用的系统资源更少,从而提升性能。
同时,好的索引也可以使得SQL Server尽可能最大的支持并发,从而使得每一个用户的查询对其他人得影响最小。最后,索引也提供了一个非常高效的方式来强制要求数据的完整,这只要是通过在创建索引的时候指定索引中数据列的唯一性来达到的。
我们本篇的文章是整个系列的第一篇,只要是介绍索引的概念和用途,更多的内容将在后续讲述。
首先来看看SQL Server是如何对用户请求的数据进行检索的,基本上有两种方式:
1.从第一行开始,依次扫描表中的每一行数据,然后检查这一行是不是用户需要的数据,直到最后一行。
2.使用索引,快速定位到所请求的数据上面。
对于上面说的第一种方式,这是SQL Server默认的方式,而第二种方式只有当我们在SQL Server上面建立了合适的索引之后才使用,并且会带来很大的性能提升。
因为索引的建立和使用是有一定的开销的(它们会占用物理磁盘空间,并且在于数据表同步的时候会消耗大量的CPU和内存,因为涉及到重组,重建等操作),所以,在SQL Server中是允许数据库没有索引的。当然了,没有索引可能会导致查询过慢等性能问题,以及相关数据完整性问题,但是SQL Server没有强制每个数据库都必须建立索引。
示例数据库准备
为了使得后续的讲解更加的具体,我们这里将会采用SQL Server的示例数据库AdventureWorks进行一些演示。在本篇中,我们主要关注一些与销售和订单相关的数据表,涉及到几个5个表:Customer, SalesPerson, Product, SalesOrderHeader, and SalesOrderDetail。表之间的关系如下:
为了使得大家的理解更加清楚,我们这里从一个小故事开始。
从一个故事开始
有一天,你收到了你女儿垒球教练的一个信息:Tracy, Rebecca和Amy的团队帽子没有丢了,问你是否可以代劳帮她们去商店买三定新的帽子,然后快递给她们。
看到信息之后,你就开始行动了。当然,对于这三个女孩子的父母,你都是认识的,彼此都比较熟悉。但是,你却不知道每个女孩子帽子的尺寸。此时,你很自然的就想到了打电话去问三个孩子的父母,于是你便开始翻你家里的那本记载了全镇人的电话号码的电话薄了。
首先你要找的第一个人就是Helen Meyer。看到“Meyer”之后,按照字母顺序,你很快就跳转到包含“M”的那几页,然后再那几页中找到了这家人的号码。采用同样的方式,你很快的找到了其他两家人得号码。然后一一的打电话过去得到了你想要的信息。
好,到这里,例子就完了,很简单,很原始的一个电话号码查找的例子。在寻找号码的过程中,就采用了索引,这和SQL Server中索引使用的方式是类似的。
在SQL Server索引可以分为两种类型:聚集和非聚集。刚刚在我们在例子中查询电话号码使用的索引其实就类似于一个非聚集索引。所以,我们下面就先介绍非聚集索引。
非聚集索引
为什么说我们刚刚在查询电话号码的时候,使用是非聚集索引呢?
这可能和大家看到的网上的一些介绍有点不同:因为网上很多的文章在介绍聚集索引和非聚集索引的时候总是那书来做例子:聚集索引就类似书中的页面,1,2,3…,而非聚集索引就是书后面的那个索引表,是按照A-Z排序一些关键字。
其实如果见过电话薄(这个电话薄是电话公司发的)的朋友应该知道:电话薄都是按照姓氏来排序的,其实也就是A-Z进行排的,而与电话号码本身没有任何的关系,没有按照电话号码来排,更加没有按照用户的住址来排(例如,按照用户的门牌号)。所以,当我们寻找某个用户的信息的时候,我们按照他们的姓名进行检索,找到对应的电话号码,然后打电话过去,询问更多的信息,也就说,如果要获取用户的全部信息,要进行两次的动作“找号码-打电话“,从这点理解,就和数据库中的非聚集索引类似,并且电话薄中仅仅只是包含了电话号码和用户的名字而已,没有包含其他的更多的信息了,因为如果是聚集索引话,一次跳转就可搞定了。
我们再来说说SQL Server索引。其实索引的查找就是一个“跳着查找“。当我们给数据库一个要检索的值得之后,数据库就会跳到包含这个值得索引中,然后从索引中获取相关的信息,如果此时任然没有找到需要的数据,那么就继续跳到包含完整信息的地方(数据页)。
与电话薄不同的是,数据库中的索引是动态的。每次只要表中有数据更新,添加或者删除的时候,索引就会更新。
正如之前所说的一样:电话薄中的电话号码的顺序不是这个小镇上面用户地理住址顺序。在数据库中,非聚集索引中的每个项的顺序也不是底层的数据表中数据物理顺序。那么就很有可能,在索引中的第一个项所代表的数据在数据表中是最后一条,也有可能,索引中最后一个项代表的数据在表中是第一个。另外,索引中项都是按照一定的顺序排序的,可能底层的数据却是完全无序的。
当创建一个索引之后,SQL Server获取为底层的数据表中的每一行都在索引中维护一个与之对应的项,简而言之就是:底层数据表中的每一行,在索引中都有一个引用指向它。我们可以再一个表上面建立多个非聚集索引,但是索引中的数据只能是这个表的,不能使其他的,否则就没有意义了。
创建和使用非聚集索引
下面,我们就要初窥一下非聚集索引,练一下手。其实本篇主要是介绍,所以内容简单,简单不代表没有意义,可能讲的东西和理解的方式不一样。
我们使用AdventureWorks数据库中的Person.Contact表为例子。手下运行下面的查询,删除已经存在的索引:
- IF EXISTS (SELECT * FROM sys.indexes
- WHERE OBJECT_ID = OBJECT_ID('Person.Contact')AND name = 'FullName')
- DROP INDEX Person.Contact.FullName;
然后运行下面的批命令:
- SET STATISTICS io ON
- SET STATISTICS time ON
- GO
- SELECT * FROM Person.Contact
- WHERE FirstName = 'Helen' AND LastName = 'Meyer'
- GO
- SET STATISTICS io off
- SET STATISTICS time off
查询结果如下:
在这里我们采用了下面两个语句来获取这个查询运行所消耗的I/O和时间:
- SET STATISTICS io ON
- SET STATISTICS time ON
信息如下:
这个查询执行了563个逻辑IO,消耗了3毫秒的CPU。
作为对比,我们在运行下面的语句:
- CREATE NONCLUSTERED INDEX FullName
- ON Person.Contact ( LastName, FirstName )
我们再次运行上面的查询,得到的信息如下:
此时,IO读取只有4个了。
当然,例子很简单,作为简单的演示倒是可以,当时在实际的项目中就没有这么容易了。
不管如何,起码可以知道:索引确确实实是数据库中提升性能的重要手段,我们下一篇将会带领大家看看索引的物理结构。