ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

SQL Server数据类型详解:从数值、字符串到日期时间的选型指南

SQL Server数据类型详解:从数值、字符串到日期时间的选型指南 1. 先搞明白SQL Server的数据类型体系究竟分几类很多人一打开SQL Server联机文档看到几十种数据类型直接头大心想这玩意儿怎么能叫大全干脆当成字典用遇到啥查啥。说实话我刚接触SQL Server那会儿也是这么干的。直到后来在真实项目里吃到亏——因为不了解类型的底层表示方式设计表结构时埋了一堆雷才老老实实把整条类型体系捋了一遍。其实SQL Server的数据类型分类特别清晰官方的套路是把它们按存储特征和行为习惯切成了几个大类精确数值、近似数值、字符串、Unicode字符串、二进制字符串、日期时间、其他特殊类型。这个分法本身就是最好的学习线索因为它强调的是这些类型在数据库引擎眼里到底是怎么存的、能精确到什么程度、支持什么样的操作。你可以把数据库里的数据类型想象成容器的材质瓷碗和塑料碗都能装水但瓷碗能进微波炉塑料碗不行有的容器标明刻度可以精确量取有的只能估个大概。字段选型本质上就是在挑容器存储的语义、取值范围、精度要求、查询方式、索引兼容性每一项都直接影响后面几十年的使用体验。还有一件事必须在开头就讲清楚SQL Server里很多类型看着名字像、其实脾气完全不同。比如char和nchar只差一个前缀但一个是非Unicode、一个是Unicode存储逻辑和字符集处理完全是两码事datetime和datetime2差了整整一个精度时代bit和tinyint都能存0和1但语义完全不同。后面我会一个个拆开讲。这篇文章的定位不是把微软官方文档搬过来念一遍而是按这个类型适合干什么、有什么坑、表单怎么建最稳的思路把整个类型体系串成一张可以实际照着用的地图。适合正在建表、优化库表结构、或者准备面试需要把基础夯实的开发者。1.1 官方分类口径与实际业务的映射官方的分类是从数据库引擎的实现角度出发的但落到真实业务里我们要关心的其实是另一组映射关系。比如精确数值类型tinyint、smallint、int、bigint、decimal、numeric、money、smallmoney——管的是数字必须分毫不差的场景主键、计数器、金额、数量。近似数值类型float、real——管的是量级和趋势对就行最后几位无所谓的场景温度传感器数据、科学计算中间量。字符串类型char、varchar、text——管的是字符集单一且以英文为主的场景。Unicode字符串类型nchar、nvarchar、ntext——管的是可能有中文、日文、emoji等多语言的场景。二进制类型binary、varbinary、image、FILESTREAM——管的是文件、加密串、序列化对象的场景。日期时间类型date、time、smalldatetime、datetime、datetime2、datetimeoffset——管的是事件发生时点、区间计算、时区换算的场景。其他特殊类型uniqueidentifier、rowversion、xml、json、sql_variant、hierarchyid、空间地理类型等——管的是分布式唯一ID、版本标记、半结构化数据、树形结构、地理位置的场景。这套映射关系理清楚之后再看官方文档就不会晕了。说白了分类是形式业务才是本质。1.2 从存储和性能角度看为什么不能拍脑袋选选错数据类型的代价是隐蔽的但一旦爆发就很疼。最典型的三个维度是存储空间、索引效率和隐式转换。先说存储空间。SQL Server的数据页固定是8KB行数据都往页里塞。你要是把只需要0~200的数字塞进bigint白白浪费6个字节一张表几百万行浪费的存储空间倒是小事关键是索引页变多、缓冲池命中率下降、IO变大。有人觉得服务器不差这点磁盘但磁盘不差、内存差数据库的性能瓶颈往往出在这些不起眼的地方。再说隐式转换。查询条件里的数据类型和字段类型不一致时优化器会尝试把一边转成另一边。常见情况是字段是varchar参数传的是nvarchar结果索引失效走全表扫描。这个问题在字符串类型上最严重后面我会单独拎出来讲。第三是精度问题。用float存金额看起来能存进去取出来多一位小数、少一位小数账面不平了财务和审计直接炸。类型选错不是以后再说的问题而是上线就会爆的问题。2. 数值系全家桶从bit到decimal的精度博弈数值类型是数据库里最较真的一类因为它直接关系到账目、统计、排序、索引。SQL Server的数值类型看起来不多但每一档都有严格的边界踩线就是报错。2.1 整数四兄弟与bit的边界先看最常用的整数类型类型存储大小取值范围bit1字节8个bit列共用1字节0、1、NULLtinyint1字节8 bit0 ~ 255smallint2字节16 bit-32,768 ~ 32,767int4字节32 bit-2,147,483,648 ~ 2,147,483,647bigint8字节64 bit-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807很多人把bit理解成布尔值这个理解没错但要特别注意它在存储上的一个特性一张表里如果只有1个bit列它占用1字节如果有8个bit列它们加起来还是占1字节。这是因为SQL Server会把同一行里的多个bit列按位打包存储。这个特性在表设计时可以帮你省一点空间但别指望靠它优化出质的飞跃。tinyint因为只能存0~255常被用来存状态码、枚举值、端口号、年龄等小范围数据。smallint和int是业务主力绝大多数计数器、自增主键用int都够了。bigint只有在确凿需要超大范围时才用——比如分布式系统里的雪花算法ID、流水号、埋点事件ID。有人建表时习惯性往大了选主键一律bigint这不是错误但确实不够精细。int主键的上限是21亿多很多业务表其实十万年也到不了这个量级。但反过来如果已经有int主键的表数据量逼近瓶颈想在线改成bigint那绝对是一场灾难——所有关联表的外键都要跟着改索引全部重建锁表时间长得能让人崩溃。所以主键用bigint还是int这个选择与其说是空间优化不如说是容量规划。2.2 decimal/numeric的精度刻度参数到底怎么设decimal和numeric在SQL Server里功能完全等价官方文档原话是numeric 等同于 decimal。它俩都允许你显式声明精度precision和小数位数scale语法是DECIMAL(p, s)。pprecision总位数包括小数点两边的所有数字取值范围1~38。sscale小数点右边保留的位数取值范围0~p。存储大小不是固定的SQL Server会根据p的值决定用多少字节p 9用5字节p 19用9字节p 28用13字节p 38用16字节。这里有个特别容易犯的错DECIMAL(18, 4)表示总位数18位其中小数部分4位整数部分只有14位。有人建表时以为DECIMAL(18, 4)能存18位整数再加4位小数结果存入超过14位整数的数字时直接报算术溢出错误将 numeric 转换为数据类型 numeric 时出错。设计金额字段时我的习惯是整数部分按业务上限的3倍预留小数部分按实际需要留2~4位。比如一个系统里的最大单笔金额不会超过1亿整数部分9位我就建DECIMAL(16, 4)整数部分12位留足余量小数4位应对汇率、折扣、计价单位等场景。另一个关键点SQL Server对decimal的除法、乘法运算结果也是有精度规则的比如两个DECIMAL(38, 10)相除结果精度可能退化或报错。如果你做复杂计算时发现了算术溢出错误不要只怀疑数据很可能是中间结果的精度被引擎卡住了解决方法是CAST成合适精度的中间值。2.3 float和real为什么差一点点会要命float和real属于近似数值类型这意味着它们在存储时用二进制近似表示十进制小数无法保证精确。float的可选参数n决定精度和存储大小float(n)精度存储大小1~247位有效数字4字节25~5315位有效数字8字节real等同于float(24)占4字节7位有效数字。7位有效数字是什么概念1234567.89这种数字存进去最终可能是1234567.9因为引擎只保证前7位数字可靠。float的设计初衷是给科学计算用的那类场景对稍微偏移几纳米温度波动0.000001度不敏感但对金融、计费、库存这种必须精确对账的业务来说float就是一颗定时炸弹。我见过不少项目把金额、单价、税率存成float平时看着好好的合计数对不上、报表偶现2.999999999排查到凌晨才怀疑到类型头上来。所以我的结论很直接业务字段凡是涉及钱、量、税、率的一律不准用float/real只有在统计传感器读数、算法输出这类本身就带误差的数据时float才是合理选择。2.4 money/smallmoney别被货币专用误导money占8字节smallmoney占4字节。这俩类型从名字看好像是存钱专用但它们内部其实是定点小数精度固定到小数点后4位而且取值范围受限于money的精度模型。说实话我在实际项目里推荐大家谨慎使用money。理由有两条第一它不支持任意精度的小数位数设定如果业务需要保留6位甚至更多小数位它直接歇菜第二它的货币语义只在SQL Server内部有意义一旦和外部系统交换数据、做报表导出、写应用程序映射反而容易引入特殊处理逻辑。绝大多数现代业务系统用DECIMAL就能完美解决问题没必要为了一个看起来像专业货币类型的名头引入额外约束。3. 字符串与Unicodechar/varchar/nvarchar的取舍与浪费字符串是日常开发里打交道最多的类型也是翻车率最高的类型。这章把char、varchar、nchar、nvarchar、text/ntext这些老少爷们都拉出来遛遛。3.1 定长与变长的根本差异char(n)是定长字符串无论你实际存入多少字符它都会占用固定n个字节非Unicode下1字符1字节不足部分用空格填充。varchar(n)是变长字符串存储开销等于实际字符数加上少量开销字节2字节记录长度varchar(max)的存储方式另有不同。nchar(n)、nvarchar(n)是它们对应的Unicode版本每个字符固定占2字节非增补平面字符同样的n值下占用的空间直接翻倍。这两组怎么选说实话现代系统里我几乎不会主动推荐char/nchar做业务字段因为变长存储能大幅节省空间而且varchar在查询上和char没有本质性能差距。但有一种场景char有优势固定长度的编码字段。比如身份证号18位、统一社会信用代码18位、MD5哈希32位这些字段语义上长度固定用char(18)可以避免varchar的2字节开销单行不算啥几亿行就有意义还能防止少填一位、多填一位这类不严谨数据悄悄溜进表里。而且定长字段在排序、比较时的行为更稳定。不过要注意char在读取时尾随空格的行为有坑。你用char(10)存了a实际存储是a SQL Server在做等值比较时会自动忽略尾随空格但LEN()函数也会忽略尾随空格而DATALENGTH()不会。如果业务逻辑对尾随空格敏感定长类型会让你抓狂。3.2 nvarchar(MAX)不是银弹先给结论nvarchar(max)是个伟大的类型但它不该被滥用。nvarchar(max)可以存最大约2GB的Unicode文本而且它在8KB以内的数据可以是行内存储超过阈值则自动把大值移到LOB存储。理论上它一个类型打天下小到标题、大到长文都能存。问题出在性能和索引上max类型的字段不能做索引键除非你给计算列建索引或用全文索引对max字段的排序、分组、去重代价很高它参与查询时优化器对它的统计信息估算往往不准容易导致执行计划不理想。比如一个用户备注字段明明业务上最多100字你用nvarchar(max)建表开发时爽快后面要在这张表上做报表、做分析查询时每次都要小心翼翼别碰那个大字段。性能调优时看到大对象字段拖慢页密度悔不当初。我的选型原则是这样的长度区间推荐类型理由1~100字符varchar(100)/nvarchar(100)行内存储、支持索引开销可控100~4000字符varchar(4000)/nvarchar(4000)大多数业务够用仍在行内常规存储区间超过4000字符或不确定上限varchar(max)/nvarchar(max)不得已而为之注意性能取舍这里要特别提醒不要直接用nvarchar存所有字符串除非确实需要多语言。系统只在中文域跑或者虽然用户可能输入中文但数据库排序规则支持中文那么普通varchar配合合适的排序规则在存储和性能上会好很多。nvarchar用2字节存英文数字相当于多花了1倍空间。3.3 老伙计text/ntext/image为何被退役text、ntext、image是SQL Server 2005之前的大对象类型现在微软官方已经明确把它们标记为不推荐使用新代码不该再碰未来版本大概率会彻底移除。它们在功能上分别被varchar(max)、nvarchar(max)、varbinary(max)取代。老系统迁移时这仨类型的字段往往还活着建议逐步转换因为旧类型的操作限制太多比如不能直接参与某些字符串函数、排序规则行为怪异。4. 日期时间类型8个亲戚怎么认日期时间是表设计中被忽视得最严重的一类。很多老项目一水儿的datetime哪怕只需要存个生日、只需要精确到天的日期也从不去想date类型。等表大了才发现所有日期查询都在做无谓的时区换算、范围计算多出好几个小时。4.1 一张表看全各类型的精度、范围与存储开销类型范围精度存储大小典型场景date0001-01-01 ~ 9999-12-311天3字节生日、发货日、业务周期time00:00:00.0000000 ~ 23:59:59.9999999100纳秒3~5字节打卡时间、时段统计smalldatetime1900-01-01 ~ 2079-06-061分钟4字节老系统的粗略时间datetime1753-01-01 ~ 9999-12-313.33毫秒8字节存量系统、兼容旧数据datetime20001-01-01 ~ 9999-12-31100纳秒6~8字节现代精确时间字段datetimeoffset0001-01-01 ~ 9999-12-31含时区偏移100纳秒8~10字节跨时区业务、审计时间datetime2(0)等同datetime2可选秒精度6字节需要秒级但无关微秒这里最容易被忽略的一对是datetime和datetime2。datetime的历史包袱太重——它只支持3.33毫秒精度且范围从1753年才开始因为当年的格里高利历切换导致年份跳变。而datetime2支持100纳秒精度范围到公元1年语法上还可以自己指定小数秒精度datetime2(0)精确到秒、datetime2(3)精确到毫秒、datetime2(7)精确到100纳秒。按需压缩精度还能省存储空间。4.2 datetime与datetime2的迁移注意如果把datetime字段改成datetime2表面上看只是精度提升内核里会发生很多变化。最大的坑是语义变化datetime在存储时舍入到0、0.003、0.007秒这样的步长datetime2则是精确存储。原本存进去的23:59:59.997在datetime2里就是精确值不会像以前那样在某些运算后出现意外进位。这本来是好事但如果老系统有大量针对毫秒等于0的分组统计逻辑迁移后结果会变必须回归测试。另一个坑是字符串格式的差异。datetime2不支持某些datetime特有的隐式格式转换行为从客户端传入乱七八糟的日期字符串时datetime2往往更严格直接把错误抛出来。老系统跑得好好的导入脚本换字段类型之后可能在格式上崩一遍。4.3 时区敏感场景datetimeoffset的正确打开方式如果你的业务涉及多地域协同——比如一个订单在纽约下单、在上海审核、在东京入库所有时间戳都落到一个数据库里那datetimeoffset就派上用场了。它能保存本地时间加上UTC偏移量比如2025-06-01 10:00:00 08:00这样无论数据来自哪个时区都能精确还原出UTC时刻并且支持SWITCHOFFSET、TODATETIMEOFFSET等函数在时区之间自由换算。很多团队嫌datetimeoffset麻烦统一用UTC时间存datetime2这也是一种很成熟的做法——应用层负责时区转换。但如果数据源非常多、采集端不可控或者审计要求保留原始本地时间datetimeoffset能减少大量口径扯皮。4.4 只存日期、只存时间、用date和time建表时能省则省的原则在这里特别适用。需求是出生日期就用date8字节变3字节需求是营业时段开始结束用time即可需求是某年某月的某一天的报表统计字段用date做分组键比datetime清爽得多索引也更小。这种细节累计起来在大表上的差距非常明显。5. 结构化与半结构化的新贵从uniqueidentifier到spatial能撑起现代业务复杂度的少不了这些特殊类型。它们不像数值和字符串那么日常但用对了能解决一堆架构难题。5.1 rowversion/uniqueidentifier并发与唯一性的幕后rowversion旧称timestamp是数据库自动维护的二进制版本号每行数据被更新时该行的rowversion值都会自动递增。它最适合做乐观并发控制读取时拿到版本号更新时在where条件里带上rowversion字段如果发现版本号变了说明这行被改过更新失败应用层决定是刷新重试还是提示用户。由于是数据库引擎自动生成完全不用担心并发下的唯一性。注意一张表只能有一个rowversion列而且它不能做逻辑主键。uniqueidentifier也就是GUID16字节常用于分布式系统的主键和外键。它的优势是可以在多个节点独立生成而不冲突劣势是随机性导致索引碎片严重。如果必须用GUID做主键有两个缓解手段一是用NEWSEQUENTIALID()生成顺序GUID二是让GUID作为聚簇索引键之外的逻辑主键给表单独建一个自增的聚簇键。我在分布式日志系统、消息表里见过大量GUID主键配合顺序生成器后性能好了不少但总体上还是比自增整型主键贵。5.2 xml与json现代接口的存储姿势SQL Server从2005年就开始支持xml类型可以在字段里存XML文档还能用XQuery查询节点。到了2016年SQL Server引入了对JSON的支持——注意这里的JSON类型实际上并不存在独立的JSON存储类型它不是像PostgreSQL的jsonb那样而是以nvarchar(max)为载体通过ISJSON、JSON_VALUE、OPENJSON等函数加以约束和解析。这也是我经常被问到的一个点SQL Server没有JSON字段类型用的是nvarchar(max) JSON函数。那xml和JSON怎么选如果你确实有大量XML格式的历史数据、并且供应链领域强依赖XML标准保留xml类型没问题。但对现代API交互型业务JSON已经是事实标准直接在nvarchar(max)上做校验和解析是更顺滑的路线。有一点务必提醒不要把整张业务表的字段都塞进一个JSON大字段里否则数据库的约束能力、查询优化、外键关系全部失效表结构变成宽键值仓库后续维护成本暴涨。JSON适合存结构不固定、需要灵活扩展的部分字段比如用户偏好设置、第三方回调的原始报文。5.3 空间地理类型与hierarchyid的适用边界geography和geometry主要用于地理空间数据。geography存经纬度坐标单位是度支持球面距离计算geometry存平面坐标系数据单位取决于坐标系定义。如果你在做地图POI、配送范围、轨迹分析这俩类型配合空间索引非常强大写查询像STDistance、STIntersects就能完成复杂的空间判断。hierarchyid是专门表达树结构的类型适合组织架构、商品分类、评论回复这类父子层级数据。它把整棵树的路径编码在一个字段里查询某个节点的所有后代非常快。但它的API有学习曲线常见写法也不像parent_id递归那么直观。如果你的树层级很深、查询频繁值得投入如果只是很浅的两三层父子关系用传统自关联外键就够了。5.4 少见的sql_variant和table类型sql_variant可以装任何SQL Server数据类型除了text、ntext、image、rowversion以及自身是万能容器。听起来很酷实战中几乎不用。因为它把数据类型的强约束完全消解了查询、索引、比较、排序的规则全得动态判断性能也差。我只在某些配置表里见过它——一行存一个配置项值的类型各不相同勉强算一个合理场景。table主要用于表值参数和表值函数返回值不用于字段定义。它在存储过程编程里很好用把一个DataTable当作参数传给存储过程做批量操作比逐条拼SQL字符串高效得多同时还能保留约束和事务。6. 选型判断一张决策清单和四个高频翻车现场聊了这么多类型细节最后把这些经验收拢成一张可以照着做的决策思路再用四个真实场景告诉你别跟我一样踩坑。6.1 从需求反推类型的决策清单我建表时习惯按下面的问题清单快速收敛字段类型建议你也直接用起来这个字段语义是什么数值、文本、时间、二进制、特殊结构如果是数值需要精确吗涉及金额/库存/计算对账——需要则decimal纯计数用整数范围多大选int/bigint。如果是文本需要多语言需要超过4000字符是否需要索引——默认varchar/nvarchar按业务上限设长度绝不无脑max。如果是时间需要精确到秒还是毫秒要跨时区吗——优先datetime2时区敏感上datetimeoffset只存日期用date。有排序、分组、去重需求吗——避免大对象类型避免sql_variant避免把业务内容塞进JSON大字段。字段宽度有硬性上限吗身份证、手机号、编码——可考虑char。这套清单能解决绝大多数建表争议。遇到团队里我就要一个万能类型的声音把清单怼过去基本好使。6.2 翻车现场1金额字段用float某个老系统的订单表金额字段是float平时原价、折后价看着都正常直到做了财务月结SQL对账脚本一跑好几笔订单差出0.000001元把明细JOIN起来看A表和B表相同的订单号金额居然对不上。最后排查发现两套系统一个用float存、一个用decimal存二进制浮点数的尾数差异在跨库关联时原形毕露。修复方案是把所有涉及钱的字段统一DECIMAL(18, 2)或更精细的小数位应用层也全部改用定点数运算。6.3 翻车现场2手机号用int/bigint有人觉得手机号不就是一串数字嘛然后建了bigint。头一回没事直到某天发现手机号前面的0被吃掉、国际区号完全没法存、后续对接营销系统时号码被科学计数法格式化导出。手机号本质是由数字构成的字符串不是数值。它不做加减乘除只有等值匹配和前缀匹配所以字段类型应选varchar(20)预留国际号码空间。6.4 翻车现场3全表nvarchar(MAX)治百病我曾经接手过一张用户反馈表除了主键、时间字段外所有字段一律nvarchar(max)。设计人当时图省事谁知道用户填多长max万无一失。后来要在这张表上做关键词模糊搜索全表扫得痛苦不堪要做按反馈类型分组统计发现同义词混乱无约束根本没法聚合。最后不得不重构表把标题、反馈类型、联系电话等字段改成合理长度并加约束只有反馈正文保留nvarchar(max)。这个改动的代价是整整两个通宵。6.5 翻车现场4时间字段取datetime算了新项目建表时想都不想就datetime结果业务要求精确到毫秒的并发时间戳发现datetime的3.33毫秒精度让两个并发操作的时间完全一样后来想改成datetime2(7)又要处理大量历史数据和时间格式的变化。现在SQL Server 2016以上的环境里新表创建日期时间字段我真的找不到用datetime的理由一律datetime2精度按业务选0~7跨时区就上datetimeoffset。我在实际项目里还有一个习惯每次建表之后花两分钟对每个字段做一次如果数据量翻100倍这个类型还撑得住吗的推演。这个方法帮我避开了很多设计期觉得没问题、上线后天天想改表的坑。SQL Server的数据类型看起来庞杂但背后的取舍规律并不复杂——空间、精度、约束能力、索引友好度永远把业务语义放在第一位。希望这份大全能让你以后建表时少走弯路。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表