状态字段优先用TINYINT UNSIGNED(1字节),够用即锁死;VARCHAR按真实上限设长度,避免无脑255;时间字段事件发生时间用DATETIME;主键单机用BIGINT自增,UUID须转BINARY(16)。

用 TINYINT 还是 INT 存状态字段?
状态类字段(如 status、is_deleted)常见错误是直接上 INT,以为“以后可能要扩”。但 MySQL 的 INT 固定占 4 字节,TINYINT 只占 1 字节——8 倍差距不是小数。表越大、索引越多,这 3 字节在内存、磁盘、网络传输上累积的开销越明显。
- 取值范围够用就锁死:比如只有 0/1/2,用
TINYINT UNSIGNED(0–255),别加SIGNED - 避免用
ENUM或字符串存状态:排序、索引效率低,且 ALTER TABLE 修改枚举值会锁表 - 如果真要预留扩展空间,优先考虑
SMALLINT(2 字节,-32768–32767),而不是跳到INT
VARCHAR(255) 是万能解?别信
很多建表脚本无脑写 VARCHAR(255),以为“够用又不浪费”。但 MySQL 8.0+ 中,VARCHAR 超过 255 字节时,长度编码从 1 字节升为 2 字节;更关键的是,InnoDB 在内存中为每列预留最大可能长度(含编码字节),影响 buffer pool 利用率和排序缓冲区大小。
- 按业务真实上限设长度:用户名通常
VARCHAR(32)足够,邮箱用VARCHAR(254)(RFC 5321 限制) - 超过 500 字符的字段,优先评估是否该拆到单独的宽表或用
TEXT类型(注意:TEXT不参与内存排序,ORDER BY会强制落地磁盘临时表) -
VARCHAR(N)的N是字符数,不是字节数;UTF8MB4 下一个 emoji 占 4 字节,VARCHAR(10)最多存 10 个字符,但可能占 40 字节
时间字段选 DATETIME 还是 TIMESTAMP?
两者都存时间,但行为差异极大:TIMESTAMP 自动转为 UTC 存储、读取时转回 session 时区,而 DATETIME 原样存储、不涉及时区转换。线上出过太多因时区配置漂移导致查询错乱的事故。
- 记录“事件发生时间”(如订单创建时间)——用
DATETIME,确保值稳定、可预期 - 只在需要自动时区转换 + 节省空间(
TIMESTAMP占 4 字节,DATETIME占 8 字节)且全站统一时区管理严格时,才考虑TIMESTAMP -
TIMESTAMP有效范围是 1970–2038 年,超限报错Incorrect datetime value;DATETIME支持 1000–9999 年
主键用自增 BIGINT 还是 UUID?
自增 BIGINT 简单高效,但分库分表后易冲突;UUID 分布式友好,却带来严重性能问题:无序插入导致 B+ 树频繁分裂、页碎片高、二级索引体积暴增(每个二级索引都要存完整 UUID)。
- 单机或已规划好分片路由的场景,坚持用
BIGINT UNSIGNED AUTO_INCREMENT - 真要用 UUID,别用字符串形式的
CHAR(36),改用BINARY(16)存UNHEX(REPLACE(uuid(), '-', '')),节省空间且提升比较效率 - 警惕 ORM 自动生成的
UUID主键——它往往没做二进制优化,还默认建聚簇索引,拖慢整张表
类型选错不会立刻报错,但会在高并发、大数据量、复杂查询时突然暴露:慢查询陡增、磁盘涨得快、备库延迟拉长。最常被忽略的是字符集与排序规则对索引失效的影响,比如 utf8mb4_0900_as_cs 和 utf8mb4_general_ci 混用,连 = 查询都可能走不了索引。











