一.本文所涉及的内容(Contents)

  1. 本文所涉及的内容(Contents)
  2. 背景(Contexts)
  3. 堆表行记录存储格式(Heap)
  4. 案例分析(Case)
  5. 参考文献(References)

二.背景(Contexts)

  有的时候你需要计算堆表的一行记录有多大?又或者想计算一个数据页(8K)能保存多少条记录?字段类型是设计成nchar还是nvarchar?他们有什么区别呢?在做数据库表设计的时候会经常出现这些问题。要计算一行记录的大小,并不是简单把列字段类型大小直接相加就行的,具体原因请看下文。

三.堆表行记录存储格式(Heap)

  下面是计算堆表行记录大小的公式,它引自MSDN:估计堆的大小

计算公式:Row_Size = Fixed_Data_Size + Variable_Data_Size + Null_Bitmap + 4

= 所有固定长度列的总字节大小 + 可变长度数据的大小 + Null位图 + 数据行的行标题开销

  单看上面的计算公式是比较难理解的,而且MSDN并没有提供相关的说明,比如上面公式最后的4代表什么意思?我参考了一些书籍之后整理出下面更容易理解的堆记录存储格式图,希望能帮助大家理解:

(Figure1:堆表记录存储格式)

四.案例分析(Case)

  本文针对堆表数据页的存储做一个测试,测试不同数据类型的存储大小,测试的数据类型包括:int、char、nchar和nvarchar,测试后你会理解什么是定长类型、什么是变长类型,他们在存储上有什么区别;

(一) 首先创建一个测试数据库;

/******* Step1:创建示例数据库*******/
USE master
GO
IF EXISTS(SELECT name FROM sys.databases WHERE name = 'RecordSize_DB')
DROP DATABASE RecordSize_DB
GO
CREATE DATABASE RecordSize_DB
GO

(二) 接着在数据库中创建3个不同的堆表:[HeapPage_char]、[HeapPage_nchar] 和[HeapPage_nvarchar],3个表分别代表:char、nchar和nvarchar不同数据类型的存储;

USE RecordSize_DB
GO
/******* Step2:创建个堆表*******/
CREATE TABLE [dbo].[HeapPage_char](
[id] [int] IDENTITY(1,1) NOT NULL,
[names] [char](10) NULL
) ON [PRIMARY]
GO CREATE TABLE [dbo].[HeapPage_nchar](
[id] [int] IDENTITY(1,1) NOT NULL,
[names] [nchar](10) NULL
) ON [PRIMARY]
GO CREATE TABLE [dbo].[HeapPage_nvarchar](
[id] [int] IDENTITY(1,1) NOT NULL,
[names] [nvarchar](10) NULL
) ON [PRIMARY]
GO

(三) 再接着就是在3个不同的堆表插入相同的数据;

/******* Step3:分别插入测试数据*******/
INSERT INTO [HeapPage_char](names) values('XX')
GO
INSERT INTO [HeapPage_char](names) values('XXXX')
GO 2 INSERT INTO [HeapPage_nchar](names) values('XX')
GO
INSERT INTO [HeapPage_nchar](names) values('XXXX')
GO 2 INSERT INTO [HeapPage_nvarchar](names) values('XX')
GO
INSERT INTO [HeapPage_nvarchar](names) values('XXXX')
GO 2

(四) 返回3个测试表数据;

-- 返回表数据
SELECT * FROM [HeapPage_char]
SELECT * FROM [HeapPage_nchar]
SELECT * FROM [HeapPage_nvarchar]

(Figure2:HeapPage_char)

(Figure3:HeapPage_nchar)

(Figure4:HeapPage_nvarchar)

(五) 通过开启3604跟踪和DBCC PAGE命令查询数据的存储信息;

/******* Step4:查看各表数据页大小*******/
--开启跟踪
DBCC TRACEON(3604)
--查看表信息
DBCC IND(RecordSize_DB,HeapPage_char,-1)

上面的命令会返回如下图所示的信息:

(Figure5:堆表HeapPage_char)

上图返回结果都会因为每次创建而有所不同,你需要根据每次不同的值来填写下面的DBCC命令中的参数。上图第一行记录为IAM page,第二行为data page(数据页),这里是查看数据页的内容,在参数中填入PageFID=1和PagePID=80

--查看PAGE信息
DBCC PAGE(RecordSize_DB,1,80,1)

执行上面的DBCC PAGE命令将返回下面信息:

DATA:
Slot 0, Offset 0x60, Length 21, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 21 Memory Dump @0x6523C060 00000000: 10001200 01000000 58582020 20202020 †........XX
00000010: 20200200 00†††††††††††††††††††††††††† ... Slot 1, Offset 0x75, Length 21, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 21 Memory Dump @0x6523C075 00000000: 10001200 02000000 58585858 20202020 †........XXXX
00000010: 20200200 00†††††††††††††††††††††††††† ... Slot 2, Offset 0x8a, Length 21, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 21 Memory Dump @0x6523C08A 00000000: 10001200 03000000 58585858 20202020 †........XXXX
00000010: 20200200 00†††††††††††††††††††††††††† ... OFFSET TABLE:
Row - Offset
2 (0x2) - 138 (0x8a)
1 (0x1) - 117 (0x75)
0 (0x0) - 96 (0x60)

从上面的信息我们可以知道3条记录的长度都是:Record Size = 21,这个值是怎么算出来的呢?公式可以参考:估计堆的大小

计算公式:Row_Size = Fixed_Data_Size + Variable_Data_Size + Null_Bitmap + 4

= 所有固定长度列的总字节大小 + 可变长度数据的大小 + Null位图 + 数据行的行标题开销

计算过程:

Fixed_Data_Size = 所有固定长度列的总字节大小 = 4+10(int大小为4个字节,char(10)固定大小为10个字节)

Variable_Data_Size = 2 + (Num_Variable_Cols x 2) + Max_Var_Size

  Num_Variable_Cols = 可变长度列数 = 0(没有可变长度列)

  Max_Var_Size = 所有可变长度列的最大总字节大小 = 0

Variable_Data_Size = 0(如果没有可变长度列,请将 Variable_Data_Size 设置为 0)

Null_Bitmap = 2 + ((Num_Cols + 7) / 8)

  Num_Cols = 总列数(固定长度和可变长度)= 2(id、names两个列)

  Null_Bitmap = 3(只保留整数部分,放弃所有余数)

Row_Size = Fixed_Data_Size + Variable_Data_Size + Null_Bitmap + 4

  = 14 + 0 + 3 +4 = 21(与上面Record Size = 21符合)

(六) 接下来,我们来看看HeapPage_nchar表的存储大小会有什么不同:

--查看表HeapPage_nchar
DBCC ind(RecordSize_DB,HeapPage_nchar,-1)

(Figure6:堆表HeapPage_nchar)

--查看PAGE信息
DBCC PAGE(RecordSize_DB,1,90,1)
DATA:
Slot 0, Offset 0x60, Length 31, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 31 Memory Dump @0x61ADC060 00000000: 10001c00 01000000 58005800 20002000 †........X.X. . .
00000010: 20002000 20002000 20002000 020000†††† . . . . . .... Slot 1, Offset 0x7f, Length 31, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 31 Memory Dump @0x61ADC07F 00000000: 10001c00 02000000 58005800 58005800 †........X.X.X.X.
00000010: 20002000 20002000 20002000 020000†††† . . . . . .... Slot 2, Offset 0x9e, Length 31, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 31 Memory Dump @0x61ADC09E 00000000: 10001c00 03000000 58005800 58005800 †........X.X.X.X.
00000010: 20002000 20002000 20002000 020000†††† . . . . . .... OFFSET TABLE: Row - Offset
2 (0x2) - 158 (0x9e)
1 (0x1) - 127 (0x7f)
0 (0x0) - 96 (0x60)

套用上面的公式:

计算公式:Row_Size = Fixed_Data_Size + Variable_Data_Size + Null_Bitmap + 4

= 所有固定长度列的总字节大小 + 可变长度数据的大小 + Null位图 + 数据行的行标题开销

计算过程:

Fixed_Data_Size = 所有固定长度列的总字节大小 = 4+10*2(int大小为4个字节,nchar(10)固定大小为20个字节)

Variable_Data_Size = 2 + (Num_Variable_Cols x 2) + Max_Var_Size

  Num_Variable_Cols = 可变长度列数 = 0(没有可变长度列)

  Max_Var_Size = 所有可变长度列的最大总字节大小 = 0

Variable_Data_Size = 0(如果没有可变长度列,请将 Variable_Data_Size 设置为 0)

Null_Bitmap = 2 + ((Num_Cols + 7) / 8)

  Num_Cols = 总列数(固定长度和可变长度)= 2(id、names两个列)

  Null_Bitmap = 3(只保留整数部分,放弃所有余数)

Row_Size = Fixed_Data_Size + Variable_Data_Size + Null_Bitmap + 4

  = + 0 + 3 +4 = 31(与上面Record Size = 31符合)

  总结:从上面测试结果来看:char(10)与nchar(10)是固定长度数据类型,无论你保存多少内容,在数据页中会为记录保留固定长度的空间,而char(10)与nchar(10)的区别是nchar(10)占用的空间是char(10)的两倍;

(七) 接下来,我们来看看HeapPage_nvarchar表的存储大小又有什么不同:

--查看表HeapPage_nvarchar
DBCC ind(RecordSize_DB,HeapPage_nvarchar,-1)

(Figure7:堆表HeapPage_nvarchar)

--查看PAGE信息
DBCC PAGE(RecordSize_DB,1,94,1)
DATA:
Slot 0, Offset 0x60, Length 19, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 19
Memory Dump @0x61ADC060 00000000: 30000800 01000000 02000001 00130058 †0..............X
00000010: 005800†††††††††††††††††††††††††††††††.X. Slot 1, Offset 0x73, Length 23, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 23
Memory Dump @0x61ADC073 00000000: 30000800 02000000 02000001 00170058 †0..............X
00000010: 00580058 005800††††††††††††††††††††††.X.X.X. Slot 2, Offset 0x8a, Length 23, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 23
Memory Dump @0x61ADC08A 00000000: 30000800 03000000 02000001 00170058 †0..............X
00000010: 00580058 005800††††††††††††††††††††††.X.X.X. OFFSET TABLE: Row - Offset
2 (0x2) - 138 (0x8a)
1 (0x1) - 115 (0x73)
0 (0x0) - 96 (0x60)

套用上面的公式:

计算公式:Row_Size = Fixed_Data_Size + Variable_Data_Size + Null_Bitmap + 4

= 所有固定长度列的总字节大小 + 可变长度数据的大小 + Null位图 + 数据行的行标题开销

计算过程:

Fixed_Data_Size = 所有固定长度列的总字节大小 = 4(int大小为4个字节)

Variable_Data_Size = 2 + (Num_Variable_Cols x 2) + Max_Var_Size

  Num_Variable_Cols = 可变长度列数 = 1(names列的数据类型是nvarchar)

  Max_Var_Size = 所有可变长度列的最大总字节大小 = 第一行记录是2*2;第二、三行记录是2*4

  第一行记录Variable_Data_Size = 2 + (1 * 2) + (2*2) = 8

  第二、三行记录Variable_Data_Size = 2 + (1 * 2) + (2*4) = 12

Null_Bitmap = 2 + ((Num_Cols + 7) / 8)

  Num_Cols = 总列数(固定长度和可变长度)= 2(id、names两个列)

  Null_Bitmap = 2 + ((2 + 7) / 8) = 3(只保留整数部分,放弃所有余数)

Row_Size = Fixed_Data_Size + Variable_Data_Size + Null_Bitmap + 4

  第一行记录Row_Size = 4 + 8 + 3 + 4 = 19(与上面第一行记录Record Size = 19符合)

  第二、三行记录Row_Size = 4 + 12 + 3 + 4 = 23(与上面第二、三行记录Record Size = 23符合)

  总结:从上面结果来看:nvarchar(10)是变长度数据类型,你输入字符串有多少就存储多少内容,这就是定长数据类型与变长数据类型的区别,从第一行记录(占用19 Bytes)与第二、三行记录(占用23 Bytes)占用了不同的数据空间可以证实这一点;而nchar(10)与nvarchar(10)一样,都属于Unicode编码,所以占用的空间是非Unicode编码的2倍;

五.参考文献(References)

估计堆的大小

估计数据库的大小

SQL SERVER单页数据存储行数计算

SQL Server页中行物理存储

SQL Server计算数据库中表、堆、聚集索引和非聚集索引的大小

SQL Server 2008连载之存储结构

Stairway to SQL Server Indexes(中文:SQL Server索引进阶

SQL Server 堆表行存储大小(Record Size)的更多相关文章

  1. SQL Server 堆表与栈表的对比(大表)

    环境准备 使用1个表,生成1000万行来进行性能对比(勉强也算比较大了),对比性能差别. 为了简化过程,不提供生成随机数据的过程.该表初始为非聚集索引(堆表),测试过程中会改为聚集索引(栈表). CR ...

  2. 一种快速统计SQL Server每个表行数的方法

    转载自:http://www.cnblogs.com/kenyang/archive/2013/04/09/3011447.html 我们都知道用聚合函数count()可以统计表的行数.如果需要统计数 ...

  3. SQL Server索引 (原理、存储)聚集索引、非聚集索引、堆 <第一篇>

    一.存储结构 在SQL Server中,有许多不同的可用排列规则选项. 二进制:按字符的数字表示形式排序(ASCII码中,用数字32表示空格,用68表示字母"D").因为所有内容都 ...

  4. SQL Server创建表超出行最大限制解决方法

    问题的现象在创建表A的时候,出现“信息 511,级别 16,状态 1,第 5 行  无法创建大小为 的行,该值大于允许的最大值 8060.”的信息提示.很奇怪,网上查了一下,是因为要插入表的数据类型的 ...

  5. 统计sql server 2012表的行数

    --功能:统计sql server 2012表的行数 SELECT a.name, a.object_id, b.rows, b.index_id FROM sys.tables AS a INNER ...

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

    标签:SQL SERVER/MSSQL SERVER/数据库/DBA/索引体系结构/非聚集索引 概述 非聚集索引与聚集索引具有相同的 B 树结构,它们之间的显著差别在于以下两点: 基础表的数据行不按非 ...

  7. 千万级SQL Server数据库表分区的实现

    千万级SQL Server数据库表分区的实现 2010-09-10 13:37 佚名 数据库 字号:T | T 一般在千万级的数据压力下,分区是一种比较好的提升性能方法.本文将介绍SQL Server ...

  8. SQL Server 深入解析索引存储(非聚集索引)

    标签:SQL SERVER/MSSQL SERVER/数据库/DBA/索引体系结构/非聚集索引 概述 非聚集索引与聚集索引具有相同的 B 树结构,它们之间的显著差别在于以下两点: 基础表的数据行不按非 ...

  9. Sql server 系统表

    sql server系统表详细说明 SQL Server 用户库中系统表说明 名称 说明 备注 syscolumns 每个表和视图中的每列在表中占一行,存储过程中的每个参数在表中也占一行.   sys ...

随机推荐

  1. 关于android 加载https网页的问题

    我在加载https网页时出现空白, 因此,我就百度一下,可以发现: webView.setWebViewClient(new WebViewClient(){ @Override public voi ...

  2. STDIN(0), STDOUT(1), STDERR(2), 2 > &1

    当我们在 shell 中执行命令的时候,每个进程都和三个打开的文件相联系,并使用文件描述符(文件描述符是一个简单的整数,用以标明每一个被进程所打开的文件和socket.第一个打开的文件是0,第二个是1 ...

  3. 【SAP BO】无法识别账户信息:无法访问CMS。计算机上的CMS由于某个严重错误而停止。(FWM 20031)

    1.系统环境 OS:Windows Server 2008 R2 RDBMS:Oracle 11g R2(Server.Client同时存在) BI:SAP Business Objects 4.2 ...

  4. tungsten抽取和应用mysql binlog

    首先举例说明 api的基本使用方式 首先进行配置 , 可以看到源数据库和目的数据库 TungstenProperties tp=new TungstenProperties(); tp.setStri ...

  5. 使用canal分析binlog(二) canal源码分析

    在能够跑通example后有几个疑问 1. canal的server端对于已经读取的binlog,client已经ack的position,是否持久化,保存在哪里 2. 即使不启动zookeeper, ...

  6. jmobile学习之路 ----检测屏幕宽度

    <script type="text/javascript"> window.onresize = function(){ var myh1 = document.ge ...

  7. Markdown 语法总结

    Markdown 语法总结 Markdown是一个神奇的语言,他比html简单,它巧妙地将内容和格式结合起来.很多平台支持Markdown语法编辑,比如github.博客园等. 下面总结一Markdo ...

  8. cocos2dx 实现flappybird

    前两天在博客园看到网友实现的一个网页版的flappy bird,挂在360游戏平台,玩了一会儿得分超低,就很想自己做一个.刚好这两天炫舞的活都清了,就弄一下玩玩. 效果图 布局类GameScene.h ...

  9. 50个jQuery插件可将你的网站带到另一个高度

    Web领域一直在发生变化并且其边界在过去的每一天都在发生变化(甚至不能以小时为计),随着其边界的扩展取得了许多新发展.在这些进步之中,开发者的不断工作创造了更大和更好的脚本,这些脚本以插件方式带来更好 ...

  10. android——自定义listView

    都知道微信主机面 有个界面会一行一一行的聊天记录,那个效果就可以用listview来实现(当然这只是其中的一种) listView是一种比较常见的组件它用来展示列的view,它是根据数据的长度来显示数 ...