数据库表设计核心目标:合理存储、减少冗余、保证数据一致性、查询性能好、易于扩展、满足业务约束,通用流程:业务分析 → 概念模型 → 逻辑模型 (范式 + 反范式权衡) → 物理表设计 → 索引、约束、分库分表、评审优化。下面结合实战,包含原则、步骤、字段设计、主键外键、范式取舍、常见坑、示例。
一、前期:梳理业务与实体
识别业务实体:比如订单系统:用户、订单、订单明细、商品、收货地址、支付记录。
识别实体属性:每个实体有哪些信息。
识别实体关系:
一对一:用户 ↔ 用户详情
一对多:用户 ↔ 订单;订单 ↔ 订单明细
多对多:订单 ↔ 商品(必须拆中间表:订单明细)
多对多绝对不能直接两边表加字段,必须建立关联中间表。
二、数据库三大范式(理解,不要死套)
第一范式 1NF:列不可再分
✅ 一个字段只存一个值,不要逗号分隔存多个 id、多个手机号。 ❌ 错误:tags:"java,python,go" ✅ 正确:标签单独一张表,通过关联表做多对多。
第二范式 2NF:消除部分依赖
联合主键场景,所有非主键字段,必须完全依赖整个主键,不能只依赖主键一部分。
常见于中间表,避免部分字段只依赖主键其中一列。
第三范式 3NF:消除传递依赖
非主键字段,不能依赖其他非主键字段。
示例:订单表里不要存用户姓名,只存
user_id,姓名去用户表查询。
范式的取舍:允许适度反范式
范式优点:减少冗余,更新一致性好; 代价:多表 JOIN,查询变慢。
反范式场景(刻意冗余)
读多写少业务,为了减少 join,冗余少量字段,例如订单表冗余
user_name、goods_name;统计汇总字段,订单表冗余商品总金额;
反范式要有代价:数据更新要做双写,存在数据不一致风险,需要业务层或 binlog 同步兜底,不要随意反范式。
现实业务:优先 3NF,性能瓶颈出现再做反范式优化,不要一开始就大量冗余。
三、物理表设计实战要点(最重要)
1. 主键设计
方案 A:自增 ID(BIGINT)
优点:简单,InnoDB 聚簇索引有序,插入性能高;
缺点:分库分表后 ID 冲突,容易被爬虫遍历。
方案 B:UUID / GUID
缺点:无序,InnoDB 主键随机写入,页分裂,性能差,不推荐做主键。
方案 C:雪花 ID(Long 64 位)
分布式场景首选,有序,适合分库分表;Java 业务系统最常用。
✅ 最佳实践:
单库:
BIGINT AUTO_INCREMENT分布式:雪花 ID BIGINT 作为主键
禁止业务字段当主键:手机号、身份证号不能做主键,业务会变更。
2. 字段选型原则
够用就好,不要过度放大类型
时间统一存 UTC 或者业务时区;业务代码处理格式化,不要数据库存格式化字符串。
3. 状态字段设计
用 tinyint 数字编码,注释写清楚枚举含义,不要字符串 status='success'。 示例:order_status TINYINT COMMENT '0待支付 1已支付 2已取消 3已完成'
4. 通用审计字段(几乎每张表都加)
create_time DATETIME(3) NOT NULL COMMENT '创建时间',
update_time DATETIME(3) NOT NULL COMMENT '更新时间',
create_by BIGINT COMMENT '创建人id',
update_by BIGINT COMMENT '更新人id',
is_deleted TINYINT DEFAULT 0 COMMENT '逻辑删除 0未删 1已删除'
优先逻辑删除,物理删除尽量少用;逻辑删除查询必须带上
where is_deleted=0。
5. NULL 和默认值
尽量避免字段为 NULL;允许为空的业务字段,评估是否可以设置默认值。 NULL 会影响索引效率,判断要is null,不能=null,容易踩坑。
6. 外键:业务系统谨慎使用
MySQL InnoDB 支持外键;
高并发业务不建议数据库层外键约束:外键会锁表,影响写入性能;改用业务代码做逻辑校验。
小内部系统、低并发可以使用外键保证参照完整性。
四、索引设计(表结构一部分)
主键自带聚簇索引;
where 条件、join 字段、order by、group by 建立索引;
联合索引最左前缀原则;
避免索引失效:函数运算、隐式转换;
不要建立大量无用索引:索引加速查询,降低写入更新性能。
小表不需要建很多索引。
五、命名规范(团队协作必备)
表名:小写 + 下划线,禁止驼峰;
user_order,不要 UserOrder表名尽量复数 / 业务名词:
order_item订单明细id 命名:关联其他表统一
user_id,不要写成 uid、u_id 混用所有字段、表写 comment 注释,生产库禁止无注释表。
六、常见设计错误(避坑清单)
❌ 使用业务字段做主键:手机号、身份证;
❌ 字符串存储时间、数字;
❌ float/double 存金额;
❌ 逗号分隔多个 id 存到一个字段;
❌ 大表大量 NULL 字段;
❌ 过度范式,疯狂拆分小表,join 爆炸;
❌ 过度反范式,到处冗余,数据不同步;
❌ 不做逻辑删除,直接物理删除数据无法恢复;
❌ 字段类型随意放大,全部 varchar (255)。
七、简单实战示例:订单表设计
CREATE TABLE `order` (
`id` BIGINT NOT NULL COMMENT '雪花ID主键',
`user_id` BIGINT NOT NULL COMMENT '用户id',
`order_no` VARCHAR(64) NOT NULL COMMENT '订单编号',
`total_amount` DECIMAL(18,2) NOT NULL COMMENT '订单总金额',
`order_status` TINYINT NOT NULL COMMENT '0待支付,1已支付,2已取消,3已完成',
`pay_time` DATETIME(3) NULL COMMENT '支付时间',
`create_time` DATETIME(3) NOT NULL,
`update_time` DATETIME(3) NOT NULL,
`create_by` BIGINT NULL,
`update_by` BIGINT NULL,
`is_deleted` TINYINT DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
UNIQUE KEY `uk_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';
订单明细(一对多)
CREATE TABLE `order_item` (
`id` BIGINT NOT NULL,
`order_id` BIGINT NOT NULL COMMENT '订单id',
`goods_id` BIGINT NOT NULL COMMENT '商品id',
`goods_name` VARCHAR(256) NOT NULL COMMENT '冗余商品名称,反范式优化',
`price` DECIMAL(18,2) NOT NULL,
`num` INT NOT NULL COMMENT '购买数量',
`create_time` DATETIME(3) NOT NULL,
`update_time` DATETIME(3) NOT NULL,
`is_deleted` TINYINT DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';
八、大表后续演进
单表数据量千万级别:考虑分表(水平分表按 user_id、时间);
冷热数据分离:历史订单归档;
大字段单独拆分:大文本不要放在高频查询主表;
OLTP 业务库不要做复杂统计查询,统计走数仓。
九、完整设计流程总结
梳理业务,抽取实体、属性、实体关系;
画 E-R 图,按照 1/2/3NF 构建逻辑模型;
根据业务读写权衡,做适度反范式;
选择主键策略、字段类型、增加审计字段、逻辑删除;
设计索引、唯一约束;
SQL 建表,评审;
上线后,随着数据量增长持续优化。
本文原创作者:易君召,详见:https://www.yijunzhao.cn/authors/yijunzhao,转载请注明出处。
原文链接
欢迎访问 小易撩挨踢