糟糕!因为一个隐藏的 bug,被领导批了。。。

(hhh标题党一下,没有被批)和鱼友们分享一个今天在工作中遇到的非常容易被忽略、但可能引起线上事故的 bug。

事情的起因是刚入职时在写 WMS 系统(仓储管理系统)的需求时需要在数据库新增一个异常来源的字段来标记异常批次包的来源,对应的业务场景是商品/货物入库时会经历对应的入库流程,流程中会包含简单检品和详细检品两个步骤,如果在上述两个步骤中检查出商品/货物有问题,就会进行拆包生成一个新的异常批次包,这时需要给这个异常的批次包标记异常的来源。因为批次包的异常来源只有两个:简单检品和详细检品,因此我选择用 tinyint 类型来存储异常来源的字段,然后我在新增字段时写了这样一句 SQL:ALTER TABLE 表名 ADD exception_source tinyint(1);

于是隐藏的 bug 出现了。。。有没有眼尖的球友已经发现了呢?!

问题就出在这个 tinyint(1) 上!为什么这里会出现隐藏的 bug 呢?

原来是因为 MySQL 数据库会将使用 tinyint(1) 存储的数据隐式地转换为 boolean 类型,而往往我们会将对应的实体类字段类型设为 Integer,从而导致无法进行强制转换。这个 bug 是不是藏得很隐蔽呢!

趁着这个机会,我们也来了解一下 tinyint(m) 中数字 m 的作用。这里的数字 m 表示的是显示宽度,与该字段的物理存储长度没有关系。我们来看个例子。

-- 新建一张测试表 CREATE TABLE `test_demo` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `t1` tinyint(1) unsigned zerofill COMMENT '第一列', `t2` tinyint(2) unsigned zerofill COMMENT '第二列', `t3` tinyint(3) unsigned zerofill COMMENT '第三列', `t4` tinyint(4) unsigned zerofill COMMENT '第四列', `t5` tinyint(5) unsigned zerofill COMMENT '第五列', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- 插入测试数据 INSERT INTO test_demo VALUES(NULL,6,6,6,6,6); INSERT INTO test_demo VALUES(NULL,123,123,123,123,123);

然后,我们来看查询的结果:

image.png

可以看到,我们在第一行插入的值都为 6,然后显示的结果却不相同。tinyint(1) 这里的 1 表示的是 最短显示一个字符。tinyint(2) 这里的 2 表示的是 最短显示两个字符,依次递增......

第二行的前两列,当插入的值字符长度超过数字 m 时,会直接显示对应的值;当字符长度小于 m 时,就需要指定拿某个字符来填充,比如 zerofill(表示用 0 填充)。也就是说,tinyint(1) 与 tinyint(n),在存储和计算时是完全相同的,tinyint(1) 并不会比 tinyint(n) 占用更少的存储空间。因此,也就不必遵循的数据类型优化原则:更小的通常更好。当我们不指定 tinyint 后的数字时,此时 tinyint 会默认为 tinyint(4),只占 1 个字节,表示范围为表示 -128 ~ 127。

而字符串类型 varchar(m) 后的数字 m 则表示字段中可以存储的最大字符长度,即字段长度。根据设置,当插入超出字段长度的数据时,我们很可能会收到错误提示;即使没有收到错误提示,我们插入的数据也会被自动截断以适应该字段的预定义长度。所以,varchar(2) 和 varchar(10) 的含义是不同的,其真实反映了该字段可以存储的数据长度。

总结: 在进行 MySQL 表结构设计时,要避免设计为 tinyint(1) 这种类型,以免与 boolean 类型数据结构进行混淆。引起不必要的 bug。

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
CrazyKeyboar
下载 APP