SQL Server 索引(一)数据结构和存储结构

  本文关注以下方面(本文所有的讨论基于SQL Server数据库):

  • 索引的分类;
  • 索引的结构;
  • 索引的存储

一、索引定义分类

  让我们先来回答几个问题:  

  • 什么是索引?

    • 索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。
    • 举个例子,索引就像我们查字典时用的按拼音或笔画或偏旁部首
  • 有哪些索引?
    • 从物理结构上可分为两种:聚集索引和非聚集索引 (此外还有空间索引、筛选索引、XML索引)
  • 索引说明 (http://msdn.microsoft.com/zh-cn/library/ms190197(v=sql.105).aspx)
    • 每张表上最大的聚集索引数为1;
    • 每张表上最大的非聚集索引数为999;
    • 每个索引最多能包含的键列数为16;
    • 索引键记录大小最多为900字节

二、索引数据结构

  在SQL Server数据库中,索引的存储是以B+树(注意和二叉树的区别)结构来存储的,又称索引树,其节点类型为如下两种:

  • 索引节点;
  • 叶子节点

  索引节点按照层级关系,有时又可以分为根节点和中间节点,其本质是一样的,都只包含下一层节点的入口值和入口指针;

  叶子节点就不同了,它包含数据,这个数据可能是表中真实的数据行,也有可能是索引列值和行书签,前者对应于聚集索引,后者对应于非聚集索引。

三、索引存储结构

  在正式讨论索引的存储结构之前,我们有必要先来了解一下SQL Server数据库的存储结构。

  SQL Server数据库存储(结构)的最小单位是页,大小为8K,共8 * 1024 = 8192Byte,不论是数据页还是索引页都是以此方式存放。实际上对于SQL Server数据库而言,其页(Page)类型有很多种,大概有如下十几种(http://www.sqlnotes.info/2011/10/31/page-type/):

  • Type 1 – Data page.

    • Data records in heap
    • Clustered index leaf-level
    • Location can be random
  • Type 2 – Index page
    • Non-clustered index
    • Non-leave-level clustered index
    • Location can be random
  • Type 3 – Text Mixed Page

    • Small LOB value(s), multiple types and rows.
    • Location can be random
  • Type 4 – Text Page
    • LOB value from a single column value
    • Location can be random
  • Type 7 – Sort Page
    • Temporary page for sort operation.
    • Usually tempdb, but can be in user database for online operations.
    • Location can be random
  • Type 8 – GAM Page
    • Global Allocation Map, track allocation of extents.
    • One bit for each extent, if the bit is 1, means the extent is free, otherwise means the extent is allocated (not necessary full).
    • The first GAM page in each file is page 2
  • Type 9 – SGAM Page
    • Shared Global Allocation Map, track allocation of shared extents
    • One bit for each extent, if the bit is 1, means the extent is allocated but has free space, otherwise means the extent is full
    • The first SGAM page in each file is page 3
  • Type 10 – IAM Page
    • Index Allocation Map. Extent allocation in a GAM interval for an index or heap table.
    • Location can be random.
  • Type 11 – PFS Page
    • Page Free Space. Byte map, keeps track of free space of pages
    • The first PFS is page 1 in each file.
  • Type 13 – Boot Page
    • Information about the page
    • Only page 9 in file 1.
  • Type 14 – Server Configuration Page (It may not be the official name)
    • Part of information returned from sp_configure.
    • It only exists in master database, file 1, page 10
    • SQL Server 2008 Only
  • Type 15 – File Header Page
    • Information about the file.
    • It’s always page 0 every data page.
  • Type 16 – Differential Changed map
    • Extents in GAM interval have changed since last full or differential backup
    • The first Differential Changed Page is page 6 in each file
  • Type 17 – Bulk Change Map
    • Extents in GAM interval modified by bulk operations since last backup
    • The first Bulk Change Map page is page 7 in each file

  表中所有数据页的存放在磁盘上又有两种组织方式:

  • 堆表;
  • 索引组织表

  如果表中所有数据页是以一种页间无序、随机存储的方式,则称这样的表为堆表;

  否则如果表中数据页间按某种方式(如表中某个字段)有序地存储与磁盘上,则称为索引组织表。

四、聚集索引

  下面我们将深入研究一下数据库中的索引到底是如何存储的以及如何被使用的。

  为了测试验证等,我们在数据库PCT上新建一张测试表Employee,有两个字段,其中EmployeeId为主键

USE PCT

CREATE TABLE Employee (
    EmployeeId    NVARCHAR(32) NOT NULL PRIMARY KEY,
    EmployeeName NVARCHAR(40) NOT NULL,
);

  插入10W笔测试数据

SET NOCOUNT ON
declare @i int
set @i=1
while @i<=100000
begin
INSERT INTO Employee VALUES(replace(newid(), ‘-‘, ‘‘), ‘Employee_‘ + CONVERT(varchar, @i) );
set @i = @i+1
end

  通过DBCC IND命令来查看索引的情况

DBCC IND ([PCT], [DBO.Employee], -1)

  结果如下

  红色标记说明:

  • PagePID:页编号
  • PageType:页类型,第三部分已经说明,1为数据页(此处为聚集索引的叶节点),2为索引页(此处为聚集索引的根或中间节点),10为IAM页
  • IndexLevel:标明页子在B树中的位置,0为叶节点,1为中间节点,2为根节点
  • NextPagePID和PrevPageID:用于标识此页的前一页和后一页,这表明每一层是一个双向链表,为0则表明没有相应的页

  为了方便查找,我们也可以把上述结果存入表中,为此建表

CREATE TABLE DBCCIndResult (
    PageFID NVARCHAR(200),
    PagePID NVARCHAR(200),
    IAMFID NVARCHAR(200),
    IAMPID NVARCHAR(200),
    ObjectID NVARCHAR(200),
    IndexID NVARCHAR(200),
    PartitionNumber NVARCHAR(200),
    PartitionID NVARCHAR(200),
    iam_chain_type NVARCHAR(200),
    PageType NVARCHAR(200),
    IndexLevel NVARCHAR(200),
    NextPageFID NVARCHAR(200),
    NextPagePID NVARCHAR(200),
    PrevPageFID NVARCHAR(200),
    PrevPagePID NVARCHAR(200)
)

  插入数据

INSERT INTO DBCCIndResult EXEC (‘DBCC IND(PCT,Employee,-1) ‘)

  我们可以通过下面的语句来查看索引的深度

select * from sys.dm_db_index_physical_stats(db_id(‘PCT‘),object_id(‘Employee‘),null,null,null)

  我们看到索引的深度为3,上面的IndexLevel分别有0,1,2也验证了这一点。page_count为1944,但是我们上面查到的结果却是1977,这是因为这里的语句没有计算Index为1和2的页(注意index_level列)

  接下来我们看看B树中各种节点存储的到底是什么?

找到根节点283

select * from DBCCIndResult where pagetype = 2 and indexLevel = 2

查看页里的数据

DBCC TRACEON (3604);
GO
DBCC PAGE (PCT, 1, 283, 3);
GO

  从上图,可以看出,此根节点共有31个儿子(中间节点),而且还存有主键值EmployeeId,那么这31个主键值是哪些记录的主键值呢?我们继续深入

以中间节点1863为例

DBCC TRACEON (3604);
GO
DBCC PAGE (PCT, 1, 1863, 3);
GO

  这和根节点很类似,标明了包含下一层的节点(共65个)和主键值,继续深入

以叶节点807为例

DBCC TRACEON (3604);
GO
DBCC PAGE (PCT, 1, 807, 3);
GO

由于结果太多,我就不把所有的截图都发出来了,但是从上面我们已经看到了一些重要的东西

  首先PAGE:(1:807)表明这是一个叶节点,同时也是一个数据页,因为它存放了表里所有字段的数据(EmployeeId和EmployeeName),换句话说这儿的叶节点就是表Employee在数据库中的存储数据页,也就是说聚集索引的叶节点其实就是表的数据存储页

  其次我们看标红的EmployeeId,它就是我们在之前根节点283和中间节点1863存储的主键值,而且它是位于数据存储页的第一个数据

  至此我们总结如下:

  • 聚集索引的根节点和中间节点是索引页,都只包含下一层的入口指针和入口值(位于存储位置的第一个主键值);
  • 聚集索引的叶节点就是数据页。

  为了更方便地查看叶节点的数据,我们将其存入表中

DBCC PAGE(PCT,1,807, 3) WITH TABLERESULTS

  这种方式是以表的方式展示

  但是这种方式也不便查找,我们索性新建表

CREATE TABLE DBCCPageResult(
    ParentObject NVARCHAR(200),
    Object NVARCHAR(200),
    Field NVARCHAR(200),
    Value NVARCHAR(200)
)

插入数据

INSERT INTO DBCCPageResult EXEC (‘DBCC PAGE(PCT,1,807, 3) WITH TABLERESULTS‘)

查看EmployeeId数据

select * from DBCCPageResult where Field = ‘EmployeeId‘

  注意Value,是按顺序排好的,这也是聚集索引的意义了 - 把数据按顺序存储.

至此我们又可以得出:

  • 聚集索引就是把数据按主键顺序存储;
  • 因为一张表中的数据只能有一个物理顺序,所以一张表只能有一个主键/聚集索引。

五、非聚集索引

  在表Employee字段EmployeeName建立非聚集索引

CREATE NONCLUSTERED INDEX IX_TBL_Employee_EmployeeName
     ON Employee(EmployeeName)
          WITH FILLFACTOR= 30
GO

  再增加一列PhoneNumber以备测试之用

ALTER TABLE Employee ADD PhoneNumber INT NULL

  先清空表DBCCIndResult数据

TRUNCATE TABLE DBCCIndResult

  再重新插入数据

INSERT INTO DBCCIndResult EXEC (‘DBCC IND(PCT,Employee,-1) ‘)

  中间红线上面的是之前聚集索引的数据,下面是非聚集索引的数据

  找到非聚集索引树的根节点,为2482

select * from DBCCIndResult where IAMPID = 2320 and indexlevel = 2

  查看根节点2482数据

DBCC TRACEON (3604);
GO
DBCC PAGE (PCT, 1, 2482, 3);
GO

  上图说明根节点包含下一层中间节点的页号,非聚集索引的键值EmployeeName以及聚集索引的键值EmployeeId

  继续查看中间节点2481情况

DBCC TRACEON (3604);
GO
DBCC PAGE (PCT, 1, 2481, 3);
GO

  中间页节点(Level为1)同样包含了下一层(叶节点)的页号以及聚集、非聚集键值

  继续查看叶节点2683情况

DBCC TRACEON (3604);
GO
DBCC PAGE (PCT, 1, 2683, 3);
GO

  此处叶节点包含聚集、非聚集索引键值以及一个KeyHasValue

  至此,我们总结如下:

  • 非聚集索引的根节点和中间节点是索引页,都只含下一层级的入口指针和入口值(位于存储位置的第一个键值);
  • 非聚集索引的叶节点也是索引页,也存储有聚集索引和非聚集索引的键值;
  • 非聚集索引中的每个索引行(不论是根节点、中间节点还是叶节点)都包含非聚集键值和行定位符(本例为聚集索引键值),此定位符指向聚集索引或堆(没有聚集索引的表)中包含该键值的数据行。

  非聚集索引行中的行定位器可以是指向行的指针,也可以是行的聚集索引键,具体根据如下情况而定:

  • 如果表是堆(意味着该表没有聚集索引),则行定位器是指向行的指针。该指针由文件标识符 (ID)、页码和页上的行数生成。整个指针称为行 ID (RID);
  • 如果表有聚集索引或索引视图上有聚集索引,则行定位器是行的聚集索引键(本例即为EmployeeId)。如果聚集索引不是唯一的索引,SQL Server 将添加在内部生成的值(称为唯一值)以使所有重复键唯一。此四字节的值对于用户不可见。仅当需要使聚集键唯一以用于非聚集索引中时,才添加该值。SQL Server 通过使用存储在非聚集索引的叶行内的聚集索引键搜索聚集索引来检索数据行。

六、索引覆盖

  新加字段

ALTER TABLE EMPLOYEE ADD DepartmentCode NVARCHAR(50) NULL

  删除并新建索引(索引覆盖)

drop index IX_TBL_Employee_EmployeeName on Employee
create index IX_Employee_EmployeeName on Employee(EmployeeName) include(DepartmentCode)

  把索引保存进表(先删除记录)

truncate table dbccindresult
INSERT INTO dbccindresult EXEC (‘DBCC IND(PCT,Employee,-1) ‘)

  和上面类似,找到非聚集索引的根节点6386

select * from dbccindresult where IAMPID = 127 and pagetype = 2 and indexlevel = 2

  查看根节点数据

DBCC TRACEON (3604);
GO
DBCC PAGE (PCT, 1, 6386, 3);
GO

  查看中间节点6385

DBCC TRACEON (3604);
GO
DBCC PAGE (PCT, 1, 6385, 3);
GO

  查看叶节点6715

DBCC TRACEON (3604);
GO
DBCC PAGE (PCT, 1, 6715, 3);
GO

  总结如下:

  • 索引覆盖和非聚集索引的根节点和中间节点一样,都是索引页,都只包含下一层的入口指针和入口值。
  • 索引覆盖的叶节点却稍有不同,多了一列DepartmentCode,此列即为索引覆盖列,而且此列只在叶节点出现,如果查询时,只需返回键值列和索引覆盖列,则只需索引查找,肯本无需访问数据页,不仅提高了性能,而且节省占用空间。
时间: 2024-12-08 17:37:32

SQL Server 索引(一)数据结构和存储结构的相关文章

解读SQL Server 2014可更新列存储索引——存储机制

概述 SQL Server 2014被号称是微软数据库的一个革命性版本,其性能的提升的幅度是有史以来之最. 可更新的列存储索引作为SQL Server 2014的一个关键功能之一,在提升数据库的查询性能方面贡献非常突出.据微软统计,在面向OLAP查询统计类系统中,相比其他SQL传统版本的数据库,报表查询的性能最大可提升上十倍. 下面我们从存储的角度来了解下SQL Server 2014的可更新列存储索引. 什么是列存储 微软为了提升SQL Server的查询性能,更好的支持大数据分析,早在SQL

SQL Server索引 (原理、存储)聚集索引、非聚集索引、堆 &lt;第一篇&gt;

一.存储结构 在SQL Server中,有许多不同的可用排列规则选项. 二进制:按字符的数字表示形式排序(ASCII码中,用数字32表示空格,用68表示字母"D").因为所有内容都表示为数字,所以处理起来速度最快,遗憾的是,它并不总是如人们所想象,在WHERE子句中进行比较时,使用该选项会造成严重的混乱. 字典顺序:这种排序方式与在字典中看到的排序方式一样,但是少有不同,可以设置大量不同的额外选项来决定是否区分大小写.音调和字符集. 1.平衡树(B-树) 平衡树或B-树仅是提供了一种以

SQL Server 2016:内存列存储索引

作者 Jonathan Allen,译者 谢丽 SQL Server 2016的一项新特性是可以在"内存优化表(Memory Optimized Table)"上添加"列存储索引(Columnstore Index)".要理解这是什么意思,我们应该首先解释术语列存储索引和内存优化表.列存储索引是一种按照列而不是行组织数据的索引.每个数据块只存储一个列的数据,最多包含100万行.因此,如果数据为5列1000万行,那么就需要存储在50个数据块中.当只查询部分列时,这种数

SQL Server 索引结构及其使用(一)

转载:SQL Server 索引结构及其使用(一) 作者:freedk 一.深入浅出理解索引结构 实际上,您可以把索引理解为一种特殊的目录.微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引.簇集索引)和非聚集索引(nonclustered index,也称非聚类索引.非簇集索引).下面,我们举例来说明一下聚集索引和非聚集索引的区别: 其实,我们的汉语字典的正文本身就是一个聚集索引.比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼

SQL Server 索引结构及其使用

SQL Server 索引结构及其使用(一)  作者:freedk 一.深入浅出理解索引结构    实际上,您可以把索引理解为一种特殊的目录.微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引.簇集索引)和非聚集索引(nonclustered index,也称非聚类索引.非簇集索引).下面,我们举例来说明一下聚集索引和非聚集索引的区别:   其实,我们的汉语字典的正文本身就是一个聚集索引.比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安

sql server 索引总结三

一.非聚集索引维护 非聚集索引的行定位器值保持相同的聚集索引值,即使该聚集索引列物理上重新定位后,也是如此. 为了优化这个维护开销,SQL Server添加一个指向旧数据页的指针,以在页面分割之后指向新的数据页面,而不是更新所有相关非聚集索引的行定位器.这样,虽然降低了非聚集索引的维护开销,但是增加了从非聚集索引行到数据行的导航开销,因为添加了一个旧数据页面和信数据页面之间的连接.因此,将聚集索引作为行定位器降低了非聚集索引相关的开销. 二.定义书签查找 当一个查询请求不是优化器选择的非聚集索引

SQL Server 索引和表体系结构(三)

原文:SQL Server 索引和表体系结构(三) 包含列索引 概述 包含列索引也是非聚集索引,索引结构跟聚集索引结构是一样,有一点不同的地方就是包含列索引的非键列只存储在叶子节点:包含列索引的列分为键列和非键列,所谓的非键列就是INCLUDE中包含的列,至少需要有一个键列,且键列和非键列不允许重复,非键列最多允许1023列(也就是表的最多列-1),由于索引键列(不包括非键)必须遵守现有索引大小的限制(最大键列数为 16,总索引键大小为 900 字节)的要求所以引进了包含列索引. 正文 创建包含

SQL Server 索引知识-应用,维护

创建聚集索引 a索引键最好唯一(如果不唯一会隐形建立uniquier列(4字节)确保唯一,也就是这列都会复制到所有非聚集索引中) b聚集索引列所占空间应尽量小(否则也会使非聚集索引的空间变大) c聚集索引应固定,不能随便改动(否则会引起分页,碎片,非聚集索引被迫修改等一些列问题) d聚集索引键一般与主键(primary key)分开(基于方便业务调整,如业务逻辑存储数据与clustered index key一致且不会改变,则主键可为聚集索引) 应用实例: 选择聚集键时,尽量避免因插入引起的分页

SQL Server索引 - 聚集索引、非聚集索引、非聚集唯一索引 &lt;第八篇&gt;

聚集索引.非聚集索引.非聚集唯一索引 我们都知道建立适当的索引能够提高查询速度,优化查询.先说明一下,无论是聚集索引还是非聚集索引都是B树结构. 聚集索引默认与主键相匹配,在设置主键时,SQL Server会默认在主键列创建聚集索引.但是可以手动更改为在任意一个列创建聚集索引,然后在另一个字段或多个字段上定义主键.这时主键将会被作为一个唯一的非聚集索引(唯一索引)被创建.通过指定NONCLUSTERED关键字就可以做到. CREATE TABLE MyTableKeyExample { Colu

SQL Server 索引 之 书签查找 &lt;第十一篇&gt;

一.书签查找的概念 书签可以帮助SQL Server快速从非聚集索引条目导向到对应的行,其实这东西几句话我就能说明白. 如果表有聚集索引(区段结构),那么书签就是从非聚集索引找到聚集索引后,利用聚集索引定位到数据.此处的书签就是聚集索引.如果表没有聚集索引(堆结构).那么扫描非聚集索引后,通过RID定位到数据,那么此处书签就是RID. 所谓的书签查找,就是通过聚集索引,然后利用聚集索引或RID定位到数据. 不论表示堆结构还是区段结构,数据的存放都是数据库文件的某文件->某页->某行,因此定位数