1.2  数据文件空间使用与管理


在1.1.1节里,讨论过数据文件的结构,以及它们的管理方法。但是不同类型的用户数据是怎么存储在数据文件里的呢?用不同的存储结构存储等量的数据,其空间消耗相等么?当用户添加或者修改数据以后,数据的存储会发生什么样的变化?当用户把数据删除之后,数据原先所占用的空间一定会释放出来么?要有效地管理数据文件空间,管理员必须能够回答这些问题。


本小节会先讨论数据文件里表和索引的存储结构(1.2.1节),以及不同的存储结构会对数据空间使用产生什么样的影响(1.2.2节)。清除历史数据,可以使用DELETE语句,也可以使用TRUNCATE语句,但是这两种方法得到的效果可能是不一样的。而最后的效果,和存储结构也有关系。这部分内容,在1.2.3节里,会做详细讨论。数据文件空间管理的一个比较难的问题,是如何缩小或清空一个现有的数据文件。这需要综合运用前面介绍的各种知识。在1.2.4节里,会做讨论。


1.2.1  表和索引存储结构


前面讲过,在数据文件中,数据以8 KB的方式存储。那这些页面是怎么组织的呢?这就要谈到表格和索引的组织了。


在SQL Server
2005以前,一个表格是以一个B树或者一个堆(Heap)存放的。每个B树或者堆,在sysindexes里面都有一条记录相对应。SQL Server
2005以后,引入了分区表(Table
Partition)的概念。在存储组织上,现在的分区基本上替代了原来表格的概念。一个分区就是一个B树或者一个堆。而一张表格则是一个到多个分区的组合(见图1-22)。


SQL Server使用下列三种方法之一来组织其分区中的数据或索引页:


1. 用B树存储有聚集索引的表数据页。


如果一个表格上有聚集索引(Clustered
Index),数据行将基于聚集索引键按顺序存储。聚集索引按B树索引结构实现,B树索引结构支持基于聚集索引键值对行进行快速检索。数据页面之间用双向链表,紧密相连。







图1-22 
表格的存储组织结构

 


2. 堆是没有聚集索引的表。


如果表格上没有聚集索引,数据行将不按任何特殊的顺序存储,数据页也没有任何特殊的顺序。数据页之间没有链表链接。


3. 非聚集索引。


非聚集索引与聚集索引有一个相似的B树索引结构。不同的是,非聚集索引不影响数据行的顺序。叶级别仅包含索引行,没有完整的数据。每个索引行包含非聚集键值和行定位符。定位符指向(在另一个B树或者堆中)包含键值的数据行。非聚集索引本身也会占用一些数据页。这些页面以双向链表相连。


sys.partitions为表或索引中每个分区返回一行。


1. 每个堆在sys.partitions中有一行记录,其index_id = 0。


sys.system_internals_allocation_units中的first_iam_page列指向指定分区中堆数据页集合的IAM链。因为这些页没有链接,不能从第一页找到下一页,所以SQL
Server只好使用IAM页查找数据页集合中的每一页。


2. 每个表或视图的聚集索引在sys.partitions中有一行记录,其index_id = 1。


sys.system_internals_allocation_units中的root_page列指向指定分区内聚集索引B树的顶端。SQL
Server使用索引B树链表能够从顶端页面查找到分区中的每个数据页。


3. 为表或视图创建的每个非聚集索引在sys.partitions中有一行记录,其index_id > 1。


sys.system_internals_allocation_units中的root_page列指向指定分区内非聚集索引B树的顶端。


至少有一个LOB列(例如text或image字段)的每个表在sys.partitions中也另外再有一行,其index_id >
250,用以管理LOB页面。


first_iam_page列指向管理LOB_DATA分配单元中的页的IAM页链。


换而言之,从一个对象的index_id就能判断出它是什么类型的存储方式。如果是0,就说明这张表没有聚集索引;如果是1,就是聚集索引页面;如果是大于250,就是text或者image字段;如果在2和250之间,就是非聚集索引页面。


堆结构


堆是不含聚集索引的表。SQL Server使用"索引分配映射(IAM)"页将堆的页面联系在一起。堆的特点有以下几个:


1. 堆内的数据页和行没有任何特定的顺序。


在一个堆里的数据完全是随机存放的。而且SQL Server也假设数据之间没有任何联系。


2. 页面也不链接在一起。


数据页之间唯一的逻辑连接是记录在IAM页内的信息。页面与页面之间没有什么紧密的联系。


3. 堆中的行一般不按照插入的顺序返回。


因为IAM按数据页在数据文件内存在的顺序标示它们,所以这意味着堆扫描会沿每个文件进行。而不是按这些行的插入顺序,或者是任何逻辑上的顺序。


图1-23表现了SQL Server数据库引擎如何使用IAM页检索具有单个分区的堆中的数据行。







图1-23 
使用IAM页检索堆中的数据行

 


从上面的介绍我们可以看到,SQL
Server对堆的管理是比较简单的。在算法能力上也是比较弱的。不谈性能,光从数据存储管理上来讲,用堆去管理一个超大的表格是比较吃力的。所以在SQL
Server里,笔者强烈建议在所有大的、经常使用的表格上都建立聚集索引。聚集索引可以帮助避免很多问题。


聚集索引结构


在SQL
Server中,索引是按B树结构进行组织的。索引B树中的每一页称为一个索引节点。B树的顶端节点称为根节点。索引中的底层节点称为叶节点。根节点与叶节点之间的任何索引级别统称为中间级。每个索引行包含一个键值和一个指针,该指针指向B树上的某一中间级页或叶级索引中的某个数据行。每级索引中的页均被链接在双向链接列表中。


数据链内的页和行将按聚集索引键值进行排序。所有插入操作都在所插入行中的键值与现有行中的排序顺序相匹配时执行。B树页集合由sys.system_internals_allocation_units系统视图中的页指针来定位。


对于某个聚集索引,sys.system_internals_allocation_units中的root_page列指向该聚集索引某个特定分区的顶部。SQL
Server将在索引中向下移动以查找与某个聚集索引键对应的行。为了查找键的范围,SQL
Server将在索引中移动以查找该范围的起始键值,然后用向前或向后指针在数据页中进行扫描。为了查找数据页链的首页,SQL
Server将从索引的根节点沿最左边的指针进行扫描。


图1-24显示了聚集索引单个分区中的结构。


相对于堆,聚集索引的特点有以下几个:


1. 堆内的数据页和行有严格的顺序。


聚集索引保证了表格的数据按照索引行的顺序排列。而且SQL Server知道这种顺序关系。


2. 页面链接在一起。页面与页面联系紧密。


3. 树中的行一般能够按照索引列的顺序返回。


从上面的比较我们也能看出来,建立了B树以后,SQL Server对数据页的管理能够更加快速有效。有些会发生在堆上的问题就不容易在B树上发生。







图1-24 
聚集索引单个分区的结构

 


非聚集索引结构


非聚集索引与聚集索引具有相同的B树结构,它们之间的显著差别在于以下两点:


基础表的数据行不按非聚集键的顺序排序和存储。


非聚集索引的叶层是由索引页而不是由数据页组成。


建立非聚集索引的表可以是一个B树,也可以是一个堆。


如果表是堆(意味着该表没有聚集索引),则行定位器是指向行的指针。该指针由文件标识符(ID)、页码和页上的行数生成。整个指针称为行ID(RID)。


如果表有聚集索引或索引视图上有聚集索引,则行定位器是行的聚集索引键。如果聚集索引不是唯一的索引,SQL
Server将添加在内部生成的值(称为唯一值)以使所有重复键唯一。SQL
Server通过使用存储在非聚集索引的叶行内的聚集索引键搜索聚集索引来检索数据行。


所以非聚集索引不会去改变或改善数据页的存储模式。它的B树结构只针对自己的索引页面。如果问题是由堆的特性导致的,加一个非聚集索引不能带来根本的改善。


图1-25说明了单个分区中的非聚集索引结构。







图1-25 
单个分区中的非聚集索引结构