mysql列类型需精准选择:整数按需选tinyint/int/mediumint;固定长字段用char;金额用decimal;时间用datetime防时区漂移;json用原生类型;避免滥用varchar和enum。

MySQL 表中列的数据类型不是随便选的——选错一个,轻则浪费空间、查询变慢,重则数据截断、计算出错、时区混乱,甚至上线后才发现不能存手机号或金额。
整数类型怎么选:从 TINYINT 到 BIGINT 的真实取舍
不是“越大越安全”。TINYINT 占 1 字节,有符号范围是 -128 ~ 127;加 UNSIGNED 就变成 0 ~ 255——刚好够存 HTTP 状态码、性别标识、订单状态枚举值。
- 用户 ID、订单号别盲目用
BIGINT:如果业务确定不会超 21 亿(INT UNSIGNED上限),用INT节省 4 字节/行,千万级表能省几十 MB -
AUTO_INCREMENT必须和列类型匹配:比如TINYINT UNSIGNED自增到 255 后再插入,会报ERROR 1062 (23000): Duplicate entry '255' for key(实际是溢出回绕,但报错不直观) -
MEDIUMINT是个被低估的选项:占 3 字节,范围 ±838 万,适合中小规模的 ID 或计数器,比INT省 1 字节又比SMALLINT更宽裕
VARCHAR 和 CHAR 的性能陷阱:长度真得写对
VARCHAR(255) 不代表每行都占 255 字节,但它的「最大长度声明」会影响 MySQL 的内部处理逻辑——尤其是排序、临时表、内存分配。
- 手机号、身份证号、UUID 这类**固定长度**字段,用
CHAR更高效:CHAR(11)存手机号比VARCHAR(11)在索引和排序时更稳定,无变长开销 -
VARCHAR(N)的N不要拍脑袋写 1024 或 65535:超出 255 会导致额外 2 字节长度头;超过 5000 字符的字段,考虑是否该拆到单独的扩展表 - 建表时没指定字符集?默认可能是
utf8mb4,那么VARCHAR(255)最坏要预留 255×4 = 1020 字节——这直接影响 InnoDB 页内能存多少行
DATETIME vs TIMESTAMP:时区问题不是理论,是凌晨三点的告警
两者都存 “年-月-日 时:分:秒”,但行为完全不同:TIMESTAMP 值在写入时转成 UTC 存储,读取时再按当前会话时区转回来;DATETIME 是“原样保存,原样返回”。
- 记录日志、审计时间、合同签署时间——选
DATETIME:它不随数据库时区变更而漂移,DBA 把服务器时区从 CST 改成 UTC,你的“创建时间”不会突然少 8 小时 -
TIMESTAMP有隐式默认行为:未显式赋值时自动设为CURRENT_TIMESTAMP,且只支持到 2038 年(Unix 时间戳溢出),金融系统或长期运行服务必须避开 - 同一张表里混用两者?可以,但别指望它们在 ORDER BY 里表现一致——尤其跨时区应用查“最新 10 条记录”时,
TIMESTAMP可能因会话时区不同返回不同结果
金额、精度、JSON:三个高频误用场景
用 FLOAT 存余额?用 VARCHAR 存 JSON 字符串?用 INT 存带两位小数的价格?这些都不是“能跑就行”,而是生产事故预备役。
- 钱必须用
DECIMAL(10,2):不是为了“看起来精确”,是因为FLOAT在二进制下无法精确表示 0.1,多次加减后误差累积(比如 0.1 + 0.2 ≠ 0.3) - MySQL 5.7+ 支持原生
JSON类型,别再用TEXT:它支持校验、索引(通过虚拟列)、路径查询(->操作符),而TEXT只能当黑盒字符串全文匹配 -
ENUM和SET看似省空间,但改值要ALTER TABLE锁表;状态字段建议用TINYINT UNSIGNED+ 注释说明,更灵活、迁移成本更低
最常被忽略的一点:数据类型一旦上线,改起来比加索引还重——尤其是修改列类型触发全表重建(ALGORITHM=INPLACE 不总适用)。设计阶段多花 10 分钟想清楚 TINYINT 还是 SMALLINT,可能省掉后续一次半夜紧急发布。











