这边文章,我将会带你深入分析数据库中 数据页 的结构。通过这篇文章的学习,你将掌握如下知识点:

1. 查看一个 表/索引 占用了多少了页。

2. 查看某一页中存储了什么的数据。

3. 验证在数据库中用 GUID类型时用 newid() 生成的数据作为聚集索引时的缺陷。

首先需要清楚 页(Page) 和 盘区(Extent) 的概念。页是SQL Server中数据存储的基本单元,每一页的大小都是8K。而盘区是一组页的集合,每一个盘区都是由8个相邻的页组合而成的。

上面的这张图片引用自微软官方文档,它展示了页的基本结构。

一个盘曲是8个页的集合,所以每一个盘曲的大小就是64K。1M的数据就包含16个盘曲。 盘曲分为两种:

1. 统一区(Uniform):由单个对象所有。区中的所有 8 页只能由所属对象使用。

2. 混合区(Mixed):最多可由八个对象共享。区中八页的每页可由不同的对象所有。

除此之外,还需要了解一个概念,就是IAM页,它的全称是Index Allocation Map Page。IAM是对盘曲(Extent)的管理,每个IAM最大为4G。当数据超过4G时,或者IAM页中的 Extent 存储跨文件时,就会形成IAM链。

可以通过  sys.system_internals_allocation_units  来查看 一个分配单元(allocation unit)的第一个IAM页 地址。

IAM链的逻辑概念图:

上面只是简单地介绍了一下 页,区,和分配单元 的基本概念,更多信息,请查看 Pages and Extents Architecture Guide.

有了上面的基本概念后,接下来进行实际案例分析。

首先创建一个测试的数据库,并且插入一些测试数据。

CREATE DATABASE TEST
GO
USE TEST
CREATE TABLE DBO.EMPLOYEE
(
EMPLOYEEID INT IDENTITY(1,1),
FIRSTNAME VARCHAR(50) NOT NULL,
LASTNAME VARCHAR(50) NOT NULL,
DATE_HIRED DATETIME NOT NULL,
IS_ACTIVE BIT NOT NULL DEFAULT 1,
CONSTRAINT PK_EMPLOYEE PRIMARY KEY (EMPLOYEEID),
CONSTRAINT UQ_EMPLOYEE_LASTNAME UNIQUE (LASTNAME, FIRSTNAME)
) GO
INSERT INTO DBO.EMPLOYEE (FIRSTNAME,LASTNAME,DATE_HIRED)
SELECT 'George', 'Washington', '1999-03-15'
GO
INSERT INTO DBO.EMPLOYEE (FIRSTNAME,LASTNAME,DATE_HIRED)
SELECT 'Benjamin', 'Franklin', '2001-07-05'
GO
INSERT INTO DBO.EMPLOYEE (FIRSTNAME,LASTNAME,DATE_HIRED)
SELECT 'Thomas', 'Jefferson', '2002-11-10'
GO

现在,上面的表和索引已经成功创建了,并且SQL Server将这些数据以页的形式存起来了。我们可以通过DBCC IND命令来罗列出这些信息。

DBCC IND语法:

DBCC IND
(
['database name'|database id], -- the database to use
table name, -- the table name to list results
index id, -- an index_id from sys.indexes; -1 shows all indexes and IAMs, -2 just show IAMs
)

接下来,让我们来看看 EMPLOYEE表的页信息:

-- List data and index pages allocated to the EMPLOYEE table
DBCC IND('Test',EMPLOYEE,-1)
GO

输出结果:

字段解释,PageFID:文件编号。PagePID:文件里页的编号。IAMFID:IAM页所在文件的编号。IAMPID:IAM页在文件里的编号。ObjectID:对象编号,可以由OBJECT_NAME获得其名称。IndexID:是sys.indexes中的的index_id值,1是聚集索引,2是非聚集索引。PartitionNumber:分区数。PartitionID:分区编号。iam_chain_type:IAM链类型,IN_ROW DATA 表示用于存储堆分区或索引分区,每个堆和索引的分区都有IN_ROW DATA的分配单元。Page Type: 页类型,1是数据页,2是索引页,10是IAM页。IndexLevel:表示页所在树中的层级,0表示叶子节点。NextPageFID:下一个文件的编号。NextPagePID:下一个页编号。PrevPageFID:前一个文件的编号。PrevPagePID:前一个页编号。

有了这些信息后,我们进行进一步的分析。上面的EMPLOYEE表,有一个聚集索引为PK_EMPLOYEE,所以它的index_id就为1,并且PageType也应该为1(因为聚集索引就是实际存储数据的顺序)。因此我们可以锁定为上面的第2条数据,就可以得出PageFID和PagePID的值,有了这两个值后,我们就可以深入到页里面去观察了。使用DBCC PAGE命令,可以清楚地观察到页里面到底存了什么数据。

-- TRACEON(3604) 表示将结果输出到控制台
-- 1 是 PageFID
-- 368 是 PagePID
-- 3 表示输出Header和Data信息
DBCC TRACEON(3604)
DBCC PAGE('Test',1,368,3) WITH TABLERESULTS
GO

输出结果:

通过上面的结果图可以看出,数据是按照聚集索引的顺序存储的(EMPLOYEEID)。每一条数据都对应一个slot,slot从0开始,每次增加1,slot 0, slot 1, slot 2 ...... slot n。Field和Value字段,清楚地展示了我们所存储数据。每次的偏移(Offset)都是上次的 Offset 加上上一个字段的长度。

EMPLOYEE表除了聚集索引,还有一个非聚集索引(UQ_EMPLOYEE_LASTNAME)。由于非聚集索引的index_id的值为2, 并且PageType也应该为2,所以我们知道它的PagePID为1, PagePID为400,接下来看看页里的详细信息:

-- TRACEON(3604) 表示将结果输出到控制台
-- 1 是 PageFID
-- 400 是 PagePID
-- 3 表示输出Header和Data信息
DBCC TRACEON(3604)
DBCC PAGE('Test',1,400,3) WITH TABLERESULTS
GO

输出结果:

滑倒最下面,可以看到一张更清楚的索引逻辑表。

从这个表中可以清楚地看到非聚集索引是按照逻辑存储的。并且每条数据都又一个EMPLOYEEID,也就是主键。换句话说,在有聚集索引的表中,非聚集索引是通过主键和原始数据关联。这一点和堆表(heap table, 没有聚集索引的表)不一样。

上面观察了聚集索引和非聚集索引的页信息,除了这两个,还有一个是IAM页的信息,这里笔者不做过多描述。有兴趣的朋友,可以自己打印出来看看。打印方法和上面的一致。接下来我们再来看看堆表(heap table)中的索引页是如何存储的。

alter table EMPLOYEE drop constraint PK_EMPLOYEE
GO
ALTER TABLE DBO.EMPLOYEE ADD CONSTRAINT PK_EMPLOYEE PRIMARY KEY NONCLUSTERED (EMPLOYEEID)
GO
DBCC IND('Test',EMPLOYEE,-1) DBCC PAGE('Test',1,440,3)

输出结果:

我们可以看出堆表中的非聚集索引都有一个HEAP RID,它指向了实际的数据源。RID值的格式为 FileID:PageID:SlotID 组成,移步Heaps(Tables Without Clustered Indexes)获取详细信息。

通过上面的学习你已经知道表的数据页的存储结构了,然后,笔者解决一下最开始提出的问题。

1. 查看一个 表/索引 占用了多少了页 ?

可以通过命令DBCC IND输出所有的页信息,然后再通过NextPagePID来得出某一个索引的全部页。

2. 查看某一页中存储了什么的数据 ?

可以通过命令DBCC PAGE某一个页里存储的数据详情。

3. 验证在数据库中用 GUID类型时用 newid() 生成的数据作为聚集索引时的缺陷?

通常情况,将newid()作为聚集索引是非常不好的设计,使用如下的测试案例来评测一下将newid()作为聚集索引时的存储缺点。

USE TEST
CREATE TABLE DBO.EMPLOYEE
(
EMPLOYEEID [uniqueidentifier] not null default newid(),
FIRSTNAME VARCHAR(50) NOT NULL,
LASTNAME VARCHAR(50) NOT NULL,
DATE_HIRED DATETIME NOT NULL,
CONSTRAINT PK_EMPLOYEE PRIMARY KEY (EMPLOYEEID)
)
INSERT INTO DBO.EMPLOYEE (FIRSTNAME,LASTNAME,DATE_HIRED)
SELECT 'George', 'Washington', '1999-03-15'
GO
INSERT INTO DBO.EMPLOYEE (FIRSTNAME,LASTNAME,DATE_HIRED)
SELECT 'Benjamin', 'Franklin', '2001-07-05'
GO
INSERT INTO DBO.EMPLOYEE (FIRSTNAME,LASTNAME,DATE_HIRED)
SELECT 'Thomas', 'Jefferson', '2002-11-10'

然后查看一下内存页的数据存储情况

DBCC IND('Test',EMPLOYEE,-1)
DBCC TRACEON(3604)
DBCC PAGE('Test',1,456,3) WITH TABLERESULTS

输出结果:

你会发现实际数据的存储顺序和插入数据的顺序不一致,也就是说在SQL Server在插入新数据时,可能会移动其它的数据(因为newid()每次生成的数据都是随机的),插入新数据时候,移动其它的数据无疑是一种额外的消耗,在大数据量的表中,缺陷尤其明显。

怎么解决这个问题呢? 有两个方法,第一是不用uniqueidentifier作为主键类型,第二种是使用这里 NEWSEQUENTIALID() 替换 NEWID() 。NEWSEQUENTIALID()每次生成的值都会比它以前生成的值大。

感谢读者耐心地阅读完本文,上面提到的 DBCC IND  和 DBCC PAGE命令,微软官方并没有提供相应的文档。未来,这些命令的功能可能会改变或是移除。目前笔者的数据库是2016的版本。本文参考了Armando Prato的Using DBCC PAGE to Examine SQL Server Table and Index Data文章,有兴趣的朋友,可以移步Armando Prato的博客查看更多内容。

[SqlServer] 理解数据库中的数据页结构的更多相关文章

  1. 【Mysql】InnoDB 引擎中的数据页结构

    InnoDB 是 mysql 的默认引擎,也是我们最常用的,所以基于 InnoDB,学习页结构.而学习页结构,是为了更好的学习索引. 一.页的简介 页是 InnoDB 管理存储空间的基本单位,一个页的 ...

  2. SQL Server 存储(1/8):理解数据页结构

    我们都很清楚SQL Server用8KB 的页来存储数据,并且在SQL Server里磁盘 I/O 操作在页级执行.也就是说,SQL Server 读取或写入所有数据页.页有不同的类型,像数据页,GA ...

  3. SQL Server :理解数据页结构

    原文:SQL Server :理解数据页结构 我们都很清楚SQL Server用8KB 的页来存储数据,并且在SQL Server里磁盘 I/O 操作在页级执行.也就是说,SQL Server 读取或 ...

  4. SQLServer---------使用Excel 往sqlServer数据库中导入数据

    1.右击创建好的表选择编辑200行 2.保证Excel的字段顺序与数据中顺序一致 3.选中好了后进行复制 4.打开文本   一个快捷方式 将excel 中的数据 黏贴放到文本中 5.点击sql    ...

  5. InnoDB数据页结构

    前言 ​ 关于数据库我们知道是通过内存对磁盘进行操作的,也知道数据会落实到磁盘上,但是数据在磁盘上的存储结构可能大家还不是很清楚. ​ MySQL服务器上负责对表中的数据的读取和写入的工作的部分是存储 ...

  6. MVC设计模式((javaWEB)在数据库连接池下,实现对数据库中的数据增删改查操作)

    设计功能的实现: ----没有业务层,直接由Servlet调用DAO,所以也没有事务操作,所以从DAO中直接获取connection对象 ----采用MVC设计模式 ----采用到的技术 .MVC设计 ...

  7. InnoDB的数据页结构

    页是InnoDB存储引擎管理数据库的最小磁盘单位.页类型为B-tree node的页,存放的即是表中行的实际数据了. InnoDB数据页由以下七个部分组成,如图所示: File Header(文件头) ...

  8. InnoDB存储引擎介绍-(7) Innodb数据页结构

    数据页结构 File Header 总共38 Bytes,记录页的头信息 名称 大小(Bytes) 描述 FIL_PAGE_SPACE 4 该页的checksum值 FIL_PAGE_OFFSET 4 ...

  9. SqlServer 查看数据库、添加数据文件

    一.查看SqlServer实例的数据库列表 1).直接在SSMS(SqlServer Management Studio)管理工具里面 展开实例下面的所有数据库便可查看  2).使用Transact- ...

随机推荐

  1. 【NX二次开发】指定矢量控件,记住上次选择的方向

    block UI控件如果有RetainValue属性,就用这个属性.没有这个属性可以参考下面这种方法.以矢量控件为例: 1.在apply_cb回调中,将控件值保存到文本中 double TopForT ...

  2. 如何让vscode C++ 终端不再显示调试启动信息

    按照微软的官方文档(https://go.microsoft.com/fwlink/?LinkID=533484#vscode)配置好C++环境之后. 每次按F5都会在终端输出,但是会附加一串信息.例 ...

  3. Android开发问题之Installation failed due to invalid URI!

    真机调试遇到以下问题: [2017-07-20 13:43:53 - VCL02PANEL] Installation failed due to invalid URI![2017-07-20 13 ...

  4. 《MySQL面试小抄》索引考点二面总结

    <MySQL面试小抄>索引考点二面总结 我是肥哥,一名不专业的面试官! 我是囧囧,一名积极找工作的小菜鸟! 囧囧表示:小白面试最怕的就是面试官问的知识点太笼统,自己无法快速定位到关键问题点 ...

  5. golang中的defer和return的执行顺序

    结论 go中是先给return准备返回值,再根据defer先进后出的规则执行,最后将返回值返回给调用者 测试用例1验证分析 代码片段如下: func foo_1() (err error) { def ...

  6. React 开发环境准备

    1. 使用reactjs,一般有以下两种方式: (1)通过script标签引入reactjs.这种方式不推荐使用,如果我们的项目比较大,就需要对项目进行拆分,于是页面就需要通过script标签引入很多 ...

  7. oscp-缓冲区溢出(持续更新)

    环境准备 Windows7虚拟机(我选了IE8,其实也没什么关系) 微软官方下载地址 These virtual machines expire after 90 days. We recommend ...

  8. 深入理解 Android ANR 触发原理以及信息收集过程

    一.概述 作为 Android 开发者,相信大家都遇到过 ANR.那么为什么会出现 ANR 呢,ANR 之后系统都做了啥.文章将对这个问题详细解说. ANR(Application Not respo ...

  9. 垃圾处理器-CMS

    一.简介 CMS垃圾收集器是一款用于老年代的,使用复制-清除-整理算法的垃圾收集器. 二.GC阶段 1.初始化标记(STW) 暂停应用程序线程,遍历 GC ROOTS 直接可达的对象并将其压入标记栈( ...

  10. kotlin gradle 生成jni头文件

    目录 问题 解决方法 使用方法 代码 gradle task位置截图 问题 最近在用kotlin写jni,但是生成头文件的时候遇到了些问题. 首先 javah 在java >= 1.9 就被取消 ...