这篇文章我想谈下空值位图掩码(NULL bitmap mask) ,并揭开它的神秘面纱。空值位图掩码是在存储引擎层为列是否存储NULL值进行编码。它是一个简单的位图掩码,如果值是1的话,表示这列有NULL值,如果是0的话,表示这列有具体的值(非NULL值)。

这样的解释听起来非常直接,但当我们进一步分析时,用这个方法还是有启发性的。首先我们来看看数据行的组合结构,这是存储引擎用来在磁盘上存储记录的结构。结构如下图所示:

这个格式被称为定长变量格式(FixedVar format),因为SQL Server总是先存储定长列(像INT,CHAR),再存储变长列(像VARCHAR)。从图中我们可以看到,SQL Server以存储2 bytes的状态位开始,接着用2 bytes存储由行头到定长列结尾长度(包含所有定长列数据)。然后用2 bytes存储列个数,紧随其他的就是真正的空值位图掩码(NULL bitmap mask)

所有的一切第一眼看起来都很合理,但我们再仔细看下的话,我们就开始思考,并且你可能会问:为什么SQL Server在每条数据行里存储具体的列数?对于每条数据行,列数都不是一样的么?为什么SQL Server要存储这些冗余的数据?

第1个答案是非常简单和有逻辑性的:SQL Server需要列数来计算用作实际空值位图掩码(NULL bitmap mask)的字节数。小于等于8列的表需要1 byte,9到16列需要 2 bytes,17到24列需要3 bytes,以此类推。明白了么?但在表里每条记录的列数必须是一样的

我们来看第2个用实例分析的技术性正确答案:首先,你要知道空值位图掩码(NULL bitmap mask)是用在数据引擎级别,即当前记录中的列数。这就是说SQL Server在物理行可以存储不同数量的列。额,好像说的有点含糊不清…………物理数据行列数和表元数据层(sys.dolumns)里列数并不一致。这些也是SQL Server内部的真正不同层级。

因此在什么情况下这些层级间会彼此不相等呢?很简单:当你往表里增加列的时候!如果你加的列是NULL还是NOT NULL,SQL Server会作出完全不同的区别。当你增加一个新的NULL列到表时,SQL Server只更新表元数据层,一点也不接触到存储引擎层。也就是说当你增加一个NULL列时,所有的记录物理存储上不发生任何改变。另一方面,当你增加一个NOT NULL列时,SQL Server会更新表元数据层,同时也会更新存储引擎层,这就是说,SQL Server会接触并重写表里的每一条记录,在那里你增加了一个NOT NULL列。这会带来性能上的巨大区别!因此SQL Server需要在每条数据记录里存储具体的列数,因为这里的列数不能和表元数据层的列数同步。

我们来拿具体的例子来详细分析下。这个例子我们创建了一个简单的含8列的表,SQL Server需要使用1 bytes来作为空值位图掩码(NULL bitmap mask)

 CREATE TABLE TestTable
(
Column1 INT IDENTITY(1, 1) NOT NULL,
Column2 CHAR(600) NOT NULL,
Column3 CHAR(600) NOT NULL,
Column4 CHAR(600) NOT NULL,
Column5 CHAR(600) NOT NULL,
Column6 VARCHAR(600) NOT NULL,
Column7 VARCHAR(600) NOT NULL,
Column8 VARCHAR(600) NOT NULL
)
GO

然后,我们往表里插入2条记录:

 INSERT INTO TestTable VALUES
(
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600)
),
(
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600)
)
GO

我们通过DBCC PAGE命令查看下具体的数据页:

 DBCC IND(ALLOCATIONDB, TestTable, -1)
GO

 DBCC TRACEON(3604)
GO
DBCC PAGE(ALLOCATIONDB, 1, 24993, 1)
GO DBCC TRACEON(3604)
GO
DBCC PAGE(ALLOCATIONDB, 1, 24995, 1)
GO

可以看到,每条记录的长度是 4129 bytes(4204 bytes 数据+ 7 bytes 行开销+ 2 bytes 变长列个数 + 3 * 2 bytes 每个变长列结束位置的偏移量)。

现在我们往表里加一个新的NULL列:

 ALTER TABLE TestTable ADD Column9 CHAR(600) NULL
GO

这是表里的第9列,也就是说SQL Server对于这个列数需要2 bytes。但是 SQL Server并不在存储引擎层改变物理数据行,因为我们只加了一个NULL列。SQL Server不需要在存储引擎层做任何处理。我们可以通过查看数据页来验证下:

记录还是同样4219 bytes的长度,但是我们逻辑上已经在表上加了1列。现在我们来更新表的1条记录,这样的话,新加列就有具体值了:

 UPDATE TestTable SET Column9 = REPLICATE('', 600)
WHERE Column1 = 1
GO

当你查看表里第2条记录的数据页时,记录大小还是原来的4219 bytes。

 DBCC TRACEON(3604)
GO
DBCC PAGE(ALLOCATIONDB, 1, 24995, 1)
GO

你现在创建了一个场景:SQL Server在数据行内部存储了不同长度的空值位图掩码(NULL bitmap mask)。这就是说你有定长列的表,在存储引擎级别,却有不同长度的行大小!很有趣,是不是?

现在我们删除表并重建,继续往表里插入2条记录:

 DROP TABLE dbo.TestTable

 CREATE TABLE TestTable
(
Column1 INT IDENTITY(1, 1) NOT NULL,
Column2 CHAR(600) NOT NULL,
Column3 CHAR(600) NOT NULL,
Column4 CHAR(600) NOT NULL,
Column5 CHAR(600) NOT NULL,
Column6 VARCHAR(600) NOT NULL,
Column7 VARCHAR(600) NOT NULL,
Column8 VARCHAR(600) NOT NULL
)
GO INSERT INTO TestTable VALUES
(
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600)
),
(
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600),
REPLICATE('', 600)
)
GO

现在我们往表里增加一个NOT NULL列:

 ALTER TABLE TestTable ADD Column9 CHAR(600) NOT NULL
DEFAULT REPLICATE('', 600)
GO

现在,SQL Server需要在存储引擎层改变每条记录,因为新列的默认值必须被增加(当你表里已经有记录存储时,新加列必须要定义一个默认值),而且SQL Server需要扩展空值位图掩码(NULL bitmap mask)

 DBCC IND(ALLOCATIONDB, TestTable, -1)
GO DBCC TRACEON(3604)
GO
DBCC PAGE(ALLOCATIONDB, 1, 24993, 1)
GO DBCC TRACEON(3604)
GO
DBCC PAGE(ALLOCATIONDB, 1, 24995, 1)
GO

当你处理大表,给表增加NOT NULL列时,这个现象会导致严重的性能问题。想象下我们往表里插入100万条记录。当我们增加NULL列时,SQL Server只需要几毫秒,因为只进行元数据修改操作。但当我们往表里增加NOT NULL列时,SQL Server待ALTER TABLE操作完成需要花费40秒!在处理大表,往表里增加NOT NULL列,这的确是个非常严重的性能降级!!

希望你现在已经理解了为什么SQL Server在存储引擎层对每条记录存储具体的列数,还有在SQL Server里,当你往大表里增加NOT NULL列,会出现严重的性能问题。

参考文章:

https://www.sqlpassion.at/archive/2011/06/29/the-mystery-of-the-null-bitmap-mask/

探秘空值位图掩码(NULL bitmap mask)的更多相关文章

  1. Flash 矢量图和位图性能对比 导出为位图/缓存为位图 export as bitmap / cache as bitmap

     大家都知道Flash处理矢量图比位图要慢,而具体的性能上对比也有不少的前人已经做过.http://bbs.9ria.com/forum.php?mod=viewthread&tid=2282 ...

  2. 25-12 空值处理(null值)

    --------------------空值处理--------------------- select * from TblStudent --查询所有年龄是null的同学学习信息 --null值无 ...

  3. JS 中判断空值 undefined 和 null

    1.JS 中如何判断 undefined JavaScript 中有两个特殊数据类型:undefined 和 null,下节介绍了 null 的判断,下面谈谈 undefined 的判断. 以下是不正 ...

  4. SQL Server 存储(2/8):理解数据记录结构

    在SQL Server :理解数据页结构我们提到每条记录都有7 bytes的系统行开销,那这个7 bytes行开销到底是一个什么样的结构,我们一起来看下. 数据记录存储我们具体的数据,换句话说,它存在 ...

  5. SQL Server :理解数据记录结构

    原文:SQL Server :理解数据记录结构 在SQL Server :理解数据页结构我们提到每条记录都有7 bytes的系统行开销,那这个7 bytes行开销到底是一个什么样的结构,我们一起来看下 ...

  6. SQLSERVER中NULL位图的作用

    SQLSERVER中NULL位图的作用 首先感谢宋沄剑提供的文章和sqlskill网站:www.sqlskills.com,看下面文章之前请先看一下下面两篇文章 SQL Server误区30日谈-Da ...

  7. bitmap位图法

    位图法定义 位图法就是bitmap的缩写,所谓bitmap,是用每一位来存放某种状态,适用于大规模数据,但数据状态又不是很多的情况.通常是用来判断某个数据存不存在的. 例如,要判断一千万个人的状态,每 ...

  8. Android Bitmap(位图)详解

    一.背景 在Android开发中,任何一个APP都离不开图片的加载和显示问题.这里的图片来源分为三种:项目图片资源文件(一般为res/drawable目录下的图片文件).手机本地图片文件.网络图片资源 ...

  9. Android笔记--Bitmap(三) 针对不用Android版本的位图管理

    Bitmap(三) | Android不同版本的相应操作 在不同的Android版本中.位图的存储方式是不同的. 1.小于等于 Android 2.2 (API level 8) 垃圾收集器回收内存时 ...

随机推荐

  1. Android 代码混淆、第三方平台加固加密、渠道分发 完整教程(图文)

    第一步:代码混淆(注意引入的第三方jar) 在新版本的ADT创建项目时,混码的文件不再是proguard.cfg,而是project.properties和proguard-project.txt. ...

  2. jackson readTree

    String jsonstr = "{\"msg\":{\"head\":{\"version\":\"1.0\&quo ...

  3. spring整合activemq发送MQ消息[queue模式]实例

    queue类型消息 pom依赖 <dependency> <groupId>junit</groupId> <artifactId>junit</ ...

  4. bash的循环中无法保存变量

    在bash中,如果循环在一个子shell中运行,那么在循环中对循环外面的变量的更改将在循环退出后不可见.像下面的例子: #!/bin/sh python run.py | while read lin ...

  5. iis7下iis安全狗不能用怎么办(安装iis6兼容性)

    is7下iis安全狗不能用怎么办 | 浏览:126 | 更新:2015-05-14 17:11 1 2 3 4 5 6 分步阅读 windows系统iis安全狗是常用的一款软件,针对网站使用很方便.在 ...

  6. Creating Apps With Material Design —— Creating Lists and Cards

    转载请注明 http://blog.csdn.net/eclipsexys 翻译自Developer Android.时间仓促,有翻译问题请留言指出,谢谢 创建Lisst和Cards 在你的应用程序创 ...

  7. 为MFC界面添加一个Log Window

    前言 由于早期的图像处理程序基于VC6.0,MFC也是采用VC6.0开发的.在实际处理中,我不仅需要界面的显示,有很多时候,我需要算法处理的过程中的信息,比如每个阶段的耗时,处理的图像大小,以及如果需 ...

  8. html5网页动画总结--jQuery旋转插件jqueryrotate

    CSS3 提供了多种变形效果,比如矩阵变形.位移.缩放.旋转和倾斜等等,让页面更加生动活泼有趣,不再一动不动.然后 IE10 以下版本的浏览器不支持 CSS3 变形,虽然 IE 有私有属性滤镜(fil ...

  9. jQuery实现菜单点击隐藏(上下左右)

    canrun <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www. ...

  10. iOS8设置应用图标红点的权限问题

    在ios8中,直接设置application badge value会出错 1 [[UIApplication sharedApplication] setApplicationIconBadgeNu ...