《Microsoft Sql server 2008 Internals》读书笔记订阅地址:
http://www.cnblogs.com/downmoon/category/230397.html/rss
《Microsoft Sql server 2008 Internals》索引目录:
《Microsoft Sql server 2008 Internal》读书笔记--目录索引
■Heap Modification Internals
我们已经了解SQL Server如何在一个Heap中存储数据。现在我们了解一下SQL Server在一个Heap数据修改时内部实际如何操作。对于一个有聚集索引的表的数据修改,我们在第七章中讨论。一般来说,在一个表中应该有一个聚集索引。某些情况下你也许会觉得Heap是一个更好的选择,但请注意,希望做出彻底测试后再做决定。现在,我们只关注Heap中数据的修改。
■1、分配结构(Allocation Structure)
正如第三章讨论时,SQL Server为每个对象分配一个或多个IAM页,以跟踪哪些分区在每个文件属于该对象。如果表是一个堆(heap),使用IAMs是SQL Server找到属于表的所有分区唯一的途径。因为表的独立数据页不连接到双向链表,这不像在聚集索引的表中的处理方式。一个索引的每一个级所在的页被连接是因为数据被认为是一个聚集索引的叶级,则SQL Server会保持这个链接。但是,对于Heap,没有这样的链表相互连接的页。SQL Server确定哪些页属于一个表的唯一方式是检查数据表中的IAMs。
另外一个特殊的分配结构有特定条件下有用,当SQL Server执行数据修改时,即Page Free Space(PFS)结构。PFS结构跟踪每页有多少空闲空间,以便Heap中的Insert操作知道哪些空间可用于新数据的插入,而Update操作知道哪个位置的行可能被移动。前面第三章我们已经提到过,这些页在每个文件的8088页范围内包含一个字节为每个页,这比GAMs和SGAMs和IAMs要稀疏得多,后者每个分区包含一个Bit(位)。下图5-13显示了一个PFS页上字节结构。只有最后3位是用来表示页面全满,其他五个字节中的四个每个都有含义。
这些位的值解释如下:
◆Bit 1 该位表示页实际分配与否。例如,一标准分区可分配给一个对象,但在分区内的所有页可能未分配。哪些分配的分区内页已实际使用,SQL Server需要看PFS页的相关字节的这个位。
◆Bit 2 表示相关的页是否来自于一个混合分区
◆Bit 3 表示这个页是否一个IAM页,记住IAM页不会定位到文件中的已知位置
◆Bit 4 表示页是否含有克隆记录。我们知道,SQL Server使用一个后台的清理线程移除克隆记录。PFS的这个位有助于SQL Server找到需要被清理的页。
◆Bit 5-7 看作一个3位值(three-bit value),0-4展示页的充满程度:
❏ 0: The page is empty.
❏ 1: The page is 1–50 percent full.
❏ 2: The page is 51–80 percent full.
❏ 3: The page is 81–95 percent full.
❏ 4: The page is 96–100 percent full.
PFS页在每个数据文件的已知位置。一个文件的第二页(Page 1)是PFS页,此后是每8088页一个。
■2、插入行(Inserting Rows)
当插入一个新行到表时,SQL Server必须确定它要被放的位置。当表没有聚集索引,也就是说,当表是一个Heap时,一个新行可以插入表中任何可用的空间。我已经讨论了IAMs和PFS页如何跟踪文件中哪些分区已经属于一个表,这些分区的哪些页有可用空间。即使没有聚集索引,空间的管理是相当有效。如果没有页空间可用,SQL Server会尝试查找已经属于对象的现有分区的未分配页。如果不存在,则SQL Server必须分配一个全新的分区给这个表。第3章讨论过。
■3、删除行(Deleting Rows)
当你从表中删除一个行时,你必须考虑到数据页和索引页发生什么。请记住,数据实际上是一个聚集索引的叶级,从表中删除一个聚集索引行与从一个非聚集索引删除叶级行方式相同。从Heap中删除行是一个不同的管理方式,这从一个索引的节点页删除。
❏ 从Heap中删除行
SQL Server2008在一行被删除时并不自动重新组织页的空间。作为一个性能优化,压缩(compaction)直到一个页需要连续空间以存放一个新插入行时才会发生。下面这个例子,它删除了页的中间一行,然后使用DBCC PAGE 检查该页:
USE testdb2;
GO
CREATE TABLE smallrows
(
a int identity ,
b char ( 10 )
);
GO
INSERT INTO smallrows
VALUES ( ' row 1 ' );
INSERT INTO smallrows
VALUES ( ' row 2 ' );
INSERT INTO smallrows
VALUES ( ' row 3 ' );
INSERT INTO smallrows
VALUES ( ' row 4 ' );
INSERT INTO smallrows
VALUES ( ' row 5 ' );
GO
DBCC IND (testdb2, smallrows, - 1 );
DBCC TRACEON( 3604 );
GO
DBCC PAGE(testdb2, 1 , 475 , 1 );
页输出如下:
现在我们删除中间行(where a=3),即第三行
注意:在Heap中,页底部的行偏移数组表明,第三行(at slot 2)现在偏移量为0,(也就是说,没有行真正使用slot 2),而使用slot 3的行仍然使用删除前的行偏移。也就是Delete发生时,页上并没有数据被移动。如果你使用DBCC PAGE的printopt 1或3,行不会显示在页。如果使用Printopt 2,则你仍然看到'Row 3'的字节。它们并没有物理地从页中移除,但是行偏移数组中偏移为0显示空间未被使用,因而可以被新行使用。
除了页的空间未被回收外,Heap中的空页也经常不能被回收。即使从Heap中删除所有的行,SQL Server并不会标记空页(Empty page)为未分配,因而空间可以为其他对象可用。DMV sys.dm_db_Partition_stats仍然显示这部分空间属于Heap表。避免这个问题的方法是在删除被执行时,请求一个表锁。在第10章中会讨论。如果这个问题已经发生,将显示(比它实际拥有的)更多的属于表的空间,你可以生成一个表上的聚集索引经重新组织空间,并删除索引。
❏ 回收页
当最后一行从一个数据页被删除,整个页被释放。唯一的例外是,如果表是一个Heap,正如我前面讨论。(如果页是唯一一个表中的,它不被释放。表总是至少包含一个页,即使它是空的。)释放一个数据页会导致指向释放数据页的索引页中的行被删除。如果一个索引行被删除,索引页被释放(这再次,作为一个/插入删除更新策略的一部分而发生),只留下一个索引页的项。该项被移动到邻近页,然后空页被释放。
到目前为止,仅限于单个的行被删除,如果多行被删除,你必须意识到更多的冲突。
■4、更新行(Updating Rows)
SQL Server可以用几种不同的方式更新行:自动和无形地为特定操作选择速度最快的更新策略。在确定策略时,SQL Server评估受影响的行数,行如何被访问(通过一个扫描或索引检索,或通过某个索引),以及是否对索引键的变化会发生。更新可能发生在任何地方,只要在原来的行改变一个列值为新值,或由一个删除接着一个插入。此外,更新可以由查询处理器或由存储引擎管理。
❏ 移动行
如果行必须移动到表中的新位置会发生什么?在SQL Server 2008中,此情况可能是由不同的原因而不同。在第6章中,我们将浏览索引的结构,并看看在一个表的聚集索引列(或列)的值如何决定该行的位置。因此,如果聚集键值被更改,该行很可能要在表内移动。
如果它仍然具有相同的行定位器(换句话说,该行的聚集键保持不变),没有非聚集索引必须修改。如果表没有聚集索引(换句话说,如果这是一个堆),一行可能被移动,因为它不再适合原来的页。每当具有可变长度列的行更新为一个新的,更大的空间时发生这种情况,非聚集索引并非仅仅因为一行移动到一个不同的物理位置时而全部都必须更新,SQL Server在行不得不移动时在原位置保留了一个转发指针。
我们看一个转发指针的例子,
USE testdb2;
GO
DROP TABLE bigrows;
GO
CREATE TABLE bigrows
( a int IDENTITY ,
b varchar ( 1600 ),
c varchar ( 1600 ));
GO
INSERT INTO bigrows
VALUES ( REPLICATE ( ' a ' , 1600 ), '' );
INSERT INTO bigrows
VALUES ( REPLICATE ( ' b ' , 1600 ), '' );
INSERT INTO bigrows
VALUES ( REPLICATE ( ' c ' , 1600 ), '' );
INSERT INTO bigrows
VALUES ( REPLICATE ( ' d ' , 1600 ), '' );
INSERT INTO bigrows
VALUES ( REPLICATE ( ' e ' , 1600 ), '' );
GO
UPDATE bigrows
SET c = REPLICATE ( ' x ' , 1600 )
WHERE a = 3 ;
GO
DBCC IND (testdb2, bigrows, - 1 );
-- PageFID PagePID
-- 1 478
-- 1 477
-- 1 50248
DBCC TRACEON( 3604 );
GO
DBCC PAGE(testdb, 1 , 478 , 1 );
GO
部分结果如下:
对第一个字节的值4表示这只是一个转发存根。后面三个字节(00c448,十六进制的)是行被移动的页数字。
❏ 管理转发指针
转发指针允许你在一个Heap中修改数据而不用担心非聚集索引发生激烈的变化,如果一个已经转向的行需要再次被移动,原转向指针被更新以指向新的位置。另 外,如果转向行收缩到足够适应原页面,该记录可能移动到原来的位置,如果原页面还有空间。此时,转向指针将消除。
在SQL Server的未来一个版本中,可能包含一些机制,如在一个Heap中执行物理识别,以除去转向指针。注意转向指针仅存在于Heap中。重新组织表的 Alter Table选项并不影响Heap。可以在一个Heap中整理一个非聚集索引,而不是更新表自身。目前,当一个转向指向被创建时,它永远在那儿不会移动, 除了几个例外:
第一个例外是刚才提到的,行被收缩返回到原始位置。
第二个例外,是整个数据库发生收缩,当一个文件被 收缩时书签实际上被重排了。收缩进程实际上并不生成转发指针。而那些对于收缩进程移动的页面,所包含的任何转向的行或存根都有有效地“反转向”了。
第三个例外,如果转向行被删除或一个聚集索引被创建而不再是一个Heap。
如果要获取一个表中的转向记录的数量,你可以使用 sys.dm_db_index_physical_stats函数。请看MSDN:
http://msdn.microsoft.com/zh-cn/library/ms188917.aspx
❏ 原地更新
在 SQL Server 2008中,原地更新一个行时主要依据规则而不是意外。(updating a row in place is the rule rather than the exception),这意味着行准确在呆在相同的页面的相同位置,仅仅受影响的字节发生改变。此外,日志为每个原地更新操作包含了一个单独的记录,除非 表有一个更新触发器或被标记为用于复制。在这些情况下,更新将在原地发生,但日志不包含索引键列被更新时的Delete+Insert操作。
在某些情况下当一个行不能被原地更新时,“非原地更新”的代价是最小的,因为非聚集索引的存储方式和转向的使用。事实上,也可以对仍保留在原始页的行进行非 原地更新。如果一个Heap被更新,并且页面有足够的空间,或一个有聚集索引的表被更新(而同时聚集键没有变化的情况下),更新在原地发生。如果聚集键发 生改变但是行不需要移动时,也可以在原地更新。比如,如果在包含连续键值(Able,Becker和Charlie)的lastname列有一个聚集索 引,你可能更新Becker为Baker,此时,行还在原来的位置,即使聚集索引键改变,SQL会执行一个原地更新。另外一方面,如果你更新“Able” 为“Buchner”,更新不能在原地发生。
❏ 非原地更新
如果正在更新聚集键时更新不能在原地发生,更新以“Delete+Insert”方式实行,在某些情况下,你可能得到一个混合更新:一些行被原地更新,一些不在。如果你更新索引键,SQL建立一个需要实行(Delete+inert)的行列表。列表在内存中,在不大的情况下,如果有必要可能会写入Tempdb 中,这个列表按键值和操作(Delete+Insert)排序。如果键值被改变的索引是不惟一的,Delete+Insert步骤被应用到表。如果是惟一 的,则执行单个的Update操作。
注意:查询优化器决定特殊的Update方法是否合适是,这个内部优化,称为split/Sort/Collapse,第八章中讨论。
■小结:
表是在一般关系数据库的核心,在SQL Server中更是如此。在这一章中,我们研究了各种数据类型的内部存储问题,特别是固定和可变长度数据类型的比较。我们看SQL Server 2008提供用于存储可变长度数据的多个方案,包括太长而不能放在一个单一的数据页的数据,你看到使用可变长度的数据类型不总是好或不总是坏。 SQL Server提供域支持的用户定义的数据类型,它提供了IDENTITY属性,使一列产生自动序列化的数值。你也看到了数据如何被物理存储在数据页,我们查询一些提供来自潜在的或不可访问的系统表的信息的元数据视图。 SQL Server还提供了约束,这提供了有力的方式,以确保您的数据的逻辑完整性。