SQL Server2008存储结构之非聚集索引

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
简介:

SQL Server 2008连载之存储结构——非聚集索引

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

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

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

 

非聚集索引既可以建在堆表结构上也可以建在聚集索引表上;非聚集索引中的每个索引行都包含非聚集键值和行定位符。此定位符指向聚集索引或堆中包含该键值的数据行。

如果表是堆则行定位器是指向行的指针。该指针由文件标识符 (ID)、页码和页上的行数生成。整个指针称为行 ID (RID)

如果表包含有聚集索引,则行定位器是行的聚集索引键。如果聚集索引不是唯一的索引,SQL Server 将添加在内部生成的值(称为唯一值)以使所有重复键唯一。此四字节的值对于用户不可见。仅当需要使聚集键唯一以用于非聚集索引中时,才添加该值。SQL Server 通过使用存储在非聚集索引的叶行内的聚集索引键搜索聚集索引来检索数据行。

树的页集合由 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=THEN ''

       WHEN c.type=THEN '聚集'

       WHEN c.type=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首先从唯一非聚集索引的根节点开始查起,直到找到合适的索引叶子页面,然后根据该索引条目中的聚集索引键值,去聚集索引根节点中进行查找,一直找到正确的聚集叶子页面为止。

 

唯一聚集索引表上的非唯一非聚集索引


page(1:196)为该唯一聚集索引表上的非唯一非聚集索引的根节点,因为数据量少实际上只有一个索引节点,即大于等于G1的指向叶子页面page(1:174),小于G1的则指向叶子页面page(1:197)页面。之所以在196页面的索引条目中包括聚集索引的键值,是因为该索引条目不唯一造成的,从图中可以看出,Type2=G1的索引条目有2条,所以需要聚集索引键值的存在才能保证该索引条目的歧义和唯一性。

非唯一非聚集索引的叶子页面中则包括每个索引条目的索引值和其聚集键值;尽管非聚集索引不唯一,但因为聚集索引是唯一的,所以两者合起来能够保证该索引条目的唯一性,所以也不需要增加额外的辅助字段。

 

非唯一聚集索引表

CREATE TABLE testUnUniqueClusterIndex

(

   name   CHAR(200),

   type1  CHAR(900),

   type2  CHAR(900)

)

CREATE CLUSTERED INDEX idx_testUnUniqueClusterIndex_cluster ON testUnUniqueClusterIndex(name)

CREATE UNIQUE INDEX idx_testUnUniqueClusterIndex1 ON testUnUniqueClusterIndex(type1)

CREATE INDEX idx_testUnUniqueClusterIndex2 ON testUnUniqueClusterIndex(type2)

INSERT INTO testUnUniqueClusterIndex VALUES('A','A1','A2')

INSERT INTO testUnUniqueClusterIndex VALUES('B','B1','B2')

INSERT INTO testUnUniqueClusterIndex VALUES('B','C1','B2')

INSERT INTO testUnUniqueClusterIndex VALUES('D','D1','B2')

INSERT INTO testUnUniqueClusterIndex VALUES('E','E1','C2')

INSERT INTO testUnUniqueClusterIndex VALUES('F','F1','F1')

INSERT INTO testUnUniqueClusterIndex VALUES('F','G1','G1')

INSERT INTO testUnUniqueClusterIndex VALUES('F','H1','G1')

INSERT INTO testUnUniqueClusterIndex VALUES('F','I1','I1')

INSERT INTO testUnUniqueClusterIndex VALUES('J','J1','J1')

 

非唯一聚集索引表上的唯一非聚集索引


我们首先可以看到page(1:205)为聚集索引的根节点,该根节点包括2个索引值和三个索引指针指向相应的叶子页面,其中第二个索引值后面还带了一个identifer3的值,这是因为该聚集索引不唯一,所以必须增加一个唯一标识才能定位到相应的下级节点中。而聚集索引的叶子页面则按照聚集索引的排序规则进行存储;注意在叶子节点中重复键值的聚集索引的尾部也带有相应的唯一标识值。

page(1:207)为该非唯一聚集索引表上的唯一非聚集索引的根节点,因为数据量少实际上只有一个索引节点,即大于等于H1的指向叶子页面page(1:201),小于H1的则指向叶子页面page(1:208)页面。

注意非唯一非聚集索引的叶子页面中则包括每个索引条目的索引值和其聚集键值,同时在重复聚集键值的后面增加了相应的唯一标识,因此三者结合起来就能够保证该索引条目的唯一性了。

 

非唯一聚集索引表上的非唯一非聚集索引


page(1:209)为该非唯一聚集索引表上的非唯一非聚集索引的根节点,因为数据量少实际上只有一个索引节点,即大于等于G1的指向叶子页面page(1:210),小于G1的则指向叶子页面page(1:203)页面。

注意非唯一非聚集索引的叶子页面中则包括每个索引条目的索引值和其聚集键值,同时在重复聚集键值的后面增加了相应的唯一标识,虽然非唯一聚集索引的索引键值是重复的,但因为聚集索引键值和唯一标识已经是唯一的,所以三者结合起来依然能够保证该索引条目的唯一性。







本文转自baoqiangwang51CTO博客,原文链接:http://blog.51cto.com/baoqiangwang/425459,如需转载请自行联系原作者

相关文章
|
4月前
|
存储 SQL 关系型数据库
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
|
7月前
|
SQL 存储 关系型数据库
SQL优化策略与实践:组合索引与最左前缀原则详解
本文介绍了SQL优化的多种方式,包括优化查询语句(避免使用SELECT *、减少数据处理量)、使用索引(创建合适索引类型)、查询缓存、优化表结构、使用存储过程和触发器、批量处理以及分析和监控数据库性能。同时,文章详细讲解了组合索引的概念及其最左前缀原则,即MySQL从索引的最左列开始匹配条件,若跳过最左列,则索引失效。通过示例代码,展示了如何在实际场景中应用这些优化策略,以提高数据库查询效率和系统响应速度。
280 10
|
8月前
|
SQL 索引
【YashanDB知识库】字段加上索引后,SQL查询不到结果
【YashanDB知识库】字段加上索引后,SQL查询不到结果
|
9月前
|
SQL 关系型数据库 OLAP
云原生数据仓库AnalyticDB PostgreSQL同一个SQL可以实现向量索引、全文索引GIN、普通索引BTREE混合查询,简化业务实现逻辑、提升查询性能
本文档介绍了如何在AnalyticDB for PostgreSQL中创建表、向量索引及混合检索的实现步骤。主要内容包括:创建`articles`表并设置向量存储格式,创建ANN向量索引,为表增加`username`和`time`列,建立BTREE索引和GIN全文检索索引,并展示了查询结果。参考文档提供了详细的SQL语句和配置说明。
275 2
|
SQL Oracle 关系型数据库
SQL优化-使用联合索引和函数索引
在一次例行巡检中,发现一条使用 `to_char` 函数将日期转换为字符串的 SQL 语句 CPU 利用率很高。为了优化该语句,首先分析了 where 条件中各列的选择性,并创建了不同类型的索引,包括普通索引、函数索引和虚拟列索引。通过对比不同索引的执行计划,最终确定了使用复合索引(包含函数表达式)能够显著降低查询成本,提高执行效率。
206 3
|
SQL 关系型数据库 MySQL
如何确认SQL用了索引:详细技巧与方法
在数据库管理中,索引是提高SQL查询性能的重要手段
2256 5
|
SQL 存储 关系型数据库
SQL默认索引是什么:深入解析与技巧
在SQL数据库中,索引是一种用于提高查询性能的重要数据结构
|
SQL 存储 关系型数据库
SQL默认索引是什么
在SQL数据库中,索引是一种用于提高查询性能的数据结构
|
SQL 关系型数据库 MySQL
如何确认SQL用了索引
在数据库管理和优化过程中,确认SQL查询是否使用了索引是一个至关重要的步骤
|
SQL 关系型数据库 MySQL
如何确认SQL查询是否使用了索引:详细步骤与技巧
在数据库管理和优化中,确认SQL查询是否有效利用了索引是提升性能的关键步骤
1327 0