您的位置:首页 > 数据库

数据库引擎-存储管理2

2009-06-16 13:09 225 查看
上一篇文章描述了数据库是如何快速而精确的将数据保存到文件中的,同时也提到了分配单元和分配单元索引表。

现在,我们来看看数据库对象是如何将它们的内容写在这些页面上的。并非所有的数据库对象都需要写入数据文件,像存储过程,函数,视图,表的结构等数据实际上保存在Master数据库中,实际保存在数据文件中的是表,索引或者索引试图。

有必要解释下索引视图,它就是通常所说的物化视图。普通视图实际只是一个SQL语句,当基于视图的SQL运行时,它会被扩展开,数据库引擎执行扩展后的SQL。如果在View上创建了聚集索引后,SQL会执行一次,并将结果就会被保存在索引页上,当基于这种View的SQL执行时,它不再被展开,而是直接获取执行结果。

对于一个Table或Index来说,顺便说一下索引视图实际上是一个索引,它按照如下方式将它的数据保存在页面上:



大家知道,每个Table是可以分成多个分区的,而每个Index自己就是一个分区。

每个分区包含3种Page: in-row,row-overflow,lob,如前所述,正常的不太长的行放在in-row类型的页面里,如果一个行太长了,行中太长的那个列就会被移出来放在Row-OverFlow的页面里,而在原行的位置填入一个指针,而lob页面是存放大对象的地方。

每个分配单元的Page是相同类型的。因此如果一个表有一个大对象列,image比如,那么它至少有两个分配单元。如果它还有一个特别长的行,ok,它还需要有第三个分配单元。当然,每个非配单元最多存放64000个extent,而且必须是同一个文件里的。这样你应该可以计算出一个table或index会对应多少个分配单元。当然你也可一通过下面的方式精确的知道。

下面的SQL查询表Test上所有Index的分配单元。

SELECT o.name AS table_name,p.index_id, i.name AS index_name , au.type_desc AS allocation_type, au.data_pages, partition_number
FROM sys.allocation_units AS au
JOIN sys.partitions AS p ON au.container_id = p.partition_id
JOIN sys.objects AS o ON p.object_id = o.object_id
JOIN sys.indexes AS i ON p.index_id = i.index_id AND i.object_id = p.object_id
WHERE o.name = N'Test'
ORDER BY o.name, p.index_id;

稍微解释下:

sys.allocation_units 存放所有的分配单元,可惜我们没办法把它和表或index直接关联起来;

好在我们有 sys.partitions, 如前所述每个分配单元是属于某个Partition的,

然后呢,某个Partition是属于某个Objects的,这时候我们就可以看到熟悉的Object_id了。

下面的你自己可以明白了。

接下来再进一步介绍一下索引和table的关系。

如果在表上没有建聚集索引,那么这个表被称为堆。和其他表一样,堆的数据也保存在页面上,然后这些页面放在Extent里,连续的extent保存在IAM页面中,同属于一个对象的IAM串联起来,最后第一个IAM页面保存在数据库的元数据里。新的数据到来时,在这一连串页面的尾部分配新的页面。

如果表上创建了聚集索引,在堆的基础上,table的所有页面的指针会组成一个B-树。新的记录插入时,先通过B-树找到插入的页面,而不是简单在后面。

非聚集索引也是存放指向页面的指针,不同的是它的叶节点并不是真正的数据页。

为什么有这种区别呢?设想Table1 有两个列ID和Name,并有基于ID的聚集索引,和基于Name的非聚集索引。那么ID在范围1到100的所有行必然是在连续的页面上,因此没有必要保存指向具体记录的指针。而所有Name是S开头的行却不一定在连续的页面中,虽然它们对应索引键值是连续的,这个时候就需要保存索引键值对应的行所在位置,Page号,Page中的偏移地址。

既然提到索引键,不妨再细看一步。

每个索引键包含以下部分:索引列的值,指向下一级别索引页的指针及其所在文件ID。让我们变身数据库引擎来通过索引树查找某个行。
第一步: 我们收到请求,需要找到ID为985的行;
第二步: 找到建立在ID的索引树,找到根节点;
第三步: 根节点告诉我们,985在它的第二个下级页面里;
第四部: 第二个下级页面告诉我们,985在它下级的第154个页面里;
第五步: 我们在154页的第78行找到了985,它指向Page 287,12行;
于是我们找到它。

在整个过程中,我们装载了5个索引页和1个数据页。看起来并不简单。换个角度,如果不用索引,我们怎么做呢?

让我们计算下上述的index对应多少的数据页面吧。为方便讨论,假设每个索引键占用20 byte,每页最多400个记录。假设根页面只有两个页,这是最小情况,第二级最少200个记录。第三级最少200*200*2=80K。那么80K行的数据占用多少页呢,假设每行100字节,那么1000页。如果顺序查找,平均需要找500页。而上述情况是基于每个索引页最小存量的假设。可见索引确实减少了数据页面的装载次数,而数据页面的装载是花时间最多的地方。

当插入或修改数据的时候会导致索引的修改。如果一个索引页的发生分裂,它的父节点上会插入新的子节点。这是一个递归的过程,直到如果某个父节点没有变满,或者根节点页分裂了。根节点的分裂会导致索引树层次的增加,那就意味这所有查询要多一次加载页面,性能就会受到较大的影响。

当对表进行修改时,关于索引的维护有两种方式:修改表或者修改索引。修改表适用于少量的修改,即每次插入,就相应的修改各个索引。而后一种方式则是当所有的修改完成后,重修生成索引。查询优化器会根据修改的多少自动选择索引修改的方式。而我们可以根据执行计划了解它实际使用哪种修改方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐