SQL Server 深入解析索引存储(下)

本文涉及的产品
云数据库 RDS SQL Server,独享型 2核4GB
简介: 原文:SQL Server 深入解析索引存储(下)  标签:SQL SERVER/MSSQL SERVER/数据库/DBA/索引体系结构/非聚集索引 概述   非聚集索引与聚集索引具有相同的 B 树结构,它们之间的显著差别在于以下两点: 基础表的数据行不按非聚集键的顺序排序和存储。
原文: SQL Server 深入解析索引存储(下)

 

标签:SQL SERVER/MSSQL SERVER/数据库/DBA/索引体系结构/非聚集索引

概述  

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

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

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

既可以使用聚集索引来为表或视图定义非聚集索引,也可以根据堆来定义非聚集索引。非聚集索引中的每个索引行都包含非聚集键值和行定位符。此定位符指向聚集索引或堆中包含该键值的数据行。

非聚集索引行中的行定位器或是指向行的指针,或是行的聚集索引键,如下所述:

  • 如果表是堆(意味着该表没有聚集索引),则行定位器是指向行的指针。该指针由文件标识符 (ID)、页码和页上的行数生成。整个指针称为行 ID (RID)。

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

对于索引使用的每个分区,非聚集索引在 index_id >0 的 sys.partitions 中都有对应的一行。默认情况下,一个非聚集索引有单个分区。如果一个非聚集索引有多个分区,则每个分区都有一个包含该特定分区的索引行的 B 树结构。例如,如果一个非聚集索引有四个分区,那么就有四个 B 树结构,每个分区中一个。

根据非聚集索引中数据类型的不同,每个非聚集索引结构会有一个或多个分配单元,在其中存储和管理特定分区的数据。每个非聚集索引至少有一个针对每个分区的 IN_ROW_DATA 分配单元(存储索引 B 树页)。如果非聚集索引包含大型对象 (LOB) 列,则还有一个针对每个分区的 LOB_DATA 分配单元。此外,如果非聚集索引包含的可变长度列超过 8,060 字节行大小限制,则还有一个针对每个分区的 ROW_OVERFLOW_DATA 分配单元。有关分配单元的详细信息,请参阅表组织和索引组织。B 树的页集合由 sys.system_internals_allocation_units 系统视图中的 root_page 指针定位。

 要很好的理解这篇文章的内容之前需要先阅读我前面写的上中部分的两篇文章:

SQL Server 深入解析索引存储(中)

SQL Server 深入解析索引存储(上)

正文

非聚集索引结构

 

生成测试数据

 

CREATE TABLE Torder
(ID INT IDENTITY(1,1) NOT NULL,
NAME CHAR(100) NOT NULL,
pro VARCHAR(8000) NULL,
Statu INT NOT NULL,
IDATE DATETIME DEFAULT(GETDATE())
)
GO
---插入1000条测试数据
DECLARE @ID INT=1
WHILE(@ID<=1000)
BEGIN
INSERT INTO Torder(NAME,pro,Statu)VALUES('商品'+CONVERT(CHAR(20),@ID),REPLICATE(1,8000),LEFT(@ID,1))
SET @ID=@ID+1 
END
GO
---创建非聚集索引
CREATE INDEX IX_Torder ON Torder
(NAME,Statu
)
INCLUDE(IDATE)


SELECT DISTINCT so.name, so.object_id,sp.index_id,internals.type_desc,internals.total_pages, internals.used_pages, internals.data_pages,first_iam_page,  first_page, root_page
FROM sys.objects so
INNER JOIN sys.partitions sp ON so.object_id = sp.object_id
INNER JOIN sys.allocation_units sa ON sa.container_id = sp.hobt_id
INNER JOIN sys.system_internals_allocation_units internals ON internals.container_id = sa.container_id
WHERE so.object_id = object_id('Torder') 

 

由于创建的表只有非聚集索引,所以整个表的页存储中有三部分数据:堆页面、溢出页面、索引页面;

堆中共有20个数据页和一个IAM页;

溢出单元有1001个页面包括一个IAM页;

索引中共有20个页其中18个数据页一个ROOT页和一个IAM页.

一个堆页对应多个溢出页,因为Pro有8000个字节所以一行占一页,而表的其它字段只有116个字节一个堆页可以存50条记录,所以并不是一个溢出页就唯一对应一个堆页

 分析页的存储信息

---开启跟踪标志
DBCC TRACEON(3604,2588)
--DBCC TRACEOFF(3604,2588)
---获取对象的数据页,结构:数据库、对象、显示
DBCC IND(Ixdata,Torder,-1)

上一章中已经讲过了堆页面和溢出页面,所以现在就讲非聚集索引页

 看过前面的文章应该一眼就能看出1281页是ROOT页,现在就分析1281页

分析非聚集索引根页

DBCC page(Ixdata,1,1281,3)

现在来分析行定位指针是怎样的:0x6801000001002F00

除去开头的16进制标示,剩下总共8个字节,从右往左其中行号2个字节,文件标示ID2个字节,剩下的4个字节就是页号了,所以

行号(002f)=47

文件页(0001)=1

页号(00000168)=360页

现在查看360页的信息

DBCC page(Ixdata,1,360,3)

47行的记录正好是“商品150”

 分析非聚集索引索引页

 

通过对比会发现索引页比根页多出了索引包含列值和键的哈希值,这个里面的keyhashvalue应该是NAME字段的值通过某种方法算出来的,应该不包括statu字段,这里只是猜测通过平时的查询你会产生这样的猜测,当你where条件只有statu的时候是不走索引查找的。

测试简单的查询

这里的'商品150'和'商品153'都是1280页中的记录,1280页是索引页,其中'商品150'是该页的第一条记录
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN  
SELECT [ID]
      ,[NAME]
      ,[pro]
      ,[Statu]
      ,[IDATE]
  FROM [Ixdata].[dbo].[Torder]
  WHERE NAME='商品153'

--COMMIT 

另开一个窗口
SELECT
[request_session_id],
c.[program_name],
DB_NAME(c.[dbid]) AS dbname,
[resource_type],
[request_status],
[request_mode],
[resource_description],OBJECT_NAME(p.[object_id]) AS objectname,
p.[index_id]
FROM sys.[dm_tran_locks] AS a LEFT JOIN sys.[partitions] AS p
ON a.[resource_associated_entity_id]=p.[hobt_id]
LEFT JOIN sys.[sysprocesses] AS c ON a.[request_session_id]=c.[spid]
 WHERE c.[dbid]=DB_ID('Ixdata') AND a.[request_session_id]=58  ----要查询申请锁的数据库
ORDER BY [request_session_id],[resource_type]

从上面的查询过程可以知道页面总共读取了三次(索引叶一次堆页一次溢出页一次)。

 

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
GO        
BEGIN TRAN  
 SELECT 
       [ID]
      ,[NAME]
      ,[pro]
      ,[Statu]
      ,[IDATE]
  FROM [Ixdata].[dbo].[Torder]
  WHERE NAME='商品150'                                                                                     
  

通过对比查询'商品150'和'商品153'可以看到如果查找的页面的第一条记录,它需要再读取该索引页的前一个页面,如果该索引页是第一页就无需再读除本身的其他索引页了,文章写到后面反过来思考才知道为什么非聚集索引还需要多查找一个页面了。因为非聚集索引是允许存在重复值所以才需要再往前查找,如果前面一个页查找不到则结束,如果前面一个页还没查完会再往前一个页进行查,当然查询商品153的时候就已经判断了前一条记录的键值是不一样的否则也是要再查询前一个页,这也是非聚集索引的一个特殊情况吧!

索引扫描

update Torder
set statu=100
where id=1

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
GO        
BEGIN TRAN  
 SELECT 
       [ID]
      ,[NAME]
      ,[pro]
      ,[Statu]
      ,[IDATE]
  FROM [Ixdata].[dbo].[Torder]
  WHERE [Statu]=100      

该查询总共扫描了18个索引页+1个堆页+1个溢出页.

创建聚集索引

 

ALTER TABLE dbo.Torder ADD CONSTRAINT
    PK_Torder PRIMARY KEY CLUSTERED 
    (
    ID
    ) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]


SELECT DISTINCT so.name, so.object_id,sp.index_id,internals.type_desc,internals.total_pages, internals.used_pages, internals.data_pages,first_iam_page,  first_page, root_page
FROM sys.objects so
INNER JOIN sys.partitions sp ON so.object_id = sp.object_id
INNER JOIN sys.allocation_units sa ON sa.container_id = sp.hobt_id
INNER JOIN sys.system_internals_allocation_units internals ON internals.container_id = sa.container_id
WHERE so.object_id = object_id('Torder') 

非聚集索引数据页比之前少了一页

由于现在的指针比之前的16进制指针要所占有的字节要少,所以只需要17个页面就可以存下。

分析索引页148

 

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN  
SELECT [ID]
      ,[NAME]
      ,[pro]
      ,[Statu]
      ,[IDATE]
  FROM [Ixdata].[dbo].[Torder]
  WHERE NAME='商品152'


在另一个窗口打开
SELECT
[request_session_id],
c.[program_name],
DB_NAME(c.[dbid]) AS dbname,
[resource_type],
[request_status],
[request_mode],
[resource_description],OBJECT_NAME(p.[object_id]) AS objectname,
p.[index_id]
FROM sys.[dm_tran_locks] AS a LEFT JOIN sys.[partitions] AS p
ON a.[resource_associated_entity_id]=p.[hobt_id]
LEFT JOIN sys.[sysprocesses] AS c ON a.[request_session_id]=c.[spid]
 WHERE c.[dbid]=DB_ID('Ixdata') AND a.[request_session_id]=58  ----要查询申请锁的数据库
ORDER BY [request_session_id],[resource_type]

从上面的逻辑读取和查询步骤可以证实前面的猜测,应该是隐藏了一张行定位表。

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

查看索引统计信息

DBCC SHOW_STATISTICS ("dbo.Torder", IX_Torder);

 

前面建的包含索引有这三种组合方式,所以组合索引的第二个字段不被用来单独做查找。

总结

  非聚集索引和聚集索引不一样,聚集索引索引页的键值指向数据页的具体行;而非聚集索引不存在数据页,非聚集索引的索引页中的记录行指向聚集索引或者堆的具体数据页的数据行然后来获取记录,如果堆或聚集索引还存在溢出的话,从堆或者聚集索引的数据记录还有指向溢出页面的指针。

花了四天时间终于把这个系列的写完了,重新去理解一遍把以前的一些不理解的知识点给弄明白了,还是收获很多。

 

如果文章对大家有帮助,希望大家能给个推荐,谢谢!!!

 

备注:

    作者:pursuer.chen

    博客:http://www.cnblogs.com/chenmh

本站点所有随笔都是原创,欢迎大家转载;但转载时必须注明文章来源,且在文章开头明显处给明链接,否则保留追究责任的权利。

《欢迎交流讨论》

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS&nbsp;SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/sqlserver
目录
相关文章
|
8天前
|
SQL 人工智能 算法
【SQL server】玩转SQL server数据库:第二章 关系数据库
【SQL server】玩转SQL server数据库:第二章 关系数据库
51 10
|
11天前
|
存储 缓存 NoSQL
深入解析Redis:一种快速、高效的键值存储系统
**Redis** 是一款高性能的键值存储系统,以其内存数据、高效数据结构、持久化机制和丰富的功能在现代应用中占有一席之地。支持字符串、哈希、列表、集合和有序集合等多种数据结构,适用于缓存、计数、分布式锁和消息队列等场景。安装Redis涉及下载、编译和配置`redis.conf`。基本操作包括键值对的设置与获取,以及哈希、列表、集合和有序集合的操作。高级特性涵盖发布/订阅、事务处理和Lua脚本。优化策略包括选择合适数据结构、配置缓存和使用Pipeline。注意安全、监控和备份策略,以确保系统稳定和数据安全。
53 1
|
28天前
|
SQL 存储 数据库
sql事务、视图和索引
sql事务、视图和索引
14 0
|
1月前
|
消息中间件 存储 数据库
RocketMQ 流存储解析:面向流场景的关键特性与典型案例
RocketMQ 流存储解析:面向流场景的关键特性与典型案例
88354 0
|
1月前
|
存储 关系型数据库 OLAP
TiDB适用场景解析:海量数据存储与高并发读写的利器
【2月更文挑战第25天】随着大数据时代的到来,海量数据存储和高并发读写成为众多企业面临的挑战。TiDB作为一种高性能、分布式的关系型数据库,以其独特的架构和强大的功能,在多个场景中展现出了卓越的性能。本文将详细探讨TiDB在海量数据存储、高并发读写等场景下的适用情况,分析其在不同业务场景中的优势与应用价值。
|
18天前
|
SQL
启动mysq异常The server quit without updating PID file [FAILED]sql/data/***.pi根本解决方案
启动mysq异常The server quit without updating PID file [FAILED]sql/data/***.pi根本解决方案
16 0
|
8天前
|
SQL 算法 数据库
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
66 6
|
21天前
|
存储 NoSQL 算法
【Redis技术进阶之路】「底层源码解析」揭秘高效存储模型与数据结构底层实现(字典)(二)
【Redis技术进阶之路】「底层源码解析」揭秘高效存储模型与数据结构底层实现(字典)
34 0
|
7天前
|
存储 人工智能 编译器
存算一体新兴力量:解析我国企业在存储创新、技术路径上的多元化探索
存算一体新兴力量:解析我国企业在存储创新、技术路径上的多元化探索
|
8天前
|
SQL 存储 数据挖掘
数据库数据恢复—RAID5上层Sql Server数据库数据恢复案例
服务器数据恢复环境: 一台安装windows server操作系统的服务器。一组由8块硬盘组建的RAID5,划分LUN供这台服务器使用。 在windows服务器内装有SqlServer数据库。存储空间LUN划分了两个逻辑分区。 服务器故障&初检: 由于未知原因,Sql Server数据库文件丢失,丢失数据涉及到3个库,表的数量有3000左右。数据库文件丢失原因还没有查清楚,也不能确定数据存储位置。 数据库文件丢失后服务器仍处于开机状态,所幸没有大量数据写入。 将raid5中所有磁盘编号后取出,经过硬件工程师检测,没有发现明显的硬件故障。以只读方式将所有磁盘进行扇区级的全盘镜像,镜像完成后将所
数据库数据恢复—RAID5上层Sql Server数据库数据恢复案例

推荐镜像

更多