【SQL SERVER重新认识】数据内部存储结构简单探索「终于解决」

【SQL SERVER重新认识】数据内部存储结构简单探索「终于解决」探索数据库内部存储数据存储结构,将从数据库内部如何存储数据,索引数据如何存储,操作数据对存储影响,最后总结。

【SQL SERVER重新认识】数据内部存储结构简单探索

 数据库经常需要打交道,但是从来没想过数据库内部是如何存储数据。

 今天探索一下数据库内部如何存储数据,从下面几个方面探索

  1. 数据库内部如何存储数据
  2. 索引数据如何存储
  3. 操作数据对存储影响
  4. 总结

数据库内部如何存储数据

1. 要验证,先准备数据,这里创建是一个表,并添加3条数据

【SQL SERVER重新认识】数据内部存储结构简单探索「终于解决」

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()

代码100分

View Code

2. 利用DBCC查看页数据,数据库名称Demo

代码100分DBCC TRACEON(2588,3604)            --打开追踪
DBCC IND(Demo,DataTable,-1)        --查看分配情况,这里查到的PageFID,PagePID,用于PAGE查询,PageType = 1 是数据页
DBCC PAGE(Demo,1,224,1)            --查看页槽位情况

PAGE是查看cha内容太多,截取部分数据,

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进制,拿第一条数据来分析,分析完再跟另外两条数据来验证

【SQL SERVER重新认识】数据内部存储结构简单探索「终于解决」

代码100分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,现在就用代码验证,代码在文章最后放出来,因为时间转换有精度丢失,所以两个时间不是完全一直

【SQL SERVER重新认识】数据内部存储结构简单探索「终于解决」

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

【SQL SERVER重新认识】数据内部存储结构简单探索「终于解决」

 到这里也大概清楚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

【SQL SERVER重新认识】数据内部存储结构简单探索「终于解决」

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+定长字段+行信息+变长数据,这里就不展开验证。

值得注意是,这里的字段数量和可变长字段数量为 05000003

这里之所以会多了一个字段,是因为我们添加的聚集索引没有指定唯一,SQL SERVER会自动添加一个4字节的字段,确保聚集索引唯一。

【SQL SERVER重新认识】数据内部存储结构简单探索「终于解决」

操作数据对存储影响

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://daima100.com/9245.html

(0)
上一篇 2023-02-12
下一篇 2023-02-12

相关推荐

  • openGauss SQL引擎(下)——查询优化[通俗易懂]

    openGauss SQL引擎(下)——查询优化[通俗易懂]上一篇[openGauss SQL引擎 (上)]中我们介绍了SQL引擎概览、SQL解析以及查询优化器的优势和优化技术的分类,本文将详细介绍查询优化的相关内容。 (一)查询重写 查询重写利用已有语句特…

    2023-04-13
    147
  • 以file.read()为中心写一个原始标题

    以file.read()为中心写一个原始标题无论你是一名Python工程师,还是正在学习Python的新手,读取文件是你不能回避的一个任务。在Python中,使用file.read()函数可以实现文件的读取。但是,如何在使用file.read()时才能更好地处理数据呢?本文将从多个方面对file.read()进行详细的阐述,帮助读者更好地理解与使用。

    2024-05-04
    91
  • Python技术加速您的业务增长

    Python技术加速您的业务增长Python是一门易于学习且十分流行的编程语言,它被许多企业和组织广泛采用。Python语言具有高效、灵活、可扩展等特点,可以用于多种用途,如web开发、数据分析、机器学习、自然语言处理等。在这篇文章中,我们将从多个方面讨论Python技术如何加速您的业务增长。

    2024-01-23
    96
  • Python os.path.walk:遍历目录并执行指定操作

    Python os.path.walk:遍历目录并执行指定操作Python的os.path模块提供了os.path.walk函数,该函数可以在指定目录及其子目录中遍历所有文件并执行指定操作。os.path.walk()需要3个参数,分别是起始目录、一个迭代函数、一个传递给迭代函数的参数。其基本语法如下:

    2024-03-14
    76
  • 技术分享 | 从库 MTS 多线程并行回放(一)[通俗易懂]

    技术分享 | 从库 MTS 多线程并行回放(一)[通俗易懂]作者:高鹏(八怪) 本节包含分发调用流程请参考链接: https://www.jianshu.com/p/8706d7422d89 一、综述 与单 SQL 线程的回放不同,MTS 包含多个工作线程,…

    2023-01-29
    156
  • Linux 上安装 PostgreSQL

    Linux 上安装 PostgreSQL 打开 PostgreSQL 官网 https://www.postgresql.org/,点击菜单栏上的 Download ,可以看到这里包含了很多平台的安装包,包括 Linux、Windo…

    2023-03-28
    140
  • Python数组过滤:快速筛选和提取数据

    Python数组过滤:快速筛选和提取数据在Python中,我们可以使用多种方法来筛选数据。其中最基础的方法就是使用for循环和if语句来遍历数组,并判断每个元素是否符合我们的筛选条件。

    2024-01-10
    93
  • MySQL锁表解决方案(亲测)「终于解决」

    MySQL锁表解决方案(亲测)「终于解决」1,MySQL5.6版本之前的解决方案: 查询表锁定的SQL语句: SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thr…

    2023-03-30
    683

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注