1. 老生常谈却值得重新梳理的MySQL数据类型
我从刚入行时就被前辈反复叮嘱:"建表之前先把数据类型想清楚,后面会少踩很多坑。"当时觉得不就是选个int、varchar嘛,能有多大差别。直到我维护过一个因为字段类型选错而被迫重构的业务系统,才真正意识到数据类型不是一个"随便选选"的细节,而是数据库设计的骨架。骨架歪了,后面所有基于这张表的查询、索引、统计、接口返回值,都会跟着别扭。
这篇内容的定位很简单:把MySQL常见的数据类型从原理到使用边界系统梳理一遍,重点回答三个问题——这个类型底层是怎么存的、实际业务里应该怎么选、选错了会出现什么问题。适合刚接触MySQL的初学者,也适合那些已经写过不少SQL但主要是"别人建表我查询"的同学。看完之后你至少能对自己项目里的建表语句多一分判断力,而不是拿到什么类型就用什么。
先声明一下,本文基于MySQL 8.0的官方语义来写,部分内容会顺带提一下5.7及更早版本的差异。版本不同,某些类型的表现确实不一样,这点后文会具体说明。
MySQL的数据类型可以粗分为这么几大类:数值类型(整数、定点数、浮点数)、字符串类型(二进制、非二进制、大文本)、日期时间类型、JSON类型,以及一些空间数据类型和枚举集合类型。日常业务我们碰得最多的是数值、字符串、日期时间这三类,所以这篇重点展开这三类,JSON和空间类型会在对应章节里单独讲,不会一笔带过。
2. 整数类型:不只是"够用就行"
2.1 整数类型的底层存储到底差多少
先看一张表,把MySQL里所有整数类型的基本参数列出来。
| 类型 | 字节数 | 有符号范围 | 无符号范围 | 常见用途 |
|---|---|---|---|---|
| TINYINT | 1 | -128 ~ 127 | 0 ~ 255 | 状态码、开关值、小枚举 |
| SMALLINT | 2 | -32768 ~ 32767 | 0 ~ 65535 | 较小的计数器、端口号 |
| MEDIUMINT | 3 | -8388608 ~ 8388607 | 0 ~ 16777215 | 中等规模的自增ID |
| INT | 4 | -2147483648 ~ 2147483647 | 0 ~ 4294967295 | 常规主键、外键 |
| BIGINT | 8 | -9223372036854775808 ~ 9223372036854775807 | 0 ~ 18446744073709551615 | 雪花ID、超大数值、时间戳 |
很多新手容易忽视一个关键点:存储字节数直接决定了能表达的范围,而不是看那个"显示宽度"。老版本的MySQL里INT(11)这种写法很常见,括号里的数字给人一种"这个字段最多存11位"的错觉,实际上它只是影响某些客户端工具展示时的补零行为,并不限制存储范围和精度。8.0版本里INT的显示宽度已经被标记为废弃,新代码里没必要再写INT(11)这种东西。
选择整数类型的核心依据只有一个:你的数据上界和下界是多少,未来五年会不会超出这个范围。自增主键、订单号这类字段,直接上BIGINT,别纠结。很多人觉得"订单量不可能上亿",所以用INT,结果遇到数据迁移或分表合并时突然发现INT不够用,修改主键类型是整个系统最痛苦的操作之一,因为所有外键和关联表都得跟着改。
2.2 UNSIGNED和ZEROFILL的误用
UNSIGNED表示无符号,也就是只能存非负数。看起来很适合"不会出现负数的字段"对吧?但这里有坑。
第一,UNSIGNED字段参与运算时,如果表达式中出现负数,MySQL会报错或者产生意外结果。比如一个UNSIGNED INT字段减1,当值为0时执行UPDATE table SET num = num - 1,在严格模式下会直接报错"BIGINT UNSIGNED value is out of range"。同样,两个UNSIGNED字段相减,结果依然会被当成UNSIGNED,如果结果为负数,会发生溢出回绕,变成一个巨大的正数。
第二,UNSIGNED并不会让存储变省,它和普通INT占的字节数完全一样,只是把负数那一半空间挪到了正数那一侧。所以除非你的字段确实只存非负数,而且数据范围刚好需要用到超过有符号上限的那部分空间,否则没必要加UNSIGNED。MySQL官方甚至建议尽量少用UNSIGNED,因为容易引发边界问题和隐式转换的困扰。
ZEROFILL就更冷门了。它会把数值左侧补零到指定宽度,比如INT(5) ZEROFILL存12,查询出来是"00012"。看着挺酷,但8.0里ZEROFILL已经和显示宽度一起被废弃了。我见过有人用ZEROFILL让主键ID编号更整齐,等数据量大了之后才发现这个字段在JOIN和索引优化时会引发各种怪问题。老老实实把显示格式化的事情交给应用层去处理,别让数据库做这些花活。
2.3 BOOLEAN其实是TINYINT
MySQL没有原生的BOOL类型,BOOLEAN和BOOL都是TINYINT(1)的别名。这意味着你可以往"布尔"字段里塞进2、3、100这些值,只要你没加CHECK约束。如果你依赖这个字段做条件判断,WHERE is_deleted = 1和WHERE is_deleted在语义上会有差异,因为后者会把任何非0值都当作真。
更稳妥的做法是建表时加CHECK约束,或者用ENUM('0','1')。不过从性能角度考虑,TINYINT配合应用层强制赋值反而更高效。我在实际项目中的习惯是:状态值用TINYINT,并且永远只用0和1两种值,命名上叫flag或is_xxx。如果字段未来可能扩展为多状态,就会直接用TINYINT存状态码,比如0待处理、1进行中、2已完成、3失败,而不是用布尔命名去硬撑。
3. 小数类型:FLOAT、DOUBLE、DECIMAL,选错等于埋雷
3.1 从精确性角度理解三种类型
先记住一句话:FLOAT和DOUBLE是浮点数,DECIMAL是定点数。浮点数用二进制近似存储,所以很多十进制小数无法精确表示;定点数以字符串或压缩十进制方式存储,能精确表示指定精度内的十进制小数。
- FLOAT占4字节,约7位有效数字。
- DOUBLE占8字节,约15~16位有效数字。
- DECIMAL(M,D),M是总位数,D是小数点后位数,存储长度随精度变化。
做金融、计费、结算这类业务,钱和相关金额字段必须用DECIMAL。这几乎是行业铁律,没有什么商量余地。浮点数在加减乘除时产生的误差虽然单个看很小,但累积起来可能对账不平,最后查半天查不出原因,其实根子就在类型选错。
3.2 DECIMAL的精度设计与存储开销
DECIMAL(M,D)里,M最大65,D最大30,且D不能大于M。举个例子,DECIMAL(10,2)表示总位数10位,其中小数位2位,也就是说整数部分最多8位,能表达的最大值是99999999.99。
实际应用中,DECIMAL的精度选择不能拍脑袋。M和D设得过大,存储空间会膨胀;设得过小,数据插入时超出范围会被截断或报错。比如DECIMAL(5,2)存999.99没问题,存1000.00就超了,因为整数部分(1000)有4位,加上小数2位总共需要4位整数+2位小数,而DECIMAL(5,2)最多容纳3位整数+2位小数。
存储字节数的估算规则是:每9位十进制数占4字节,剩余位数按1~2位占1字节、3~4位占2字节、5~6位占3字节、7~9位占4字节计算。比如DECIMAL(10,2),整数部分8位,小数部分2位,总共10位,存储时按9位+1位分,需要4+1=5字节。这个细节在数据量极大的表里会影响行大小和索引效率,值得算一下。
3.3 浮点数的坑与应用场景
虽然金融必须用DECIMAL,但科学计算、地理坐标、统计分析这类场景,FLOAT和DOUBLE也是合理选择,因为它们的范围和性能在特定条件下更好。MySQL里FLOAT的默认精度是单精度,DOUBLE是双精度。如果你不指定M和D,MySQL会按类型本身的精度来存储。
浮点数最大的坑在于比较。两个看起来一样的浮点数,用WHERE price = 19.9可能查不到记录,因为19.9在二进制里是个无限循环小数,存储时被截断成了近似值。正确的做法是使用范围比较,比如ABS(price - 19.9) < 0.0001,或者把浮点数值转换为DECIMAL后再比较。
另外,不要把时间戳当整数存,也不要把金额当浮点存,这两个是我见过最多的两类"类型错配"。时间戳在MySQL里直接用BIGINT或INT存秒数/毫秒数,金额一律DECIMAL。跨过这两条线,你的系统就稳了一大半。
4. 字符串类型:CHAR、VARCHAR、TEXT,长度只是其中一个维度
4.1 CHAR与VARCHAR的存储机制和性能差异
CHAR(N)和VARCHAR(N)里的N是字符数,不是字节数。这一点的意义在utf8mb4字符集下尤其明显:一个中文字符占3或4字节,因此VARCHAR(255)最多能存255个字符,而不是255字节。
CHAR是定长字符串。MySQL存入CHAR(N)时会用空格填充到N个字符,取出时会自动去掉末尾空格。这意味着CHAR适合存储长度基本固定的值,比如手机号(在有些国家是固定长度)、身份证号、订单号、MD5哈希值。定长的好处是存储和比较时不需要额外记录长度信息,某些场景下存取效率略高。
VARCHAR是变长字符串。它会在数据前用1~2字节记录实际长度(255个字符以内用1字节,超过用2字节),所以存储上会更紧凑。VARCHAR需要额外的长度前缀,并且在更新时可能引发"行迁移"——即行被更新导致变长后,原位置放不下整行,需要移动到新的页,影响性能和页分裂概率。但这通常只在频繁更新且长度变动剧烈的场景下才明显,普通业务不需要过度担心。
选型逻辑其实很朴素:
- 长度固定:CHAR。
- 长度会变且上限明确:VARCHAR。
- 长度上限极其不明确或超大:TEXT,但这个要谨慎用。
4.2 VARCHAR(255)为什么会成为"默认选择"
很多框架自动生成的建表语句里,字符串字段默认都是varchar(255)。这个数字的来源其实和MySQL的索引限制有关——老版本InnoDB索引键最长767字节(为什么是767?因为早期MyISAM对索引前缀字节数限制为255个字符3字节,后续受单列索引长度限制),而utf8字符集下一个字符最多3字节,2553=765,刚好接近但不能超过767,所以varchar(255)成了最安全的"大字符串"选择。
但在utf8mb4下,一个字符最多4字节,varchar(255)意味着最坏情况需要255*4=1020字节,已经超过单列索引的3072字节限制的1/3。如果你需要给一个varchar(255)字段建前缀索引,你会发现前缀长度必须控制在某个值以内。这不是不能用,而是说"255"不是万能安全值。
正确的姿势是:把字符串字段的实际最大长度量出来再加缓冲。比如用户名最大20字符,那用varchar(50)就足够宽裕了,没必要255。用户备注可能写到几百字,那就varchar(500)或varchar(1000),同时不要对这种长字段随意建索引。
4.3 TEXT类型使用时的注意点
MySQL的TEXT族包括TINYTEXT(255字节)、TEXT(64KB)、MEDIUMTEXT(16MB)、LONGTEXT(4GB)。这里说的都是字节上限,不是字符数。
TEXT类型的几个硬伤:
第一,TEXT字段不能有默认值(在MySQL 8.0之前;8.0.13之后可以指定默认值,但有一定限制),如果你一定要默认值,就得在应用层处理。
第二,TEXT字段不能直接建完整索引,只能建前缀索引。即使你把索引指定为INDEX(content(100)),这种索引也只能用于前缀匹配查询,无法像普通字段那样做精确查找排序。
第三,TEXT字段存储在行外还是行内?InnoDB对变长字段有一个"页外存储"机制,当记录长度超过页大小阈值时,会把长的字段放到单独的页。这会导致对这些字段的查询需要额外的IO,性能远低于普通短字段。所以不要把大段文章、日志、JSON直接放在业务核心表的TEXT字段里还频繁查询。
如果只是存文章内容、评论正文这类"写多读少且很少参与条件过滤"的数据,MEDIUMTEXT或LONGTEXT是合适的。如果还需要对内容做全文搜索,那应该考虑全文索引或者外接搜索引擎,而不是在TEXT字段上硬扛。
4.4 BINARY、VARBINARY与BLOB
BINARY和VARBINARY类似CHAR和VARCHAR,但存的是二进制字节串。BLOB族存二进制大对象。日常Web开发中,BLOB多用于存图片、文件等原始数据,但我不建议在关系库里直接存大文件,因为这样会让数据库体积飞速膨胀,备份、迁移都会变得很痛苦。文件应该放对象存储,数据库里只存URL或对象键。
BINARY还有个冷门用途:存储需要精确比较且大小写敏感的字符串。CHAR和VARCHAR在默认排序规则下通常不区分大小写,而BINARY比较时按字节值比较,天然区分大小写。比如存用户密码的校验盐时,用BINARY可以避免大小写折叠问题,但这种场景不常见,了解即可。
5. 日期时间类型:TIMESTAMP、DATETIME、DATE、TIME,还有时区问题
5.1 四个基础类型各自的边界
MySQL日期时间类型有DATE、TIME、DATETIME、TIMESTAMP、YEAR。日常主要用前四个。
- DATE:年月日,范围1000-01-01到9999-12-31,3字节。
- TIME:时分秒,可以带小数秒,范围'-838:59:59'到'838:59:59',3字节加小数秒存储。
- DATETIME:年月日时分秒,范围1000-01-01 00:00:00到9999-12-31 23:59:59,8字节,8.0中支持小数秒(需要额外字节)。
- TIMESTAMP:范围从1970-01-01 00:00:01 UTC到2038-01-19 03:14:07 UTC,4字节(8.0中支持小数秒会额外占用),存储时会从当前时区转换到UTC存储,查询时再转回当前时区。
这里面最容易被忽视的是TIMESTAMP的上限2038年问题。很多系统现在还活得好好的,但如果你设计一个需要长期运行且会存几十年后日期的系统,TIMESTAMP就不够用了。DATETIME能存到9999年,显然更稳妥。
5.2 时区问题怎么选
TIMESTAMP和DATETIME最重要的差异不是存储范围,而是时区处理。
TIMESTAMP存的是UTC时间戳。也就是说,无论数据库服务器的时区设置是什么,存进去的是绝对时刻,查询出来时MySQL会根据time_zone变量转成对应时区的本地时间。DATETIME则纯粹是个日历时间,不带时区语义,你存什么就是什么,查询结果也是原样。
这两者的取舍直接决定业务在不同时区下怎么表现。对于面向全球用户的应用,我建议用TIMESTAMP存"事件发生的绝对时刻"(比如订单创建时间、消息发布时间),这样不同时区的用户看到的是各自本地化后的时间。对于"用户自己输入的本地时间"(比如用户设定的生日、闹钟、会议时间),用DATETIME更合适,因为这种时间本来就与具体时区绑定,不应该被全局时区转换影响。
还要注意,TIMESTAMP字段在插入时如果不显式赋值,MySQL会默认填当前时间CURRENT_TIMESTAMP,并且在更新时也可能自动更新。这个行为可以通过DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP控制,特别适合做updated_at字段。DATETIME在8.0里也可以设置默认值为CURRENT_TIMESTAMP和ON UPDATE语法的,但有些老版本不支持,需要确认版本。
5.3 日期的存储优化与查询规范
如果只是存"年月",用DATE类型就够了,不要用VARCHAR(7)拼成"2026-03",否则排序、范围查询、日期函数全废。如果只需要年份,甚至可以直接用YEAR类型(1字节)。
日常查询中,对日期字段做比较时建议直接使用标准格式字符串:WHERE create_time >= '2026-01-01' AND create_time < '2026-02-01'。尽量避免在字段上套函数,比如WHERE DATE_FORMAT(create_time, '%Y-%m-%d') = '2026-01-01',这样会让索引失效。替代方案是用范围查询。
关于小数秒:MySQL 8.0支持TIMESTAMP(6)、DATETIME(6)这样的精度定义,括号里的数字表示小数秒位数。需要精确到毫秒或微秒的业务,直接用这种带精度的类型,别用VARCHAR存毫秒数。这里有个反直觉的点:在8.0之前,DATETIME不支持小数秒,很多系统用BIGINT存毫秒时间戳。8.0之后,用DATETIME(3)存毫秒并配上索引,查询可读性和范围扫描效率都比BIGINT更好。
5.4 实际案例:时区切换后数据错乱
我之前遇到过一个问题:系统原本部署在国内,所有时间都用DATETIME存北京时间。后来业务扩展,服务器迁到海外机房,连接串上的时区参数被设置成UTC。结果所有新增的DATETIME字段在插入时没有经过任何转换,应用层却按本地时间生成数据,最后库里出现了一批"对不上号"的记录——比如本地时间22点写入,数据库显示14点,前后查询结果不一致。
这个问题的根源是:应用层和数据库层的时区假设不一致,而DATETIME类型又不会自动做任何换算。解决方法是统一约定:应用层一律用UTC时间戳进行逻辑处理,需要展示时再转本地;数据库层面统一time_zone = '+00:00',并把"绝对时刻"字段用TIMESTAMP存储。经过这次踩坑,我对时区问题特别敏感,也建议大家在新项目设计阶段就明确时区口径,别等到线上故障才追悔。
6. 枚举、集合、JSON与其他"少有人用却很好用"的类型
6.1 ENUM和SET:能用但要用得克制
MySQL的ENUM类型在表面上是一个"下拉选择框",底层其实是用整数存储的。ENUM('small','medium','large')内部会把三个值分别映射成1、2、3。好处是存储紧凑(1~2字节),查询时能直接按字符串比较;坏处也非常明显:
- 值变更麻烦:要增加一个新的枚举值,需要ALTER TABLE修改字段定义,而ALTER TABLE在数据量大时可能锁表,代价很高。
- 排序规则会按照内部索引顺序,而不是字符串的字母顺序。比如上面的ENUM按size排序会返回small、medium、large,而不是alphabetical。
- 如果插入不在枚举列表里的值,在严格模式下会报错,非严格模式下会被截断为空字符串并告警,很容易产生脏数据。
所以我现在的建议是:如果枚举值非常稳定且数量很少(比如性别、布尔状态),ENUM勉强可用;否则还是用TINYINT或VARCHAR配合应用层字典表,灵活度更高。SET的定位是多选值,底层按位存储,如果业务需要"一个字段表达多个标签",SET在空间上确实省,但同样有变更和查询可读性的问题,非必要不用。
6.2 JSON类型:灵活与反范式
MySQL从5.7开始支持JSON类型,8.0大幅增强了相关函数。JSON列在存储时会自动校验格式,非法JSON会被拒绝,这比用VARCHAR存JSON然后依靠应用层解析要可靠得多。
JSON类型带来的诱惑是"灵活":今天加个字段,明天改个结构,不用改表结构。这让很多团队在业务早期选择了JSON字段来"快速迭代"。但它也带来几个代价:
第一,JSON字段无法直接按内部某个键值做索引,虽然8.0支持CREATE INDEX通过生成的列来间接索引,但写起来绕。
第二,JSON列的更新是整体覆盖的。你用JSON_SET修改其中一个键,底层会把整个JSON文档重新写入新位置,行变大、写放大,大文档的更新代价很高。
第三,JSON字段让逻辑变得不透明。数据库里到底是什么结构,只有应用代码知道,换人来维护时容易出问题。
我的建议是:JSON适合存"查询频率低、结构变化快"的附属信息,比如第三方接口返回的原始数据、不同渠道的扩展属性。核心业务字段一定要规范化成独立列,不要为了省事把所有配置塞进一个JSON里然后天天去取某几个键。
6.3 空间类型:GIS业务的标配
MySQL 8.0提供GEOMETRY、POINT、LINESTRING、POLYGON等空间数据类型,配合ST_Distance_Sphere、ST_Within等函数可以做一些基础的地理计算。
如果你只是需要在坐标上做简单的距离筛选,用DOUBLE存经纬度,配合Haversine公式也能工作,但精度和性能都不理想。需要开发地理位置相关功能时,把经纬度用POINT类型存,再建SPATIAL索引,才是比较正规的做法。空间索引的底层用的是R树,在范围和包含查询上的表现优于传统B树。不过说实话,MySQL的空间功能远不如专业GIS引擎或Elasticsearch等方案强大,如果业务复杂度高,还是应该考虑专用服务。
7. 从热门搜索词看大家最想解决的问题
整理这篇内容的时候,我顺手翻了一圈热门搜索词,发现大家搜索频率最高的几个问题非常集中:mysql安装教程、mysql主从复制、mysql锁原理及面试题、mysql事务处理,以及一个特别具体的需求——"把远程库的这张表同步到本地"。这些问题看起来和数据类型无关,但仔细想想,其实都和数据类型有关。
比如"远程库的这张表同步到本地"。当你用mysqldump导数据再导入本地时,如果两端MySQL版本不一致,或者源表的字段类型用了新特性(如CHECK约束、JSON默认值),导入就可能报错。我自己就遇过源端是5.7、本地是8.0,row_format和字符集规则不一样,导致导入后中文乱码的问题。解决方法是先用SHOW CREATE TABLE查看源表的完整定义,重点核对字符集(utf8mb4还是utf8)、表引擎、小数精度和时间类型。
再比如事务处理和锁机制,它们和数据类型的关系也密不可分。一个DECIMAL字段在高并发下做加减法时,因为行锁的存在,更新会串行化。如果你把金额存成DOUBLE,可能出现两个事务各自读到近似值再写回,最终数据不一致的概率会显著上升。数据类型不仅影响存储,还影响事务的一致性和并发安全。
还有一个高频词是数据类型强制转换。MySQL有一个非常"智能"但也非常"坑"的隐式转换机制,比如SELECT * FROM user WHERE id = '123abc',在非严格模式下会把字符串转换成数字123,查出来的结果可能出乎意料。经验法则:永远不要让数据库在WHERE条件里帮你做类型转换,查询参数和字段类型要严格一致。如果应用层传参的类型不可控,在SQL里显式CAST一下都比让它隐式转换靠谱。
关于mysql设置默认值为0,这往往是指DEFAULT 0的用法。但这个看起来简单的操作在字符串类型上有个陷阱:VARCHAR字段设置DEFAULT ''没问题,设置DEFAULT NULL和DEFAULT ''语义完全不同。NULL会影响索引优化的判断,COUNT(字段)不会统计NULL值,应用层读出来也容易遇到空指针。所以我在建表时都会问一遍:这个字段真的需要允许NULL吗?如果业务上逻辑上不可能为空,那就NOT NULL DEFAULT ''或NOT NULL DEFAULT 0,省一大串麻烦。
8. 关于数据类型,我最后的几点实践体会
写到这里,主干内容基本讲完了。最后分享一些我在多个项目里沉淀下来的实操习惯,不算什么高深理论,但很管用。
第一,字符集统一为utf8mb4。MySQL 8.0默认就是utf8mb4,老项目迁移时也要尽量统一。很多人纠结utf8mb4是不是比utf8更占空间,确实占,但换来的是能存Emoji和四字节扩展字符,以及不出现"问号乱码"。一个字符可能出现4字节没错,但实际业务里绝大多数内容还是1~3字节为主,这点空间成本几乎可以忽略。
第二,建表时给每个字段写上COMMENT。这个习惯太重要了。一个团队协作的项目里,字段没有注释,三个月后连写的人都可能记不清当时为什么这么设计。数据类型本身能传递一部分信息,但业务语义还是要靠注释。我见过一个字段叫status,注释空白,后来排查问题才发现它肚子里装了七八种状态,这种代码能把你逼疯。
第三,用SHOW CREATE TABLE校验你的表定义。不管是用可视化工具建的还是ORM自动生成的,我都建议上线前DBA或者有经验的人过一遍SHOW CREATE TABLE。这一步能看到隐藏的字符集继承、排序规则、行格式、索引冗余。比如VARCHAR(255)在utf8mb4下能建前缀索引的长度是多少、有没有额外生成了奇怪的全表索引,都能在这条命令里发现。
第四,不要迷信"空间越省越好"。有些新手为了让表小一点,把所有数值字段都精简到最极限的TINYINT或SMALLINT。实际上,让字段类型偏小,未来业务增长一点就会撞天花板;稍微留一档空间(比如本来可以用SMALLINT的用INT),换来的是几年之内不必做结构变更。结构变更在数据量大的时候代价极大,不只是ALTER TABLE本身耗时,一堆依赖它的查询计划、缓存、ORM映射都得重新调整。
第五,警惕ORM框架的隐式类型映射。MyBatis、Hibernate等框架在读取数据库结果集时,可能会把DECIMAL转成BigDecimal或Double,把DATETIME转成本地日期类型。如果你在Java里用一个double字段接收DECIMAL(10,2),精度会在转换时丢失。这种问题不体现在SQL层,而是完全藏在应用代码里,排查起来最耗时间。任何时候做类型透传,都要在接口层写个测试断言,确保值域和精度没有变化。
最后想多说一句:数据类型是数据库设计的最小决策单位,也是所有上层优化的基石。索引好不好用,查询快不快,数据一致不稳定,很多问题追根溯源都能落到类型选择上。与其在出问题后疯狂补丁,不如在建表第一步就多花五分钟认真想清楚。
希望大家看完这一篇之后,再次打开建表语句时能多问自己几个问题:这个字段会不会出现负数?这个字符串最长会有多长?日期时间需要跨时区吗?值会不会超过当前类型的范围?这些问题的答案会引导你做出更稳妥的判断。