用TINYINT UNSIGNED代替INT存布尔值或状态码可省3字节/行,因其仅占1字节且支持0–255范围;VARCHAR长度应按实际业务需求设定,避免过大导致内存浪费和性能下降;DATETIME占8字节、TIMESTAMP占4字节且有时区自动转换;JSON类型查询性能差、难索引,应优先规范建表。

用 TINYINT 代替 INT 存布尔值或状态码能省 3 字节/行
MySQL 中 INT 固定占 4 字节,而 TINYINT 仅占 1 字节。如果你存的是开关(0/1)、订单状态(1=待支付、2=已发货、3=已完成)这类取值范围在 0–255 的整数,TINYINT UNSIGNED 是更紧凑的选择。误用 INT 不仅浪费磁盘空间,在大表(千万级行)中还会显著增加 Buffer Pool 压力和查询扫描开销。
注意点:
-
TINYINT默认有符号,范围是 −128 到 127;如需 0–255,必须显式声明TINYINT UNSIGNED - ORM 框架(如 Django、Laravel Eloquent)可能默认映射布尔字段为
TINYINT(1),但TINYINT(1)的(1)仅影响显示宽度,不约束取值范围或存储大小 - 不要因为“以后可能扩展”提前用大类型——类型升级代价远高于初期设计成本
VARCHAR 长度设太大会拖慢排序和临时表性能
MySQL 在执行 ORDER BY、GROUP BY 或创建内部临时表时,若字段是 VARCHAR(1024),即使实际只存 10 个字符,也会按最大长度预分配内存。尤其当该字段参与索引或作为 JOIN 条件时,可能导致临时表从内存转到磁盘(Using temporary; Using filesort)。
实操建议:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 根据业务确定最大长度:用户名通常
VARCHAR(32)足够,邮箱用VARCHAR(255)(符合 RFC 标准上限),别直接写VARCHAR(1024) - UTF8MB4 下,每个字符最多占 4 字节,所以
VARCHAR(255)实际可能占用 1020 字节 —— 这会影响单页存储行数,间接影响 B+ 树深度 - 如果字段内容长度非常固定(如身份证号、UUID),优先考虑
CHAR,避免变长开销
DATETIME 和 TIMESTAMP 不只是时区差异,还涉及存储空间与自动行为
DATETIME 占 8 字节,范围是 1000–9999 年;TIMESTAMP 只占 4 字节,范围是 1970–2038 年(UNIX 时间戳限制),且会自动转换时区。选错不仅浪费空间,还可能引发隐式行为问题。
关键区别:
-
TIMESTAMP列默认开启ON UPDATE CURRENT_TIMESTAMP,而DATETIME不会,除非显式声明 - 如果应用层已统一处理时区(比如全用 UTC 存储),用
DATETIME更可控;若依赖 MySQL 自动转时区,才考虑TIMESTAMP - MySQL 5.6.4+ 支持
DATETIME(3)等微秒精度,但会额外增加 1–3 字节;TIMESTAMP(3)同理,需权衡精度是否真有必要
JSON 字段不是万能的,JSON 类型比等效的规范表结构慢且难索引
MySQL 5.7+ 支持原生 JSON 类型,但它本质是经过验证的长文本(内部以二进制格式存储),查询时仍需解析。相比拆成独立字段,它在过滤、排序、连接场景下性能明显下降。
典型陷阱:
-
WHERE JSON_CONTAINS(json_col, '"active"', '$.status')无法使用普通索引,必须建生成列 + 索引才能加速 - 频繁更新 JSON 中某个键值,会导致整条记录重写,加剧 MVCC 版本链和 undo log 压力
- 备份、复制、审计工具对 JSON 内容支持不一,某些监控系统甚至无法提取字段级统计
ALTER TABLE users ADD status TINYINT UNSIGNED AS (json_unquote(json_extract(profile, '$.status'))) STORED; CREATE INDEX idx_status ON users(status);索引生成列虽可补救,但终究是绕过范式的设计。真正需要灵活 schema 的场景,不如交给 MongoDB 或外部配置服务,别强塞进 MySQL。

















