去线上库捞表结构时,你会发现一个有趣现象:INT 几乎成了 MySQL 整数类型的默认配置。状态位用 INT,计数用 INT,连只需要 0/1 的标记也用 INT。我早年在项目里也这么干,因为模板里面主键默认就是 int(11),字段拿来就往上套。可等到表量级上来、慢查询变多、自增主键逼近上限、接口返回大整数被前端截断之后,我才意识到:整数类型选得好不好,根本不只是"省几个字节"的问题。这篇文章不聊安装、不堆概念,只把 TINYINT、INT 和 BIGINT 的范围、空间、属性组合、场景选型和常见坑一次讲清楚,适合刚接触 MySQL 的新手,也适合正在做表结构评审、改表或者接口对接的开发者。
1. 先搞清楚家底:TINYINT、INT、BIGINT 的存储边界
先把最基础的东西刻在脑子里。MySQL 的整数类型按字节数从小到大排列,常见的有 TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT。日常开发中 TINYINT、INT、BIGINT 三兄弟出现频率最高,所以要理解这三者到底能存多少数据、各自占多少空间。
1.1 一张表看懂取值范围和内存开销
| 类型 | 占字节 | 有符号范围 | 无符号范围 |
|---|---|---|---|
| TINYINT | 1 | -128 到 127 | 0 到 255 |
| INT | 4 | -2147483648 到 2147483647 | 0 到 4294967295 |
| BIGINT | 8 | -9223372036854775808 到 9223372036854775807 | 0 到 18446744073709551615 |
记忆方法也很简单:1 字节等于 8 个 bit,n 字节能表达的数值总量是 2 的 8n 次方。有符号类型需要拿出最高位做符号位,所以 TINYINT 正数最大是 2 的 7 次方减 1,也就是 127;负数最小是负的 2 的 7 次方,也就是 -128。无符号类型把所有位都用来表达数值,所以 TINYINT 最大是 255。INT 是 4 字节,BIGINT 是 8 字节,把 n 分别替换成 4 和 8 就能推出上面的范围。
如果觉得数字太抽象,可以换个角度理解:TINYINT 就是一张小纸条,适合写"0、1、2"这种一眼能数完的状态;INT 是一张 A4 纸,能够写平时绝大多数数量;BIGINT 是一本厚笔记本,主要给那种大到无法预期的流水和 ID 使用。用一张小纸条能搞定的事情,没必要掏出一本笔记本;反过来,明知道内容会写满笔记本,就别为了省事赖在小纸条上。
1.2 显示宽度和 ZEROFILL 的坑,十个人里有八个理解错
再说一个高频误区:很多人看到 int(11),以为括号里的 11 是"最多只能存 11 位数字"。这是错的。INT 本身能存的上限 2147483647 只有 10 位,但你在 MySQL 中把字段写成 int(11),依然可以存 21 亿不到的数字,也可以存负数,根本不会被 11 限制住。括号里的数字叫"显示宽度",它只有在结合 ZEROFILL 属性时才有视觉上的作用。
举个例子,字段定义为 INT(5) ZEROFILL,当插入 123 时,查询结果会显示为 00123。如果没有 ZEROFILL,这个宽度完全不影响实际存储和计算。而 ZEROFILL 本身又会自动把字段变成无符号类型,导致负数无法写入,所以现在新版本 MySQL 已经逐步把整数显示宽度属性淡出。日常建表,直接写 INT、BIGINT 就够了,完全不需要在括号里填一堆没有语义的数字。
这个坑的实际影响在于很多旧工具生成的建表语句都是 int(11)、tinyint(1)。看到 tinyint(1) 时,又有人会以为它只能存 0 或 1,于是拿它当布尔类型用。其实 TINYINT(1) 默认有符号范围还是 -128 到 127,只是展示时最多显示 1 位。某些 ORM 框架确实会把 tinyint(1) 特殊处理成布尔值,但这是框架行为,不是 MySQL 本身的性质,别混为一谈。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 实际业务怎么选:状态位、计数、主键各有各的逻辑
知道了存储范围,下一步就是选型。选型不能单独看"这个字段现在要存什么",还要看它未来几年可能变成什么。我习惯把整数字段分成三类:状态位、普通计数和主键 ID。
2.1 能用 TINYINT 的字段,别让 INT 来凑热闹
最常见的 TINYINT 使用场景是订单状态、支付状态、逻辑删除标记、渠道来源这类枚举值。订单状态通常就那几种:待支付、已支付、已发货、已取消、已退款,最多再加十来个细分状态,1 个字节的 TINYINT 完全够用。逻辑删除标记只有"未删除"和"已删除",用 TINYINT DEFAULT 0 也是最标准的选择。
有些团队习惯把所有整数字段都定义成 INT,理由是"反正以后可能要加状态,INT 保险"。这句话听上去没毛病,实际代价被你忽略了。每个 INT 字段比 TINYINT 字段多 3 个字节,一张表如果有 5 个本应使用 TINYINT 的字段,1000 万行数据就会多出 150MB 左右的裸数据。如果这几个字段还建了索引,那么 InnoDB 存索引时也要多占用空间,扫描数据时从磁盘读到内存的数据量也会变大。
我并不是说所有状态都一定用 TINYINT。如果状态枚举多到超过 1
