
个人主页 我不会起名字322 欢迎各位大佬莅临其他栏目 技术栈学习笔记 其他栏目 力扣Hot100题目解析 其他栏目 Go项目学习笔记 文章目录MySQL 表设计那些后期几乎改不动的决定为什么单独讲改不动一、主键自增还是 UUID二、金额永远不要用 FLOAT / DOUBLE三、时间DATETIME 还是 TIMESTAMP四、VARCHAR 的长度不是随便写的五、大字段TEXT / BLOB应该拆表六、外键物理外键还是应用层保证七、别用预留字段一张决策清单MySQL 表设计那些后期几乎改不动的决定为什么单独讲改不动加个字段、加个索引线上就能做MySQL 5.6 支持 Online DDL8.0 更快。但有些决定一旦表里有几千万行数据改动成本会变成停服几小时 全量数据迁移。这篇只讲这类决定。每条都给出为什么难改和该怎么选。一、主键自增还是 UUID这是被低估最严重的一条。InnoDB 的表是聚簇索引——数据行本身就存在主键的 B 树叶子节点上不是像 MyISAM 那样另存一份。用自增主键时新行永远插在最右边顺序写入页是满的。用随机 UUID 时每次插入的位置都是随机的页分裂目标页满了就要拆成两页还牵动父节点。随机 IO写入不再是顺序的。二级索引膨胀InnoDB 的每个二级索引叶子节点里都存着主键值。主键从 8 字节的 bigint 变成 36 字节的 char(36)每一个二级索引都被放大。实测经验是随机 UUID 主键的写入吞吐可能只有自增主键的几分之一而且随着表变大差距更明显。怎么选场景建议单库单表、写入量大BIGINT AUTO_INCREMENT需要对外隐藏 ID / 分库分表自增主键 独立的业务唯一列如order_no别拿 UUID 当主键分布式且必须全局唯一 ID雪花算法这类趋势递增的 bigint不要用纯随机 UUID如果非要用 UUID至少用UUID v7或者把 UUID 转成有序的 binary(16)——但那时你已经该问自己为什么不直接用 bigint 了。二、金额永远不要用 FLOAT / DOUBLE-- ❌ 不要这样amountFLOAT-- ✅ 这样amountDECIMAL(12,2)FLOAT/DOUBLE是二进制浮点数没法精确表示 0.1。做几次加减就可能出现0.30000000000000004这种结果。金额对不上账是要出事故的。DECIMAL是定点数精确。代价是运算比浮点慢但金额场景这个代价必须付。如果对性能极度敏感也可以用BIGINT存分整数运算最快代价是所有地方都要记得乘除 100——容易漏我不推荐作为默认方案。三、时间DATETIME 还是 TIMESTAMPDATETIMETIMESTAMP占用5 字节MySQL 5.6可带小数秒4 字节范围1000-01-01 ~ 9999-12-311970 ~2038-01-19时区存什么就是什么按time_zone转换存储和读取自动更新需手动ON UPDATE支持两个坑TIMESTAMP有 2038 问题。做长期系统的别用它存业务时间。TIMESTAMP会跟着时区变。数据库time_zone一改读出来的值就变了。跨时区部署时这个特性有时是优点但更多时候是线上数据怎么和我本地不一样的根源。建议业务时间统一用DATETIME应用层统一用 UTC 存储、展示时再转本地时区。created_at/updated_at这种审计字段可以用TIMESTAMP省一个字节但要清楚它的限制。四、VARCHAR 的长度不是随便写的nameVARCHAR(255)-- 习惯性写 255长度影响的是内存里的临时表和排序MySQL 处理ORDER BY/GROUP BY时可能建内部临时表而VARCHAR在临时表里是按定义的最大长度分配内存的不是按实际内容。VARCHAR(255)和VARCHAR(20)在内存里的开销差一个数量级。另外索引也有长度限制InnoDB 单列索引前缀最长 3072 字节utf8mb4下VARCHAR(768)就到顶了。建议按业务实际需要写。手机号VARCHAR(20)、身份证VARCHAR(18)、姓名VARCHAR(50)就够别一律 255。五、大字段TEXT / BLOB应该拆表TEXT/BLOB本身不长在主记录里存的是指针但SELECT *会把它们一起拉出来白白产生大量 IO。它们的更新可能导致行溢出页的重组。建议文章正文、图片二进制这类大字段拆到独立的扩展表用主键关联主表只留元数据。查询列表页时就不会被拖累。六、外键物理外键还是应用层保证物理外键FOREIGN KEY能保证一致性但在高并发写入下每次写入都要检查父表会在父表上加锁容易产生意外的锁等待。分库分表后物理外键直接不可用。DDL 变更比如改父表结构会变得很麻烦。建议互联网业务普遍不用物理外键改由应用层保证 定期跑一致性校验脚本。这不是不规范是在一致性和可用性之间做的取舍——前提是你的应用层真的做了校验。七、别用预留字段reserve1VARCHAR(255),reserve2VARCHAR(255),看起来省事实际上类型和名字都是猜的真用的时候大概率不合适。语义不明接手的人不敢动。加索引时还是要改表。现代 MySQL 加字段已经很便宜了ALTER TABLE ... ADD COLUMN是 INSTANT 操作几乎不锁表。该加就加别预留。一张决策清单决定选错的代价建议主键类型写入性能永久下降改要重建全表BIGINT AUTO_INCREMENT金额类型对账出错属于事故DECIMAL时间类型2038 后失效 / 时区错乱DATETIME 应用层统一 UTCVARCHAR 长度临时表内存放大按实际业务写大字段位置全表查询变慢拆扩展表物理外键高并发下锁等待应用层保证预留字段基本都白留需要时再加表设计最贵的地方在于它不是写错了改一下而是写错了要迁移几个亿的数据。上面这些决定建表那天多花十分钟想清楚能省掉后面很多个通宵。