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

原文: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

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

《欢迎交流讨论》

时间: 2024-09-20 05:29:28

SQL Server 深入解析索引存储(下)的相关文章

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

原文:SQL Server 深入解析索引存储(上) 标签:SQL SERVER/MSSQL SERVER/数据库/DBA/索引体系结构/堆 概述      最近要分享一个课件就重新把这块知识整理了一遍出来,篇幅有点长,想要理解的透彻还是要上机实践.       正文 聚集索引 --创建测试数据库 CREATE DATABASE Ixdata GO USE [Ixdata] GO ---创建测试表 CREATE TABLE Orders (ID INT PRIMARY KEY IDENTITY(1

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

原文:SQL Server 深入解析索引存储(中) 标签:SQL SERVER/MSSQL SERVER/数据库/DBA/索引体系结构/堆 概述      本篇文章是关于堆的存储结构.堆是不含聚集索引的表(所以只有非聚集索引的表也是堆).堆的 sys.partitions 中具有一行,对于堆使用的每个分区,都有 index_id = 0.默认情况下,一个堆有一个分区.当堆有多个分区时,每个分区有一个堆结构,其中包含该特定分区的数据.例如,如果一个堆有四个分区,则有四个堆结构:每个分区有一个堆结构

SQL Server on Linux列存储索引

问题引入 "鸟儿啊,听说微软至SQL Server 2012以来,推出了一种全新的基于列式存储的索引,你去研究看看SQL Server on Linux对这个功能的支持度如何,效率有多大的提升?".老鸟又迫不及待的开始给菜鸟分配任务. 分析问题 的确如老鸟所说,从SQL Server 2012开始推出了列存储索引,这个版本限制颇多,但是它对统计查询的效率提升又是实实在在的.所以,让我们来看看SQL Server on Linux列存储索引对统计查询的效率提升情况如何. 这里也顺便提一下

Sql Server优化之索引提示----我们为什么需要查询提示,Sql Server默认情况下优化策略选择的不足

原文:Sql Server优化之索引提示----我们为什么需要查询提示,Sql Server默认情况下优化策略选择的不足 环境: Sql Server2012 SP3企业版,Windows Server2008 标准版   问题由来: 最近在做DB优化的时候,发现一个存储过程有非常严重的性能问题, 由于整个SP整体逻辑是一个多表关联的复杂的查询,整体结构比较复杂的,通过的分析和尝试, 最后发现问题出在其中一个大表的查询上实现方式上, 因为这个大表上的意外的执行方式,导致其他表无法被驱动,其他表也

Sql server优化其索引的小技巧

关于索引的常识:影响到数据库性能的最大因素就是索引.由于该问题的复杂性,我只可能简单的谈谈这个问题,不过关于这方面的问题,目前有好几本不错的书籍可供你参阅.我在这里只讨论两种SQL Server索引,即clustered索引和nonclustered索引.当考察建立什么类型的索引时,你应当考虑数据类型和保存这些数据的column.同样,你也必须考虑数据库可能用到的查询类型以及使用的最为频繁的查询类型. 索引的类型 如果column保存了高度相关的数据,并且常常被顺序访问时,最好使用cluster

SQL Server 优化---为什么索引视图(物化视图)需要with(noexpand)强制查询提示

原文:SQL Server 优化---为什么索引视图(物化视图)需要with(noexpand)强制查询提示   本文出处:http://www.cnblogs.com/wy123/p/6694933.html    第一次通过索引视图优化SQL语句,以及遇到的一些问题,记录一下.   语句分析 最近开发递交过来一个查询统计的SQL,说是性能有问题,原本执行需要4-5秒钟,这个业务本身对性能要求又比较critical,期望是在1s之内在用尽各种办法之后(执行计划,统计信息,索引,改写SQL,临时

SQL Server基础之&lt;索引&gt;

原文:SQL Server基础之<索引>  索引用于快速找出在某个列中有某一特定值的行,不使用索引,数据库必须从第一条记录开始读完整个表,直到找出相关的行.表越大,查询数据所花费的时间越多,如果表中查询的列有一个索引,数据库能快速到达一个位置去搜索数据,本篇主要介绍与索引相关的内容,包括索引的含义和特点,索引的分类,索引的设计原则以及如何创建和删除索引. 一:索引的含义和特点  索引是一个单独的,存储在磁盘上的数据结构,它们包含则对数据表里所有记录的引用指针,使用索引用于快速找出在某个或多个列

SQL Server创建复合索引时,复合索引列顺序对查询的性能影响

原文:SQL Server创建复合索引时,复合索引列顺序对查询的性能影响    说说复合索引 写索引的博客太多了,一直不想动手写,有一下两个原因: 一是觉得有炒剩饭的嫌疑,有兄弟曾说:索引吗,只要在查询条件上建索引就行了,真的可以这么暴力吗? 二来觉得,索引是个非常大的话题,很难概括出所有的情况,你不整出点新意来,倒是有抄袭照搬的嫌疑 既然写了,就写一点稍微不一样的东西出来, 好了,废话打住,开搞   搭建测试环境: 创建一张表,模拟实际业务中的一个表,往里面填入数据, 时间字段上,相对按照时间

利用ASP嵌套JS+SQL Server打造两级连动下拉框(一)

js|server|下拉 利用ASP嵌套JS+SQL Server打造两级连动下拉框(1)   随着网络技术的迅速发展,越来越多的电子商务网站也应运而生.而在构建一个功能强大的电子商务网站往往少不了提供网上注册功能给客户自行注册这个模块,为了尽可能方便客户填写信息,我们经常要用到下拉框供客户选择,特别是两级连动下拉框最为常用. 再说,本人今日发现CSDN论坛上也经常有网友提问这个问题,就是,需要一个两级连动下拉框,第一个显示省份名称,第二个显示相应的城市名.为了解答这个问题,我特别写这篇文章,希