【SQL SERVER重新认识】数据内部存储结构简单探索
数据库内部如何存储数据
1. 要验证,先准备数据,这里创建是一个表,并添加3条数据
create table DataTable(Id int identity(1,1), [Name] varchar(50), [Address] varchar(200), CreateTime datetime2) insert into DataTable
select 'Wilson','广州市天河区',GETDATE() union all
select 'Alice','北京市朝阳区',GETDATE() union all
select 'Key','广州市番禺区',GETDATE()
2. 利用DBCC查看页数据,数据库名称Demo
DBCC TRACEON(2588,3604) --打开追踪
DBCC IND(Demo,DataTable,-1) --查看分配情况,这里查到的PageFID,PagePID,用于PAGE查询,PageType = 1 是数据页
DBCC PAGE(Demo,1,224,1) --查看页槽位情况
PAGE内容太多,截取部分数据。
Slot 0, Offset 0x60, Length 43, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 43
Memory Dump @0x000000557C77A060 0000000000000000: 30001000 01000000 c0bb04c8 7fea400b 04000002 0.............@.....
0000000000000014: 001f002b 0057696c 736f6eb9 e3d6ddca d0ccecba ...+.Wilson.........
0000000000000028: d3c7f8 ... Slot 1, Offset 0x8b, Length 42, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 42
Memory Dump @0x000000557C77A08B 0000000000000000: 30001000 02000000 c0bb04c8 7fea400b 04000002 0.............@.....
0000000000000014: 001e002a 00416c69 6365b1b1 bea9cad0 b3afd1f4 ...*.Alice..........
0000000000000028: c7f8 .. Slot 2, Offset 0xb5, Length 40, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 40
Memory Dump @0x000000557C77A0B5 0000000000000000: 30001000 03000000 c0bb04c8 7fea400b 04000002 0.............@.....
0000000000000014: 001c0028 004b6579 b9e3d6dd cad0b7ac d8aec7f8 ...(.Key............ OFFSET TABLE: Row - Offset
2 (0x2) - 181 (0xb5)
1 (0x1) - 139 (0x8b)
0 (0x0) - 96 (0x60)
上面是16进制,拿第一条数据来分析,分析完再跟另外两条数据来验证

Slot 0, Offset 0x60, Length 43, DumpStyle BYTE
0000000000000000: 30001000 01000000 c0bb04c8 7fea400b 04000002 0.............@.....
0000000000000014: 001f002b 0057696c 736f6eb9 e3d6ddca d0ccecba ...+.Wilson.........
0000000000000028: d3c7f8
30001000:字段的标志位,没找到官方说明,目前了解是,有可变字段和无可变字段不一致,已经删除的行会从30->3c
01000000:这个明显代表是Id,从另外两条数据可以猜到
c0bb04c8 7fea400b:因为看到Name在后面,这里刚好8个字符,这个应该是时间datetime2,写了个代码将16进制转换datetime2,现在就用代码验证,代码在文章最后放出来,因为时间转换有精度丢失,所以两个时间不是完全一致。

04000002:04代表一共有4个字段,02代表一共有连个可变长字段
001f002b: 这里是两个可变字符的偏移量
0057696c 736f6eb9 e3d6ddca d0ccecba d3c7f8:这一串保存是姓名和地址,前面两个00,应该是上面长度的一个分隔符,下面也是用代码验证一下

到这里也大概清楚SQL如何存储数据,它不是按字段顺序存储,先存固定长度的字段,然后插入分隔的符号,然后存可变长的字段,这样做可能为了减少移动数据带来的成本,后面操作数据会有讲到。
索引数据如何存储
到目前为止,是没有涉及到索引,因为我们上面的数据是没有创建索引,是一个堆表。
索引分非聚集索引和聚集索引
1. 创建非聚集索引
create index IX_DataTable_Name on DataTable(Name ASC)
1.1 查看数据页分配情况
DBCC IND(Demo,DataTable,-1) --查看分配情况
DBCC PAGE(Demo,1,280,1) --查看页分配情况
1.2 Page分配情况
Slot 0, Offset 0x60, Length 21, DumpStyle BYTE Record Type = INDEX_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 21
Memory Dump @0x00000055721FA060 0000000000000000: 36000100 00010001 00020000 01001500 416c6963 6...............Alic
0000000000000014: 65 e
Slot 1, Offset 0x75, Length 22, DumpStyle BYTE Record Type = INDEX_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 22
Memory Dump @0x00000055721FA075 0000000000000000: 36000100 00010002 00020000 01001600 4b657920 6...............Key
0000000000000014: 4c69 Li Slot 2, Offset 0x8b, Length 22, DumpStyle BYTE Record Type = INDEX_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 22
Memory Dump @0x00000055721FA08B 0000000000000000: 36000100 00010000 00020000 01001600 57696c73 6...............Wils
0000000000000014: 6f6e on
1.3 Page页面分析
0100 00010001:这个是行Id,转义过来是(1:256:1),因为现在还是堆表,所以指向行Id
00020000:这个也是正文开始标记
01001500:这里记录索引的长度
后面的就是索引内容
可以看到,索引存储大概结构
指针 -> 索引信息(长度)-> 索引内容
查看行Id,可以使用sys.fn_PhysLocFormatter(%%physloc%%)
select sys.fn_PhysLocFormatter(%%physloc%%),* from DataTable

2 创建聚集索引
create clustered index IX_DatTaable_Name on DataTable(Name ASC)
2.1 查看数据页分配情况
DBCC IND(Demo,DataTable,-1) --IndexID=1就是聚集索引
DBCC PAGE(Demo,1,234,3)
2.2 Page分配情况
0000000000000000: 30001000 02000000 20d1565f 0deb400b 05000003 0....... .V_..@.....
0000000000000014: 001b0020 002c0041 6c696365 b1b1bea9 cad0b3af ... .,.Alice........
0000000000000028: d1f4c7f8
2.3 Page页面分析
这里数据大概格式行Id+定长字段+行信息+变长数据,这里就不展开验证。
值得注意是,这里的字段数量和可变长字段数量为
这里之所以会多了一个字段,是因为我们添加的聚集索引没有指定唯一,SQL SERVER会自动添加一个4字节的字段,确保聚集索引唯一。

操作数据对存储影响
1. INSERT
insert into DataTable(Name,Address,CreateTime)
select 'Jack','广州市天河区',GETDATE()
1.1 查看Page情况
Slot 0, Offset 0x60, Length 44, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 44
Memory Dump @0x0000005CD11FA060 0000000000000000: 30001000 02000000 40f61b57 1aeb400b 05000003 0.......@..W..@.....
0000000000000014: 001b0020 002c0041 6c696365 b1b1bea9 cad0b3af ... .,.Alice........
0000000000000028: d1f4c7f8 .... Slot 1, Offset 0xe3, Length 43, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 43
Memory Dump @0x0000005CD11FA0E3 0000000000000000: 30001000 04000000 350f285e 1aeb400b 05000003 0.......5.(^..@.....
0000000000000014: 001b001f 002b004a 61636bb9 e3d6ddca d0ccecba .....+.Jack.........
0000000000000028: d3c7f8 ...
可以看到在Alice后面槽位插入了数据(因为这个表的聚集索引是在Name,升序)
2. UPDATE
update DataTable set Address = '广州市白云区黄边路8号' where Id = 1
2.1 查看Pag情况,用2,查看整个Page
0000005CD387A064: 02000000 40f61b57 1aeb400b 05000003 001b0020 ....@..W..@........
0000005CD387A078: 002c0041 6c696365 b1b1bea9 cad0b3af d1f4c7f8 .,.Alice............
0000005CD387A08C: 30001000 03000000 40f61b57 1aeb400b 05000003 0.......@..W..@.....
0000005CD387A0A0: 001b001e 002a004b 6579b9e3 d6ddcad0 b7acd8ae .....*.Key..........
0000005CD387A0B4: c7f83000 10000100 000040f6 1b571aeb 400b0500 ..0.......@..W..@...
0000005CD387A0C8: 0003001b 0021002d 0057696c 736f6eb9 e3d6ddca .....!.-.Wilson.....
0000005CD387A0DC: d0b0d7d4 c6c7f830 00100004 00000035 0f285e1a .......0.......5.(^.
0000005CD387A0F0: eb400b05 00000300 1b001f00 2b004a61 636bb9e3 .@..........+.Jack..
0000005CD387A104: d6ddcad0 ccecbad3 c7f83000 10000100 000040f6 ..........0.......@.
0000005CD387A118: 1b571aeb 400b0500 0003001b 00210036 0057696c .W..@........!.6.Wil
0000005CD387A12C: 736f6eb9 e3d6ddca d0b0d7d4 c6c7f8bb c6b1dfc2 son.................
可以看到有2个Wilson的记录,因为更新的字段比原来的长,原来地方放不下,在当前页空闲的地方重新整条记录复制过去,然后偏移量指向新的地址。实际上字段变短了也会发生这种迁移。
这样原来的地方就变成碎片。事实上索引页的维护也是一样道理。
3.DELETE
delete DataTable where Id = 2
3.1 查看Pag情况,用2,查看整个Page
3c001000 ................<...
0000005CD627A064: 02000000 40f61b57 1aeb400b 05000003 001b0020 ....@..W..@........
0000005CD627A078: 002c0041 6c696365 b1b1bea9 cad0b3af d1f4c7f8 .,.Alice............
0000005CD627A08C: 30001000 03000000 40f61b57 1aeb400b 05000003 0.......@..W..@.....
可以看到Alice这条记录还存在页上,原来行头的4个标志位从30001000 -> 3c001000
总结
其实数据库如何存储对平常开发没什么影响,只是无聊研究一下。
其实还是有点影响,我能想到如下,未必准确和完整。
1. 尽量选择定长的字段,例如状态,类型这种字段定义int
2. char 是用空间换时间,经常更新字段且比较短的字段可以考虑定义char
3. 超长的varchar字段可以考虑不放在主表,不然一页存不下,又会发生行溢出问题
4. 索引有利有弊,要尽量合理使用索引,特别是聚集索引,非常要谨慎使用。
转发请标明出处:https://www.cnblogs.com/WilsonPan/p/12605299.html
示例代码:https://github.com/WilsonPan/Net.Demos/tree/master/Demo.SQLTools
【SQL SERVER重新认识】数据内部存储结构简单探索的更多相关文章
- 【数据处理】SQL Server高效大数据量存储方案SqlBulkCopy
要求将Excel数据,大批量的导入到数据库中,尽量少的访问数据库,高性能的对数据库进行存储. 一个比较好的解决方案,就是采用SqlBulkCopy来处理存储数据. SqlBulkCopy存储大批量的数 ...
- SQL Server 批量插入数据方案 SqlBulkCopy 的简单封装,让批量插入更方便
一.Sql Server插入方案介绍 关于 SqlServer 批量插入的方式,有三种比较常用的插入方式,Insert.BatchInsert.SqlBulkCopy,下面我们对比以下三种方案的速度 ...
- SQL Server :理解数据页结构
原文:SQL Server :理解数据页结构 我们都很清楚SQL Server用8KB 的页来存储数据,并且在SQL Server里磁盘 I/O 操作在页级执行.也就是说,SQL Server 读取或 ...
- SQL Server :理解数据记录结构
原文:SQL Server :理解数据记录结构 在SQL Server :理解数据页结构我们提到每条记录都有7 bytes的系统行开销,那这个7 bytes行开销到底是一个什么样的结构,我们一起来看下 ...
- sql server数据库备份单个表的结构和数据生成脚本
1.使用场景:sql server数据库备份单个表的结构和数据,在我们要修改正式系统的数据的一天或者多条某些数据时候,要执行update语句操作,安全稳健考虑,最好先做好所修改的表的结构和数据备份! ...
- sql server数据库备份单个表的结构和数据生成脚本【转】
1.使用场景:sql server数据库备份单个表的结构和数据,在我们要修改正式系统的数据的一天或者多条某些数据时候,要执行update语句操作,安全稳健考虑,最好先做好所修改的表的结构和数据备份! ...
- SQL Server 内存中OLTP内部机制概述(四)
----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory ...
- SQL Server 内存中OLTP内部机制概述(二)
----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory ...
- SQL Server 内存中OLTP内部机制概述(一)
----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory ...
随机推荐
- 内核ioctl函数的cmd宏参数
在驱动程序里, ioctl() 函数上传送的变量 cmd 是应用程序用于区别设备驱动程序请求处理内容的值.cmd除了可区别数字外,还包含有助于处理的几种相应信息. cmd的大小为 32位,共分 4 个 ...
- 创建 GPG 证书
一.什么是 GPG 以下引自维基百科: GNU Privacy Guard(GnuPG或GPG)是一种加密软件,它是PGP加密软件的满足GPL的替代物.GnuPG依照由IETF订定的OpenPGP技术 ...
- 【转载】ArcGIS中的WKID
原出处:http://www.cnblogs.com/liweis/p/5951032.html 提到坐标系统,大家多少能明白一些,但在运用时,有些朋友搞得不是非常清楚,以后专门来总结.在实地生产项目 ...
- 苹果为何要一定要去印度生产iPhone
现在,关于苹果手机有几种流行的猜想和期待,今年恰逢iPhone问世十周年,新产品估计会有颠覆性创新,消费者正望穿秋水,翘首企盼,但只需待到金秋便可知晓,何况iPhone8或许也就是一小撮发烧友的选 ...
- Django中使用websocket并实现简易聊天室
django使用websocket并实现简易聊天室 django默认只支持http协议 如果你想让django即支持http协议又支持websocket协议,则需要做以下配置 前期配置 前提需要安装c ...
- SPA中前端路由基本原理与实现方式
SPA 前端路由原理与实现方式 通常 SPA 中前端路由有2中实现方式,本文会简单快速总结这两种方法及其实现: 修改 url 中 Hash 利用 H5 中的 history Hash 我们都知道 ur ...
- SSM动态切换数据源
有需求就要想办法解决,最近参与的项目其涉及的三个数据表分别在三台不同的服务器上,这就有点突兀了,第一次遇到这种情况,可这难不倒笔者,资料一查,代码一打,回头看看源码,万事大吉 1. 预备知识 这里默认 ...
- Python基础-两个乒乓球队进行比赛,各出三人。
两个乒乓球队进行比赛,各出三人.甲队为a,b,c三人,乙队为x,y,z三人.已抽签决定比赛名单.有人向队员打听比赛的名单.a说他不和x比,c说他不和x,z比,请编程序找出三队赛手的名单. L1 = [ ...
- python之函数介绍
# 函数 # 什么是函数: 能完成特定功能的工具,在Python中表示能完成特定功能的代码块.(函数定义) # 为什么要用函数 :①函数可以重复调用出来,效率高,而且维护成本低 ②使程序结构看起来清晰 ...
- vue移动端字体大小设置
const setRemUnit = () => { const docEl = document.documentElement; // IPhone6下750像素来设计,实际像素375px, ...