【转】对于表列数据类型选择的一点思考

原文链接:http://www.cnblogs.com/CareySon/archive/2012/06/14/ChoiceOfDataTypeWhenDesignTable.html

简介

SQL Server每个表中各列的数据类型的选择通常显得很简单,但是对于具体数据类型的选择的不同对性能的影响还是略有差别。本篇文章对SQL Server表列数据类型的选择进行一些探索。

一些数据存储的基础知识

在SQL Server中,数据的存储以页为单位。八个页为一个区。一页为8K,一个区为64K,这个意味着1M的空间可以容纳16个区。如图1所示:

图1.SQL Server中的页和区

如图1(PS:发现用windows自带的画图程序画博客中的图片也不错)可以看出,SQL Server中的分配单元分为三种,分别为存储行内数据的In_Row_Data,存储Lob对象的LOB_Data,存储溢出数据的Row_Overflow_data。下面我们通过一个更具体的例子来理解这三种分配单元。

我建立如图2所示的表。

图2.测试表

图2的测试表不难看出,通过插入数据使得每一行的长度会超过每页所能容纳的最大长度8060字节。使得不仅产生了行溢出(Row_Overflow_Data),还需要存储LOB的页.测试的插入语句和通过DBCC IND看到的分配情况如图3所示。

图3.超过8060字节的行所分配的页

除去IAM页,这1行数据所需要三个页来存储。首先是LOB页,这类是用于存储存在数据库的二进制文件所设计,当这个类型的列出现时,在原有的列会存储一个24字节的指针,而将具体的二进制数据存在LOB页中,除去Text之外,VarBinary(max)也是存在LOB页中的。然后是溢出行,在SQL Server 2000中,一行超过8060字节是不被允许的,在SQL Server 2005之后的版本对这个特性进行了改进,使用Varchar,nvarchar等数据类型时,当行的大小不超过8060字节时,全部存在行内In-row data,当varchar中存储的数据过多使得整行超过8060字节时,会将额外的部分存于Row-overflow data页中,如果update这列使得行大小减少到小于8060字节,则这行又会全部回到in-row data页。

数据类型的选择

在了解了一些基础知识之后。我们知道SQL Server读取数据是以页为单位,更少的页不仅仅意味着更少的IO,还有更少的内存和CPU资源消耗。所以对于数据选择的主旨是:

尽量使得每行的大小更小

这个听起来非常简单,但实际上还需要对SQL Server的数据类型有更多的了解。

比如存储INT类型的数据,按照业务规则,能用INT就不用BIGINT,能用SMALLINT就不用INT,能用TINYINT就不用SMALLINT。

所以为了使每行的数据更小,则使用占字节最小的数据类型。

1.比如不要使用DateTime类型,而根据业务使用更精确的类型,如下表:

类型 所占字节
Date(仅日期) 3
Time(仅时间) 5
DateTime2(时间和日期) 8
DateTimeOffSet(外加时区) 10

2.使用VarChar(Max),Nvarchar(Max),varbinary(Max)来代替text,ntext和image类型

根据前面的基础知识可以知道,对于text,ntext和image类型来说,每一列只要不为null,即使占用很小的数据,也需要额外分配一个LOB页,这无疑占用了更多的页。而对于Varchar(Max)等数据类型来说,当数据量很小的时候,存在In-row-data中就能满足要求,而不用额外的LOB页,只有当数据溢出时,才会额外分配LOB页,除此之外,Varchar(Max)等类型支持字符串操作函数比如:

  • COL_LENGTH
  • CHARINDEX
  • PATINDEX
  • LEN
  • DATALENGTH
  • SUBSTRING

3.对于仅仅存储数字的列,使用数字类型而不是Varchar等。

因为数字类型占用更小的存储空间。比如存储123456789使用INT类型只需要4个字节,而使用Varchar就需要9个字节(这还不包括Varchar还需要占用4个字节记录长度)。

4.如果没有必要,不要使用Nvarchar,Nchar等以“字”为单位存储的数据类型。这类数据类型相比varchar或是char需要更多的存储空间。

5.关于Char和VarChar的选择

这类比较其实有一些了。如果懒得记忆,大多数情况下使用Varchar都是正确的选择。我们知道Varchar所占用的存储空间由其存储的内容决定,而Char所占用的存储空间由定义其的长度决定。因此Char的长度无论存储多少数据,都会占用其定义的空间。所以如果列存储着像邮政编码这样的固定长度的数据,选择Char吧,否则选择Varchar会比较好。除此之外,Varchar相比Char要多占用几个字节存储其长度,下面我们来做个简单的实验。

首先我们建立表,这个表中只有两个列,一个INT类型的列,另一个类型定义为Char(5),向其中插入两条测试数据,然后通过DBCC PAGE来查看其页内结构,如图4所示。

 
    图4.使用char(5)类型,每行所占的空间为16字节

下面我们再来看改为Varchar(5),此时的页信息,如图5所示。

图5.Varchar(5),每行所占用的空间为20字节

因此可以看出,Varchar需要额外4个字节来记录其内容长度。因此,当实际列存储的内容长度小于5字节时,使用char而不是varchar会更节省空间。

关于Null的使用

关于Null的使用也是略有争议。有些人建议不要允许Null,全部设置成Not Null+Default。这样做是由于SQL Server比较时就不会使用三值逻辑(TRUE,FALSE,UNKNOWN),而使用二值逻辑(True,False),并且查询的时候也不再需要IsNull函数来替换Null值。

但这也引出了一些问题,比如聚合函数的时候,Null值是不参与运算的,而使用Not Null+Default这个值就需要做排除处理。

因此Null的使用还需要按照具体的业务来看。

考虑使用稀疏列(Sparse)

稀疏列是对 Null 值采用优化的存储方式的普通列。 稀疏列减少了 Null 值的空间需求,但代价是检索非 Null 值的开销增加。 当至少能够节省 20% 到 40% 的空间时,才应考虑使用稀疏列。

稀疏列在SSMS中的设置如图6所示。

图6.稀疏列

更具体的稀疏列如何能节省空间,请参看MSDN

对于主键的选择

对于主键的选择是表设计的重中之重,因为主键不仅关系到业务模型,更关系到对表数据操作的的效率(因为主键会处于B树的非叶子节点中,对树的高度的影响最多)。关于主键的选择,我之前已经有一篇文章关于这点:从性能的角度谈SQL Server聚集索引键的选择,这里就不再细说了。

----------------------------------------------

IAM=index allocation map page,它用来跟踪哪些盘区是放数据的,哪些盘区是用来放索引的,它本是不存储用户的数据或索引,而是存储并决定用户的数据或索引该存储在哪个盘区!所以,它可以管理多达4GB的空间!

nvarchar存中文还好,存英文一个字符会占两个字节,选择nvarchar中文就不会出现乱码什么的。所以字段只存英文的话,不要考虑nvarchar

总结

本篇文章对于设计表时,数据列的选择进行了一些探寻。好的表设计不仅仅是能满足业务需求,还能够满足对性能的优化。

分类: SQL SERVER

标签: varcharchar数据类型的选择

好文要顶 关注我 收藏该文  

CareySon
关注 - 22
粉丝 - 4858

荣誉:推荐博客

+加关注

34

0

?上一篇:【译】表变量和临时表的比较
?下一篇:SQL Server复制入门(一)----复制简介

posted @ 2012-06-14 16:56 CareySon 阅读(9220) 评论(36) 编辑 收藏

评论列表

回复引用

#1楼 2012-06-14 17:44 ·游民

支持楼主...文章写的不错!
欢迎喜欢深入研究SQL的人加群:214363958

支持(0)反对(0)

回复引用

#2楼 2012-06-14 20:28 盘诚

楼主的数据库基础不是一般的扎实

支持(0)反对(0)

回复引用

#3楼 2012-06-15 08:52 dudu

“除非需要非常小,比如长度小于,此时加上这四个字节,varchar占用的空间一定大于char, 都选择Varchar而不是Char会更节省空间。”
这句话是不是漏掉了一些字?

支持(0)反对(0)

回复引用

#4楼[楼主] 2012-06-15 09:30 CareySon

@ dudu
-.-貌似是,谢谢老大,我改了去

支持(0)反对(0)

回复引用

#5楼 2012-06-15 09:35 英雄饶命啊

“除非需要非常小,比如长度小于,此时加上这四个字节,varchar占用的空间一定大于char, 都选择Varchar而不是Char会更节省空间。”
这句话是不是漏掉了一些字?

支持(0)反对(0)

回复引用

#6楼[楼主] 2012-06-15 09:43 CareySon

@ 英雄饶命啊
我都改了,不能继续吐槽....

支持(0)反对(0)

回复引用

#7楼 2012-06-15 09:52 Hunt

有个疑问想请教一下, 将Date字段分割成年月日多字段之后,在系统进行查询的时候会不会有负担。

支持(0)反对(0)

回复引用

#8楼[楼主] 2012-06-15 09:59 CareySon

@ Hunt
一般情况下可以忽略不计

不过看你以何种数据类型存储了

支持(0)反对(0)

回复引用

#9楼 2012-06-15 11:01 heywap

好文章感谢分享。。。
另外:占用16个字节是怎么回事?int(4byte)+char(5)(5byte)=9byte
多的7byte是干什么用的?

支持(0)反对(0)

回复引用

#10楼[楼主] 2012-06-15 11:12 CareySon

@ heywap
好问题

除了页头占用的空间和行偏移矩阵占用的空间,中间剩下的空间就是给数据行使用的。一个数据行中还存在其他的信息用于表示该行数据,具体的结构是这样的:

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

这些最少占据1+1+2+2+1=7

支持(1)反对(0)

回复引用

#11楼 2012-06-15 11:18 heywap

@ CareySon
哦。原来如此。谢了哥们。

支持(0)反对(0)

回复引用

#12楼 2012-06-15 12:20 geass..

假如保存到char列的内容的长度不够,系统会自动在剩余的长度内容补上空格代替。

支持(0)反对(0)

回复引用

#13楼 2012-06-15 12:39 煋火

看到第一张配图时,哥震惊了

支持(0)反对(0)

回复引用

#14楼[楼主] 2012-06-15 13:05 CareySon

@ 煋火
何解?

支持(0)反对(0)

回复引用

#15楼 2012-06-15 14:28 

有料;

支持(0)反对(0)

回复引用

#16楼 2012-06-15 16:01 undefined

@ 煋火
引用看到第一张配图时,哥震惊了
+1

支持(0)反对(0)

回复引用

#17楼 2012-06-15 19:35 TimYang

支持下,虽然不是很懂!

支持(0)反对(0)

回复引用

#18楼 2012-06-15 21:54 无色

楼主的假设在于软件系统不需要跨数据库(可现在很多软件开发要求跨数据库),仅仅从性能考虑,而不从数据库的通用性考虑,不考虑在高并发性能下更强大的nosql,不需要云计算。

如果从上面几个方法考虑,像“尽量使得每行的大小更小”就是错误的,应该优先使用多种数据库支持的可变字符串,整型,而不是二进制类型。

关于主键的选择,更不应该使用“关系到业务模型”的主键,最好使用guid/uuid的base64编码主键,方便高并发合并,支持nosql的key-value存储。

优先使用字符串类型,不要使用和业务相关的主键,不要使用外键,不要使用二进制存储,不要使用和数据库特征相关的字段。

把数据库当做存储数据的地方,而不是放业务逻辑的地方,业务逻辑包括外键,存储过程,触发器,视图等。

优先考虑可伸缩性更强,可分布式的数据库设计方案,因为一台电脑再强也强不过云计算。

支持(2)反对(0)

回复引用

#19楼 2012-06-15 23:08 月漩涡

@ CareySon
引用@煋火何解?
估计是图里的字太不让人淡定了。
文章不错,感谢分享。

支持(0)反对(0)

回复引用

#20楼 2012-06-15 23:59 记忆

@ 无色
有道理较实际的说法。

支持(0)反对(0)

回复引用

#21楼 2012-06-16 02:27 hoodlum1980

图做的不专业,一看就觉得图肯定是原创。就算是mspaint,里面也能画箭头,也有文字工具。

支持(0)反对(0)

回复引用

#22楼 2012-06-16 11:04 劍聖

这些底层的原理,很难见到,楼主从哪儿了解来的,谢谢你的分享

支持(0)反对(0)

回复引用

#23楼 2012-06-16 11:14 银河

> 有些人建议不要允许Null,全部设置成Not Null+Default。

这个建议不是很靠谱。
例如:某个表中有一个“身份证号”列,这个列应该建一个 Unique 索引,并且允许 Null。因为不同人的必须有不同的身份证号,也必须允许某些人没有身份证。这个列就无法设置成 Not Null + Default 的。

支持(0)反对(0)

回复引用

#24楼 2012-06-16 13:24 桦仔

大家看法不一样,关于数据类型的选择确实支持LZ

支持(0)反对(0)

回复引用

#25楼 2012-06-16 13:46 遗忘海岸

老大IAM页是啥,另外nvarchar,跟varchar有啥区别啊,貌似平时多用nvarchar啊

支持(0)反对(0)

回复引用

#26楼 2012-06-17 00:42 周强

@ 无色
引用楼主的假设在于软件系统不需要跨数据库(可现在很多软件开发要求跨数据库),仅仅从性能考虑,而不从数据库的通用性考虑,不考虑在高并发性能下更强大的nosql,不需要云计算。 如果从上面几个方法考虑,像“尽量使得每行的大小更小”就是错误的,应该优先使用多种数据库支持的可变字符串,整型,而不是二进制类型。 关于主键的选择,更不应该使用“关系到业务模型”的主键,最好使用guid/uuid的base64编码主键,方便高并发合并,支持nosql的key-value存储。 优先使用字符串类型,不要使用和业务相关的主键,不要使用外键,不要使用二进制存储,不要使用和数据库特征相关的字段。 把数据库当做存储数据的地...

1. 人家在讨论SQL,你在NOSQL.
2. 数据库的通用性,在code级别是事情,不是数据库内部要考虑的问题。
3. 我同意nosql仅仅是sql在特定业务方面的补充,想完全取代SQL,这就是个笑话。(没看到国内,国外好多公司一窝蜂的上nosql,而现在偃旗息鼓了么?)
4. ‘云计算’就是个概念,别把它当神。恕我浅薄,我至今搞不清楚云计算与分布式计算有啥区别,特别是所谓的‘私有云’
5. 怎么又扯到主键的选择上去了?

支持(0)反对(0)

回复引用

#27楼 2012-06-17 08:10 无色

@ 周强
引用@无色引用楼主的假设在于软件系统不需要跨数据库(可现在很多软件开发要求跨数据库),仅仅从性能考虑,而不从数据库的通用性考虑,不考虑在高并发性能下更强大的nosql,不需要云计算。 如果从上面几个方法考虑,像“尽量使得每行的大小更小”就是错误的,应该优先使用多种数据库支持的可变字符串,整型,而不是二进制类型。 关于主键的选择,更不应该使用“关系到业务模型”的主键,最好使用guid/uuid的base64编码主键,方便高并发合并,支持nosql的key-value存储。 优先使用字符串类型,不要使用和业务相关的主键,不要使用外键,不要使用二进制存储,不要使用和数据库特征相关的字段。 把数据库当做...
如果你设计数据库表,可以很方便的迁移到nosql上,这种设计就是一种可伸缩性设计,如同好的框架对于软件后期发展的重要性。

举个例子,现在很多公司不用存储过程的原因,就是存储过程只是把一部分代码写在数据库,而这些代码很大程度上是业务逻辑层的事。

依赖第三范式过度设计数据库,实际上数据库干了很多应该属于业务逻辑层的事。第三范式产生之初只是为了节省那时比现在手机功能还弱的电脑资源,发挥单机的最大性能,而现在可以多个电脑水平扩展来解决这些问题。游击兵单兵再厉害也是无法和可扩展的正规矩阵军团对抗的。在提前假设条件的变化下,数据库表的设计需要更好的可扩展,而不是用类似用二进制的数据来提高效率(如同当初socket网络通信多用二进制通信,可扩展性极差,现在多用json或xml)。

关于主键的选择,不使用业务逻辑相关的主键,也是上述思想的体现。其实业务逻辑相关的主键都可以通过索引来解决问题。而在分布式环境下,如果产生唯一主键,可以方便扩展到未来的nosql中(可以把数据量大的表放在nosql数据库中,如发布的文章)使用key-value来存储。

注意了,不是要使用nosql来淘汰关系数据库,而是各取所长混合使用。小数据量的表放在关系数据库中,而大数据量的表使用key-value方式放入nosql,如发表的文章,一个key,一个文章主体value,而在关系数据中依然保存文章这个表,只是文章主体没有了,只有文章key。这样方便查询,又能解决高性能问题。说白了,就是把数据库表中的大字段类型,用key-value的方式放入nosql,如同现在解决图片问题一样(一个图片的存储路径key,value放在硬盘)。当然你可以把很少查询的表也用nosql存储,这样原来数据库表设计的guid/uuid主键就起关键作用了。这样整个系统可伸缩性就高多了。

关于云计算与分布式计算,如同web 2.0对http协议,不是一个概念,云计算建立在分布式计算之上,但不光使用分布式计算技术,还包括很多如云存储,虚拟技术,基础设施平台你可以了解一下亚马逊云计算服务内容。对于电脑中的操作系统,网络,文件存储,数据库这些基础的东西,云计算强调可伸缩性分配。如果用水来比喻计算能力,云计算是海洋,私有云是湖泊,单机是池塘,手机是脸盆。

云计算不是普通人能玩得起的,如果建设一个100万台机器的云计算中心,一年光是电费就要20-50亿元(一台2000到5000元)。另外新的arm低功耗服务器可能使云计算快速普及,使用成本下降。当然普通程序员,只需要怎么实现这些程序就行了,设计可扩展可伸缩程序是关键。

支持(1)反对(0)

回复引用

#28楼[楼主] 2012-06-18 09:45 CareySon

@ 无色
谢谢评论.对于nosql并不了解,感谢分享知识

支持(0)反对(0)

回复引用

#29楼[楼主] 2012-06-18 09:45 CareySon

@ 银河
所以我才说要根据具体的业务来进行抉择

支持(0)反对(0)

回复引用

#30楼[楼主] 2012-06-18 09:46 CareySon

@ 张晓亮(David)

大多数是看书得来。

支持(0)反对(0)

回复引用

#31楼[楼主] 2012-06-18 09:50 CareySon

@ 遗忘海岸

IAM=index allocation map page,它用来跟踪哪些盘区是放数据的,哪些盘区是用来放索引的,它本是不存储用户的数据或索引,而是存储并决定用户的数据或索引该存储在哪个盘区!所以,它可以管理多达4GB的空间!

nvarchar存中文还好,存英文一个字符会占两个字节,选择nvarchar中文就不会出现乱码什么的。所以字段只存英文的话,不要考虑nvarchar

支持(0)反对(0)

回复引用

#32楼 2012-06-19 10:37 - -||

配图略虎

支持(0)反对(0)

回复引用

#33楼 2012-07-04 15:08 永远的阿哲

顶你一万年!顶你一万年!顶你一万年!顶你一万年!顶你一万年!

支持(0)反对(0)

回复引用

#34楼 2012-10-16 10:09 深蓝医生

推荐,对于了解数据库底层很有用。
----------------------------
欢迎加入PDF.NET开源数据开发框架团队

支持(0)反对(0)

回复引用

#35楼[楼主] 2012-10-16 10:19 CareySon

@ 深蓝医生
你这个开源项目挺不错的:-D

支持(0)反对(0)

回复引用

#36楼 2014-04-07 22:03 rottenvine

varchar和nvarchar的选择,楼主能否做个分析呢?

原文地址:https://www.cnblogs.com/gered/p/8423496.html

时间: 2024-10-12 17:33:58

【转】对于表列数据类型选择的一点思考的相关文章

设计表的时候,对变长字段长度选择的一点思考

不管是在MSSQL还是MySQL或者Oracle,变长字段的长度衡量都是要经常面对的.对于一个变长的字段,在满足业务的情况下(其实所谓的满足业务是一个比较模糊的东西),到底是选择varchar(50)还是varchar(200)亦或是varchar(500)?对于保守型选择,往往是选择一个较大的长度,比如varchar(500)要比varchar(50)更具有兼容性,因为是变长字段的原因,存储空间也一样.这样的选择并不能说就不好,看站在哪个角度来看问题.那么,相对于varchar(50),var

MySQL数据表类型 数据类型

来源:http://blog.chinaunix.net/uid-10064369-id-2971161.html 表类型 MySQL的数据表类型很多,其中比较重要的是MyISAM,InnoDB这两种. 这两种类型各有优缺点,需要根据实际情况选择适合的,MySQL支持对不同的表设置不同的类型.下面做个对比: MyISAM表类型是一种比较成熟稳定的表类型,但是MyISAM对一些功能不支持.    MyISAM  InnoDB 事务  不支持  支持 数据行锁定  不支持,只有表锁定  支持 外键约

MySQL中char与varchar数据类型选择原则

很多地方在分析char与varchar数据类型选择原则上,往往忽略了存储引擎.事实上,我们应当根据选定的存储引擎,确定如何选择合适的数据类型. char与varchar比较 值 char(3) 实际存储字符长度 varchar(3) 实际存储字符长度 " ' ' 3 " 1 'ab' 'ab ' 3 'ab' 3 'abc' 'abc' 3 'abc' 4 'abcdefg' 'abc' 3 'abc' 4 总结:从上表可以看出,char属于定长的. char属于定长类型,varcha

四 Django框架,models.py模块,数据库操作——创建表、数据类型、索引、admin后台,补充Django目录说明以及全局配置文件配置

Django框架,models.py模块,数据库操作--创建表.数据类型.索引.admin后台,补充Django目录说明以及全局配置文件配置 数据库配置 django默认支持sqlite,mysql, oracle,postgresql数据库. 1,django默认使用sqlite的数据库,默认自带sqlite的数据库驱动   引擎名称:django.db.backends.sqlite3 在全局配置文件settings.py可以看到确认配置使用的sqlite数据库 # Database # h

SQL Server数据库、表、数据类型基本概念

一.SQL Server的数据存储结构 SQL Server是一个数据库管理系统,需要以有效方式存储高容量数据.要更好地理解SQL Server处理数据的方式,就需要了解数据的存储结构. 1.文件类型 数据库在磁盘上是以文件为单位存储的,由数据文件和事务日志文件组成.一个数据库至少应该包含一个数据文件和一个事务日志文件. 主数据文件:主数据文件包含数据库的启动信息,指向数据库中的其他文件,每个数据库都有一个主数据文件(有且只有一个),推荐文件扩展名是.mdf: 次要(辅助)数据文件:除主数据文件

ms-sql 给表列添加注释

需求: 在创建数据库是对相应的数据库.表.字段给出注释. 解决方案: 首先,要明确一点的是注释存在sysproperties表中而不是跟创建的表捆绑到一起的(我的理解). 一.使用SQL Server窗口创建表是会有注释窗口: 二.使用SQL语句的comment语句,该语句放置在create table()后面,如: comment on table table_name is 'table_mark' comment on column table_name."Column" is

Oracle 建表常用数据类型的详解

创建表时,必须为表的各个列指定数据类型.如果实际的数据与该列的数据类型不相匹配,则数据库会拒绝保存.如为学生指定出生日期为"1980-13-31". 在Oracle中,常见的数据类型有: 字符串:字符串分为定长类型char和变长类型varchar2. 数字:整数 number(整数位),小数 number(总长度,小数位),只写number,表示无限制. 日期:date类型,可以保存年月日时分秒. 问题:Oracle中为什么字符串类型为varchar2,它与varchar有什么关系?

SQL面试题:有A B C三列,用SQL语句实现:当A列大于B列时选择A列否则选择B列

.请教一个面试中遇到的SQL语句的查询问题 表中有A B C三列,用SQL语句实现:当A列大于B列时选择A列否则选择B列,当B列大于C列时选择B列否则选择C列. ------------------------------------------ select (case when a>b then a else b end ), (case when b>c then b esle c end) from table_name [sql] view plain copy drop table

Sql Server 数据库教程三 、添加、删除、修改、表列

修改数据库的表设计,可以使用 SQL ALTER TABLE 语句,ALTER TABLE 用于在已有的表中,添加.删除.或修改表列. 1>在数据库表中添加新列: 语法: ALTER TABLE 表名称 ADD 列名称 数据类型 例如:在下面这个Persons表中添加一个新列Birthday,数据类型为varchar(50) 在新建查询中输入如下语法: ALTER TABLE Persons ADD Birthday varchar(50) 插入后的结果如下: 2>在数据库表中删除表中的列: