概括:
这里说的聚集索引是聚簇索引
聚簇索引即建立在聚簇上的索引,创建聚簇索引时,需要对已有表数据重新进行排序(若表中已有数据),即删除原始的表数据后再将排序结果按物理顺序插回,故聚簇索引建立完毕后,建立聚簇索引的列中的数据已经全部按序排列。
一个表中只能包含一个聚簇索引,但该索引可以包含多个列。
B-树索引中,聚簇索引的叶层就是数据页。
非聚簇索引类似书本索引,索引与数据存放在不同的物理区域,建立非聚簇索引时数据本身不进行排序。一个表中科含多个非聚簇索引。
B-树索引中,非聚簇索引的叶层仍是索引页,其以指针指向数据页实际存储位置。
详解,列子:
非聚集索引与聚集索引具有相同的 B 树结构,它们之间的显著差别在于以下两点:
基础表的数据行不按非聚集键的顺序排序和存储。
非聚集索引的叶层是由索引页而不是由数据页组成。
非聚集索引既可以建在堆表结构上也可以建在聚集索引表上;非聚集索引中的每个索引行都包含非聚集键值和行定位符。此定位符指向聚集索引或堆中包含该键值的数据行。
如果表是堆则行定位器是指向行的指针。该指针由文件标识符 (ID) 、页码和页上的行数生成。整个指针称为行 ID (RID) 。
如果表包含有聚集索引,则行定位器是行的聚集索引键。如果聚集索引不是唯一的索引, SQL Server 将添加在内部生成的值(称为唯一值)以使所有重复键唯一。此四字节的值对于用户不可见。仅当需要使聚集键唯一以用于非聚集索引中时,才添加该值。 SQL Server 通过使用存储在非聚集索引的叶行内的聚集索引键搜索聚集索引来检索数据行。
B 树的页集合由 sys.system_internals_allocation_units 系统视图中的 root_page 指针定位。
堆表
-- 创建一张堆表
CREATE TABLE testHeapIndex
(
name CHAR ( 200),
type1 CHAR ( 900),
type2 CHAR ( 900)
)
-- 分别创建一个唯一索引和一个非唯一索引
CREATE UNIQUE INDEX idx_testHeapIndex1 ON testHeapIndex ( type1 )
CREATE INDEX idx_testHeapIndex2 ON testHeapIndex ( type2 )
-- 插入测试数据
INSERT INTO testHeapIndex VALUES ( A , A1 , A2 )
INSERT INTO testHeapIndex VALUES ( B , B1 , B2 )
INSERT INTO testHeapIndex VALUES ( C , C1 , B2 )
INSERT INTO testHeapIndex VALUES ( D , D1 , B2 )
INSERT INTO testHeapIndex VALUES ( E , E1 , C2 )
INSERT INTO testHeapIndex VALUES ( F , F1 , F1 )
INSERT INTO testHeapIndex VALUES ( G , G1 , G1 )
INSERT INTO testHeapIndex VALUES ( H , H1 , G1 )
INSERT INTO testHeapIndex VALUES ( I , I1 , G1 )
INSERT INTO testHeapIndex VALUES ( J , J1 , J1 )
-- 获取该表的相应页面信息
SELECT A . NAME TABLE_NAME , B . NAME INDEX_NAME , B . INDEX_ID
FROM SYS . OBJECTS A , SYS . INDEXES B
WHERE A . OBJECT_ID = B . OBJECT_ID AND A . NAME = testHeapIndex
TRUNCATE TABLE tablepage ;
INSERT INTO tablepage EXEC ( DBCC IND(testdb,testHeapIndex,0) );
INSERT INTO tablepage EXEC ( DBCC IND(testdb,testHeapIndex,2) );
INSERT INTO tablepage EXEC ( DBCC IND(testdb,testHeapIndex,3) );
SELECT
b . name table_name ,
CASE WHEN c . type = 0 THEN 堆
WHEN c . type = 1 THEN 聚集
WHEN c . type = 2 THEN 非聚集
ELSE 其他
END index_type ,
c . name index_name ,
PagePID , IAMPID , ObjectID , IndexID , Pagetype , IndexLevel ,
NextPagePID , PrevPagePID
FROM tablepage a , sys . objects b , sys . indexes c
WHERE A . ObjectID = b . object_id
AND A . ObjectID = c . object_id
AND a . IndexID = c . index_id
-- 获取该表的 root 页面地址,聚集索引的根节点必须通过下面脚本才能找到
SELECT c . name , a . type_desc , d . name ,
total_pages , used_pages , data_pages ,
testdb . dbo . f_get_page ( first_page ) first_page_address ,
testdb . dbo . f_get_page ( root_page ) root_address ,
testdb . dbo . f_get_page ( first_iam_page ) IAM_address
FROM sys . system_internals_allocation_units a , sys . partitions b , sys . objects c , sys . indexes d
WHERE a . container_id = b . partition_id and b . object_id = c . object_id
AND d . object_id = b . object_id AND d . index_id = b . index_id
AND c . name in ( testHeapIndex )
-- 下面各个例子获取相关页面和 root 页面的脚本基本相同,不再重复
堆表上的唯一非聚集索引
首先堆表是由若干叶子页面组成的,相互之间没有链接关系,完全靠 IAM 页面进行管理和维护。
我们可以看到 page(1:90) 为该唯一非聚集索引的根节点,因为数据量少实际上只有一个索引节点,即大于等于 I1 的指向叶子页面 page(1:93) ,小于 I1 的则指向叶子页面 page(1:55) 页面。
叶子页面中则包括每个索引条目的索引值和该索引指向的记录的位置(文件号 + 页面 + 插槽号),因为这是唯一索引所以不需要额外的字段来记录重复值。
堆表上的非唯一非聚集索引
我们可以看到 page(1:94) 为该非唯一非聚集索引的根节点,因为数据量少实际上只有一个索引节点,即大于等于 G1 的指向叶子页面 page(1:78) ,小于 G1 的则指向叶子页面 page(1:109) 页面。
叶子页面中则包括每个索引条目的索引值和该索引指向的记录的位置(文件号 + 页面 + 插槽号);存储结构与堆表上的唯一非聚集索引完全一致,我们可以看出在堆表中尽管索引值不唯一,但通过索引值 + 指针 ( 文件号 + 页面 + 插槽号 ) 的方式,也能够保证该索引条目的唯一性,所以不需要增加额外的辅助字段。
唯一聚集索引表
CREATE TABLE testUniqueClusterIndex
(
name CHAR ( 200),
type1 CHAR ( 900),
type2 CHAR ( 900)
)
CREATE UNIQUE CLUSTERED INDEX idx_testUniqueClusterIndex_cluster ON testUniqueClusterIndex ( name )
CREATE UNIQUE INDEX idx_testUniqueClusterIndex1 ON testUniqueClusterIndex ( type1 )
CREATE INDEX idx_testUniqueClusterIndex2 ON testUniqueClusterIndex ( type2 )
INSERT INTO testUniqueClusterIndex VALUES ( A , A1 , A2 )
INSERT INTO testUniqueClusterIndex VALUES ( B , B1 , B2 )
INSERT INTO testUniqueClusterIndex VALUES ( C , C1 , B2 )
INSERT INTO testUniqueClusterIndex VALUES ( D , D1 , B2 )
INSERT INTO testUniqueClusterIndex VALUES ( E , E1 , C2 )
INSERT INTO testUniqueClusterIndex VALUES ( F , F1 , F1 )
INSERT INTO testUniqueClusterIndex VALUES ( G , G1 , G1 )
INSERT INTO testUniqueClusterIndex VALUES ( H , H1 , G1 )
INSERT INTO testUniqueClusterIndex VALUES ( I , I1 , G1 )
INSERT INTO testUniqueClusterIndex VALUES ( J , J1 , J1 )
唯一聚集索引表上的唯一非聚集索引
我们首先可以看到 page(1:192) 为聚集索引的根节点,该根节点包括 2 个索引值和三个索引指针指向相应的叶子页面,而聚集索引的叶子页面则按照聚集索引的排序规则进行存储。
page(1:194) 为该唯一聚集索引表上的唯一非聚集索引的根节点,因为数据量少实际上只有一个索引节点,即大于等于 H1 的指向叶子页面 page(1:195) ,小于 H1 的则指向叶子页面 page(1:151) 页面。
唯一非聚集索引的叶子页面中则包括每个索引条目的索引值和其聚集键值;因为都是唯一的,所以两者合起来能够保证该索引条目的唯一性,所以不需要增加额外的辅助字段。
那么当对唯一非聚集索引的字段进行查找的时候是如何处理的呢? SQL Server 首先从唯一非聚集索引的根节点开始查起,直到找到合适的索引叶子页面,然后根据该索引条目中的聚集索引键值,去聚