1、表和索引存储结构
在SQL Server2005以前,一个表格是以一个B树或者一个堆(heap)存放的。每个B树或者堆,在sysindexes里面都有一条记录相对应。SQL Server2005以后,引入了分区表的概念(Table Partition),在存储组织上,现有的分区基本上替代了原来表格的概念,原先表的概念成为了一个逻辑概念。一个分区就是一个B树或者一个堆。而一张表格则是一个到多个分区的组合。
1.1用B树存储于聚集索引的表数据页
如果一个表格上有聚集索引(Clustered Index),数据行将基于聚集索引键按顺序存储。聚集索引按B树索引结构来实现,B树索引结构支持基于聚集索引键值对行快速检索。数据页面之间用双向链表,紧密相连。
1.2堆是没有聚集索引的表
如果表格上没有聚集索引,数据行将不按任何的顺序存储,数据页也没有任何特殊的顺序。数据页之间没有链表链接。
1.3非聚集索引
非聚集索引与聚集索引有一个相似的B树索引结构。不同的是,非聚集索引不影响数据行的顺序。叶级别仅包含索引行,没有完整的数据。每个索引行包含非聚集索引键值和行定位符。定位符指向(在另一个B树或者堆中)包含键值的数据行。非聚集索引本身也会占用一些数据页。这些页面以双向链表相连。
sys.partitions为表和索引提供一个对象,每个对象占据一行信息,其中分为以下几类:
1、index_id=0,这种行记录的是堆表信息
sys.system_internals_allocation_units中的first_iam_page列指向指定分区中堆数据页i聚合的IAM链。因为这些页没有连接,不可能从第一页找到下一页,所以SQL Server只能使用IAM页查找数据页集合中的每一页。
2、index_id=1,表示表或视图的聚集索引
sys.system_internals_allocation_units中的root_page列指向指定分区内聚集索引B树的顶端。SQL Server使用索引B树链表能够从顶端页面查找到分页中的每个数据页。
3、index_id>1,为表或视图创建的非聚集索引
sys.system_internals_allocation_units中的root_page列指向指定分区内非聚集索引B树的顶端。
如果存在LOB列(image、varchar(max)、text)的每个表在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按数据页在数据文件内存在的顺序标示他们,所以这意味着堆扫描会沿每个文件进行。而不是按这些行的插入顺序,或者任何逻辑上的顺序。
下图,表现了SQL Server数据库引擎如何使用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、堆内的数据页和行有严格的顺序。
聚集索引保证了表格的数据按照索引行的顺序排列。而且SQL Server知道这种顺序关系。
2、页面链接在一起。页面与页面联系紧密。
3、树种的行一般能够按照索引列的顺序返回。
所以从这几点来看,建立B树以后,SQL Sever对数据页的管理能够更加快速有效,有些发生在堆上的问题就不容易发生在B树上,性能高出很多
非聚集索引结构
非聚集索引与聚集索引具有相同的B树结构,他们之间的显著差别在于以下两点:
1、基础表的数据行不按非聚集键的顺序排序和存储
2、非聚集索引的页层是由索引页面不是由数据页组成
3、建立非聚集索引的表可以是一个B树,也可以是一个堆。
4、如果表示堆(意味着该表没有聚集索引),则行定位器是指向行的指针。该指针由文件标识符(ID)、页码和页上的行数生成。整个指针称为行ID(RID)。
5、如果表没有聚集索引或索引视图上有聚集索引,则行定位器是行的聚集索引键。如果聚集索引不是唯一的索引,SQL Server将添加在内部生成的值(称为唯一值)以使所有重复键唯一。SQL Server通过使用存储在非聚集索引的叶行内的聚集索引键搜索聚集索引来检索数据行。
所以非聚集索引不会去改变或改善数据页的存储方式。它的B数结构只针对自己的索引页面。如果问题是堆的特性导致的,加一个非聚集索引不能带来根本的改善。
对于表格建立聚集索引不会增加表格的大小,而增加非聚集索引就会增加空间,但是如果在一个经常变化的表格建立聚集索引,会容易遇到页拆分(page split),所以尽力聚集索引会影响性能。基于这种考虑,很多设计者不愿意在SQL Server的表格中建立聚集索引,但是一张不建立索引的表格性能是非常差的,所以就增加了非聚集索引,以期望得到好的性能。
但是这这种方案是一种浪费空间、性能也不一定好的设计,为此SQL Server2005做了个比较,得出的结论是:有聚集索引的表格在Select 、Update、Delete这些动作性能有很大的提升,更重要的是在Insert这一项上,两者没有什么差别。并没有出现聚集索引影响Insert速度的现象。所以再次强烈建议,在一个大的表格上一定要建立一个聚集索引。
DELETE和TRUNCATE之间的区别
1、DELETE命令并不能完全释放表格或索引的数据结构以及他们申请的页面,尤其在堆表上。在SQL Server2005以后的版本中树结构的数据对于页释放做的更好一点,但是也不能完全释放。而TRUNCATE就能完全释放掉
2、所用事务日志空间较少,因为DELETE语句每次删除一行,并在事务日志中为所删除的每行记录一个项,TRUNCATE TABLE通过释放用于存储数据的数据页来删除数据,并且在事务日志中只记录这个动作,而不记录每一行。
3、使用的锁通常较少,当使用行锁执行Delete语句时,将锁定表中各行以便删除,TRUNCATE TABLE始终锁定表和页,而且速度更快
4、表中将毫无例外的不保留任何页,执行DELETE语句后,表仍会包含空页。例如,必须至少一个排他(LCK_M_X)表锁,才能释放堆中的空页。如果执行删除操作时没有使用表锁,表(堆)中将包含许多空页。对于索引、删除操作会留下一些空页,尽管这些页会通过后台清楚迅速释放。
TRUNCATE TABLE删除表中的行的所有行,但表结构以及其列、约束、索引等保持不变。若删除表定义以及其数据,使用Drop table语句。
所以对于像及时的删除数据,然后释放空间,可以采用的方法有:
1、在表格建立聚集索引
2、如果所有数据都不要了,要使用TRUNCATE TABLE而不是delete
Delete 不能完全释放空间,会不会造成空间泄露了呢?其实不用担心,虽然没有将这些页面释放掉,但是当表格插入新的数据的时候,这些页面会被重新使用的。所以这些页面并没有“泄露”掉,会留给SQL Server重用。
如果真的非要用DELETE语句,如果表有聚集索引,重建一下索引能释放掉出这些空间,但是如果没有,可以重建一张新表,把数据从旧表中倒过去,然后删除旧表,释放空间;也可以在这种表上新建聚集索引,这样有点折腾,当然有时候也没必要非要把空间释放出来,可以等着新数据插入时,直接利用就行。