曾经有朋友说:你是搞开发的,需要懂把数据库搞的那么深吗?我曾经也认为:确实没有必要,不是有DBA吗?后来,我发现我错了:第一,没有几个公司有很明确的DBA,很多时候开发人员做着DBA的事情;第二,数据库是一个应用的关键,性能优化,很大的程度上,都需要关注数据库;第三,如果想以后为项目做架构,做设计,不懂数据库,不考虑它的存储架构,设计,维护,性能,甚至是灾难恢复,如何敢说自己会做架构,如何扛起这么大一个责任。朋友们可能就说了:这么多,搞的完吗?看需要,需要啥,就去学习啥,开始的时候,为了应急,可以懂个大概,等时间有了,再深入!
不得不承认我们技术界有些浮躁的气氛,存在着很多的“潜规则论“,”关系论“,”忽悠论“等,但是不管怎么样,作为一个技术人员,要过活下去,要有点竞争力,技术这一关是要扎扎实实过的!我们分析内核,不是单从理论上讲述,而是从实用的角度,我也不想浪费大家的时间去看毫无用处的文章,也不想浪费自己的时间写没有营养的东西。
如果大家认为说的还有点道理,就接着往下看技术的部分;如果不赞同,大家可以选择不看下面的文章,避免浪费大家的 时间!
在上一篇文章中介绍了Scan的操作,因为我每次都只是介绍一小点,所以,朋友们可能阅读起来没有感觉,一是因为这些东西确实不好讲,二是,我希望每次写一点,利于大家消化,等到整个系列文章完整之后,大家就会全面掌握这些知识的每一个细节,自己在啃这些东西的时候,也是一点点的消化的。
说了这么多,我们言归正传,对于Seek,这个操作主要是发生在聚集索引和非聚集索引上面。
为了讲明这个问题,我们依然采用的是数据库AdventureWorks,看看下面的一个例子,看到下面的查询:
我们运行这个查询并且查看它的执行计划,如下图:
很显然,这个查询最后在执行的时候,采用了Seek操作。
下面,我们就来具体的看看,为什么?
首先,我们讲解一下与数据库索引存储相关的知识。
我们知道,聚集索引是按 B 树结构进行组织的,既然是B树组织,那么就有叶子结点和非叶子节点之分。聚集索引B 树的顶端节点称为根节点;聚集索引中的底层节点称为叶节点。在根节点与叶节点之间的任何索引级别统称为中间级。
在聚集索引中,叶节点包含基础表的数据页。根节点和中间级节点包含存有索引行的索引页。每个索引行包含一个键值和一个指针,该指针指向 B 树上的某一中间级页或叶级索引中的某个数据行。每级索引中的页都处于链接在双向链接所在列表中。
可以这么说,聚集索引的叶子结点存储的是按聚集索引顺序排列的数据本身,而中间结点和根节点则在维护索引和其层级。如图:
对于每个聚集索引,在系统表 sys.system_internals_allocation_units 中的 root_page 列指向该聚集索引某个特定分区的顶部。下图就是sys.system_internals_allocation_units的数据:
SQL Server 将从索引中向下移动以查找与某个聚集索引键对应的行。
为了查找键的范围,SQL Server 将在索引中移动以查找该范围的起始键值,然后用向前或向后指针在数据页中进行扫描。
为了查找数据页链的首页,SQL Server 将从索引的根节点沿最左边的指针进行扫描。
看完了上面的介绍之后,大家基本对聚集索引的存储有了大致的理解。
下面我们首先来看看Person.Address表的聚集索引的定义:
大家看到上面的这个图,这个表的聚集索引就是AddressID。当查询在执行的时候,就会使用AddressID作为索引键通过B树一下子就找到了这个键对应的数据页,然后读数据!
在下一篇中,我们看看非聚集索引的一些情况!
非聚集索引与聚集索引具有相同的 B 树结构,它们之间的显著差别在于以下两点:
1.基础表的数据行不按非聚集键的顺序排序和存储。
2.非聚集索引的叶层是由索引页而不是由数据页组成。
非聚集索引既可以建在堆表结构上也可以建在聚集索引表上;非聚集索引中的每个索引行都包含非聚集键值和行定位符。此定位符指向聚集索引或堆中包含该键值的数据行。
如果表是堆则行定位器是指向行的指针。该指针由文件标识符 (ID)、页码和页上的行数生成。整个指针称为行 ID (RID)。
如果表包含有聚集索引,则行定位器是行的聚集索引键。如果聚集索引不是唯一的索引,SQL Server 将添加在内部生成的值(称为唯一值)以使所有重复键唯一。此值对于用户不可见。仅当需要使聚集键唯一以用于非聚集索引中时,才添加该值。SQL Server 通过使用存储在非聚集索引的叶行内的聚集索引键搜索聚集索引来检索数据行。
我们来看下一个例子:
这个查询的执行计划如下:
毫无疑问,进行了一个非聚集索引的Seek操作,下面我们就稍微具体的来看看。
还是看看这个查询中的一些字段以及这个表中的非聚集索引的定义:
我们发现,这个查询中的StateProvinceID字段上建立了聚集索引,之前所说的:在一个包含有聚集索引的表中,非聚集索引会包含对聚集索引的引用,以便快速的查找数据。也就说:我们查询中的两个字段,都是在索引中的,此时只要查找这个索引结构就行了:因为查询中的两个字段都在索引结构里,没有必要去读底层数据页了,这个速度也是很快的。