一个字节造成的巨大性能差异——SQL Server存储结构

今天同事问了我一个SQL的问题,关于SQL Server内部存储结构的,我觉得挺有意思,所以写下这篇博客讨论并归纳了一下。问题是这样的:

首先我们创建两张表,一张表的列长度是4039字节,另一张表的长度是4040字节,他们就只有一个字节的差距,比如以下创建表的SQL:

CREATE TABLE tb4039(c1 INT IDENTITY,c2 char(4035) not null)
CREATE TABLE tb4040(c1 INT IDENTITY,c2 char(4036) not null)

 

由于INT类型是4个字节,所以我们创建的tb4039表有4+4035=4039个字节的长度,tb4040中的c2字段比tb4039中的c2字段多了一个字节,总长度是4040字节,其他没有区别了。接下来是向这两个表中插入数据,比如插入100条数据,SQL语句是:


DECLARE @i INT
SET @i=1
WHILE @i<=100
BEGIN
    INSERT INTO tb4039 (c2) VALUES('test'+CONVERT(VARCHAR(5),@i));
    INSERT INTO tb4040 (c2) VALUES('test'+CONVERT(VARCHAR(5),@i));
    SET @i=@i+1
END

 

好,现在我们使用SSMS来查看一下这两个表的空间占用量,如果是SQL2005,那么可以使用SSMS自带的报表查看,如果是SQL2008,那么直接使用对象资源管理器详细信息界面进行查看(如果使用的是SQL2008而不知道怎么查看表空间使用量那么请查看我以前写的一篇博客:SQL Server 2008新特性之SSMS增强)。我这里使用的是SQL2008,查看到的情况如图:

当然,我们也可以使用T-SQL来查询系统视图,得出这两个表的数据占用的空间,查询代码为:

SELECT OBJECT_NAME(i.object_id) AS TableName,data_pages*8 AS DataSize --这里返回的是数据页个数,1页是8K,所以乘以8
FROM sys.indexes as i
JOIN sys.partitions as p ON p.object_id = i.object_id and p.index_id = i.index_id
JOIN sys.allocation_units as a ON a.container_id = p.partition_id
where i.object_id=OBJECT_ID('tb4039') OR i.object_id=OBJECT_ID('tb4040')

 

系统返回结果:

TableName    DataSize
tb4039           400
tb4040           800

和我们通过报表或者SSMS查看到的结果相同,两个表只相差了一个字节,可是一个占用了400K的存储空间,另一个却占用了800K的存储空间,是另一个表的双倍!!!

一个字节的差距就造成了存储空间成倍的增加,为什么会这样呢?这就要从SQL Server存储结构讲起。

------------------------------------------------华丽的分割线,进入主题-----------------------------------------------------------------

SQL Server最小的存储单位是页(Page),一个页的大小是8K=8192字节。一个数据页是由3部分组成:页头、数据行和行偏移矩阵,具体结构如图:

页头保存了页的编号、上一页ID、下一页ID、可以字节数等等关于该页的基本信息。页头的大小是固定的96个字节,所以剩下8192-96=8096个字节用于存储数据行和行偏移矩阵。

行偏移矩阵在页的最后面,而且是倒序排列的,使用2个字节来表示数据行在页面内部的偏移量,有1行数据则行偏移矩阵的大小是2字节,有2行数据则行偏移矩阵的大小是4字节,以此类推。

除了页头占用的空间和行偏移矩阵占用的空间,中间剩下的空间就是给数据行使用的。假设我们要在一个页中保存2行数据,那么这2行数据可以使用8096-4=8092个字节的空间,也就是说1行数据可以使用8092/2=4046个字节的空间。这里的4046个字节并不是完全都用来保存数据行,一个数据行中还存在其他的信息用于表示该行数据,具体的结构是这样的:

状态位A 状态位B 定长数据类型的长度 定长数据的内容 列数 NULL位图 变长列的个数 变长列的偏移矩阵 变长列的数据
1字节 1字节 2字节 具体定长数据字节 2字节
列数/8个字节 2字节 变长列个数*2个字节 具体变长数据字节

不管我们对表的定义是多么的简单,一行数据除了数据自身占用的空间外,至少还要占用1+1+2+2+1=7个字节。如果定义的数据列很多,或者里面有变长数据列,那么占用的空间可能会更多。

现在回到我们前面讲到的2个表tb4039和tb4040,要存储tb4039中的一行数据需要1+1+2+4039+2+1=4046字节,所以正好可以在一个页中保存2行数据。所以插入了100行数据,实际上是保存在50个数据页中,大小就是8K*50=400K。而对于tb4040表,要存储一行数据需要4047个字节,没法在一个页中保存2行数据,所以一行数据就占用一个数据页,100行数据占用了100个数据页,大小就是8K*100=800K。

--------------------------------------------做了一堆加减乘除,下面总结下--------------------------------------------

这里只是举了一个极端的例子,所以造成了一个字节的偏差而使占用的存储空间翻倍,在实际应用中很少会出现这么极端的情况,但是很有可能使一个页存储5条数据的因为某个列多了1个2个字节所以只能存储4条数据。也许大家认为少存一条数据并没有什么,但是在数据量变的非常庞大以后一页4条数据和一页5条数据将会产生明显的性能差异。使得一页中存放更多的数据并不是为了节约存储成本,现在的硬盘已经很便宜了很多服务器都是几百个G的硬盘,本来5G的数据现在变长了10G,相对几百个G上T的硬盘来说又算得了什么。

实际上我们要让一个数据页中存放更多行的数据主要是出于性能的考虑。SQL Server进行数据库读写操作的基本单位是页,如果一页中存放了更多的数据,那么对表进行扫描和查找时进行的IO操作将减少,毕竟IO操作是非常消耗时间影响性能的。假设tb4039中有100W条数据,那么进行全表扫描就要读取50W个数据页,如果读取10W个数据页花费1秒钟,那么对表tb4039进行扫描需要花费5秒钟时间,而如果是使用tb4040存储这100W条数据,进行全表扫描则需要读取100W个数据页,总共花费10秒钟时间。就一个字节的差别,一个是5秒另一个是10秒,对性能的影响非常明显。

为了提高数据库查询的性能,在表设计时可以遵循以下建议:

  • 主键尽可能的短,能用tinyint的就不要用int,能用char(5)的就不要用成varchar(50)。
  • 计算好表列的长度,能够在一个页中存放5条数据的,那就不要将字段设置的太长使得一个页中只能存放3条或者4条数据。
  • 尽量将字段设置为不允许为NULL,因为NULL值在存储和数据处理时系统需要专门的处理,降低了性能。
  • 能够用固定长度的就不要用变长字段,比如身份证号就可以使用CHAR(18),而不应该使用VARCHAR(18)。
  • 不要在一个表中建立太多的列,如果一个实体的属性太多时可以考虑进行垂直分割,将常用的字段放在一个表,不常用的字段放另外的表,这样可以减小常用字段表中数据列占用的空间,使得一个数据页中存储更多的数据行。
  • 不要将大对象、长字符串和常用的字段放在同一个表中。同样还是出于性能上的考虑,比如有个产品表,里面有产品ID、产品名字、产品售价、产品图片、产品描述等字段,那么我们可以将产品ID、产品名字、产品售价这几个常用的而且占用空间小的列放在一个表,然后建立产品ID、产品图片、产品描述这样的表,通过外键约束的方式将大对象数据和长字符串数据放在另一个表中。
时间: 2024-10-31 01:42:08

一个字节造成的巨大性能差异——SQL Server存储结构的相关文章

SQL Server存储图像数据的机制介绍

本文介绍MIS SQL Server对图像数据的存储机制和存取方法.针对VB开发工具,介绍了一种通过ADO Field 对象的GetChunk 方法和AppendChunk 方法来存取MIS SQL Server中的图像数据的方法. 在一个完善的医院信息MIS中,图像数据的存取是必不可少的,比如X光片.CT像片的保存.一方面,这些图像数据在远程诊疗为准确诊断病情提供了重要的依据,另一方面,也为快速查阅病人资料提供了基本条件.图像数据的存取在其它应用系统如GIS中也有广泛的应用. 1.SQL Se

SQL Server 存储引擎-剖析Forwarded Records

原文:SQL Server 存储引擎-剖析Forwarded Records 我们都知道数据在存储引擎中是以页的形式组织的,但数据页在不同的组织形式中其中对应的数据行存储是不尽相同的,这里通过实例为大家介绍下堆表的中特有的一种情形Forwared Records及处理方式. 概念 堆表中,当对其中的记录进行更新时,如果当前数据页无法满足更新行的容量,此时这行记录将会转移到新的数据页中,而原数据页中将会留下指针(文件号,页号,槽号)链接到新的数据页中. Code 创建测试数据 create dat

六步走战略,助您实现SQL Server存储扩展性提升

  打算对SQL Server存储进行优化?以下六项提示将帮助大家充分利用自己的宝贵资源. SQL Server可扩展性是否已经成为系统优先级列表中的顶端选项?您是否正在努力寻找对SQL Server数据库性能加以优化的途径?也许向外扩展并不是最理想的选择,那么大家不妨遵循以下六个步骤,相信能够更为轻松高效地实现业务规模提升: 1)尽量利用现有SQL Server组件 扩展SQL Server是一项繁琐而耗时的工作,涉及多个系统.分区数据库.内存.存储.CPU以及网络适配器.我们不妨先从最大程度

性能测试:自建数据库与RDS性能对比SQL Server案例排查分析

近期经常遇到用户将自建数据库与RDS进行对比,简单的对比结果是自建库比RDS实例查询快.我们这里来看看一个实例,有一家物流公司,刚开始使用RDS SQL Server数据库,发现通过ECS访问RDS实例,执行语句需要60s左右,但是访问ECS本地自建库只需要2-3s.那么RDS是否是真的不如自建数据库呢? 接下来,我们来探讨对比自建库和RDS的正确姿势,如何公平地对比自建库和RDS的性能. 对比自建库和RDS的语句执行性能,下面这些因素必须都考虑到: 1. 可用区和网络链路. 可用区.网络链路的

SQL Server存储图像数据的策略与方法

server|策略|数据 目前对于图像数据的管理大都采用表+实体的方法,即图像数据以文件形式存放于指定的计算机目录下,在数据库表中只反映图像数据文件的存储路径.这种管理模式,给数据的维护增加了难度,同时,也给数据的安全带来一定的隐患.因此,要真正做到各类数据在数据库中安全管理,研究和探索直接将图像数据存储在数据库关系表中的方法是非常必要的. 笔者在Visual Basic 6.0开发环境中,采用客户机/服务器的工作方式,针对SQL Server数据库关系表中存储图像数据的问题进行了初步探讨,提出

SQL Server存储引擎容量的规划技巧

概述 巧妙的规划是实现关系型数据库管理系统(RDBMS)的基础.要满足对更多存储容量的要求,对更快地取得信息的要求,它是唯一方法.想从Microsoft SQL Server 7.0中获得最多,要求组织机构了解它的关键部件--存储引擎--内在和外在.本文是存储引擎结构的高级指南,推荐了配置参数,SQL Server的最佳硬件,以及通过文件和文件组存储大量数据的创新方法.本文也概述了SQL Server 7.0存储引擎的新的动态特性,它使雇员花最少的努力公司数据库应用程序. 介绍 十年前,数据库应

SQL Server 索引结构及其使用(二) 改善SQL语句第1/3页_MsSql

比如: select * from table1 where name=''zhangsan'' and tID > 10000 和执行: select * from table1 where tID > 10000 and name=''zhangsan'' 一些人不知道以上两条语句的执行效率是否一样,因为如果简单的从语句先后上看,这两个语句的确是不一样,如果tID是一个聚合索引,那么后一句仅仅从表的10000条以后的记录中查找就行了:而前一句则要先从全表中查找看有几个name=''zhan

SQL Server 索引结构及其使用(一)--深入浅出理解索引结构第1/4页_MsSql

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

SQL Server 索引结构及其使用(二) 改善SQL语句第1/3页

比如: select * from table1 where name=''zhangsan'' and tID > 10000 和执行: select * from table1 where tID > 10000 and name=''zhangsan'' 一些人不知道以上两条语句的执行效率是否一样,因为如果简单的从语句先后上看,这两个语句的确是不一样,如果tID是一个聚合索引,那么后一句仅仅从表的10000条以后的记录中查找就行了:而前一句则要先从全表中查找看有几个name=''zhan