易君召
发布于 2026-08-22 / 作者:易君召 / 2 阅读
0

如何设计有效的数据库表结构

数据库表设计核心目标:合理存储、减少冗余、保证数据一致性、查询性能好、易于扩展、满足业务约束,通用流程:业务分析 → 概念模型 → 逻辑模型 (范式 + 反范式权衡) → 物理表设计 → 索引、约束、分库分表、评审优化。下面结合实战,包含原则、步骤、字段设计、主键外键、范式取舍、常见坑、示例。

一、前期:梳理业务与实体

  1. 识别业务实体:比如订单系统:用户、订单、订单明细、商品、收货地址、支付记录。

  2. 识别实体属性:每个实体有哪些信息。

  3. 识别实体关系:

  • 一对一:用户 ↔ 用户详情

  • 一对多:用户 ↔ 订单;订单 ↔ 订单明细

  • 多对多:订单 ↔ 商品(必须拆中间表:订单明细)

多对多绝对不能直接两边表加字段,必须建立关联中间表。

二、数据库三大范式(理解,不要死套)

第一范式 1NF:列不可再分

✅ 一个字段只存一个值,不要逗号分隔存多个 id、多个手机号。 ❌ 错误:tags"java,python,go" ✅ 正确:标签单独一张表,通过关联表做多对多。

第二范式 2NF:消除部分依赖

联合主键场景,所有非主键字段,必须完全依赖整个主键,不能只依赖主键一部分。

常见于中间表,避免部分字段只依赖主键其中一列。

第三范式 3NF:消除传递依赖

非主键字段,不能依赖其他非主键字段。

示例:订单表里不要存用户姓名,只存user_id,姓名去用户表查询。

范式的取舍:允许适度反范式

范式优点:减少冗余,更新一致性好; 代价:多表 JOIN,查询变慢。

反范式场景(刻意冗余)

  1. 读多写少业务,为了减少 join,冗余少量字段,例如订单表冗余user_namegoods_name

  2. 统计汇总字段,订单表冗余商品总金额;

反范式要有代价:数据更新要做双写,存在数据不一致风险,需要业务层或 binlog 同步兜底,不要随意反范式。

现实业务:优先 3NF,性能瓶颈出现再做反范式优化,不要一开始就大量冗余。

三、物理表设计实战要点(最重要)

1. 主键设计

方案 A:自增 ID(BIGINT)

  • 优点:简单,InnoDB 聚簇索引有序,插入性能高;

  • 缺点:分库分表后 ID 冲突,容易被爬虫遍历。

方案 B:UUID / GUID

  • 缺点:无序,InnoDB 主键随机写入,页分裂,性能差,不推荐做主键。

方案 C:雪花 ID(Long 64 位)

分布式场景首选,有序,适合分库分表;Java 业务系统最常用。

✅ 最佳实践:

  • 单库:BIGINT AUTO_INCREMENT

  • 分布式:雪花 ID BIGINT 作为主键

  • 禁止业务字段当主键:手机号、身份证号不能做主键,业务会变更。

2. 字段选型原则

  1. 够用就好,不要过度放大类型

    业务场景

    推荐类型

    不推荐

    id、数量

    BIGINT

    INT(溢出风险)

    金额

    DECIMAL(18,2)

    double/float(浮点精度丢失)

    字符串短

    VARCHAR(32/64/128)

    CHAR (255) 浪费空间

    大文本、备注

    TEXT

    VARCHAR(20000)

    时间

    DATETIME (3) 带毫秒

    不要用 varchar 存时间;不要用 timestamp(2038 问题)

    布尔状态

    TINYINT 0/1

    不要用 bool

时间统一存 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 支持外键;

  • 高并发业务不建议数据库层外键约束:外键会锁表,影响写入性能;改用业务代码做逻辑校验。

  • 小内部系统、低并发可以使用外键保证参照完整性。

四、索引设计(表结构一部分)

  1. 主键自带聚簇索引;

  2. where 条件、join 字段、order by、group by 建立索引;

  3. 联合索引最左前缀原则;

  4. 避免索引失效:函数运算、隐式转换;

  5. 不要建立大量无用索引:索引加速查询,降低写入更新性能。

小表不需要建很多索引。

五、命名规范(团队协作必备)

  1. 表名:小写 + 下划线,禁止驼峰;user_order,不要 UserOrder

  2. 表名尽量复数 / 业务名词:order_item订单明细

  3. id 命名:关联其他表统一user_id,不要写成 uid、u_id 混用

  4. 所有字段、表写 comment 注释,生产库禁止无注释表。

六、常见设计错误(避坑清单)

  1. ❌ 使用业务字段做主键:手机号、身份证;

  2. ❌ 字符串存储时间、数字;

  3. ❌ float/double 存金额;

  4. ❌ 逗号分隔多个 id 存到一个字段;

  5. ❌ 大表大量 NULL 字段;

  6. ❌ 过度范式,疯狂拆分小表,join 爆炸;

  7. ❌ 过度反范式,到处冗余,数据不同步;

  8. ❌ 不做逻辑删除,直接物理删除数据无法恢复;

  9. ❌ 字段类型随意放大,全部 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='订单明细表';

八、大表后续演进

  1. 单表数据量千万级别:考虑分表(水平分表按 user_id、时间);

  2. 冷热数据分离:历史订单归档;

  3. 大字段单独拆分:大文本不要放在高频查询主表;

  4. OLTP 业务库不要做复杂统计查询,统计走数仓。

九、完整设计流程总结

  1. 梳理业务,抽取实体、属性、实体关系;

  2. 画 E-R 图,按照 1/2/3NF 构建逻辑模型;

  3. 根据业务读写权衡,做适度反范式;

  4. 选择主键策略、字段类型、增加审计字段、逻辑删除;

  5. 设计索引、唯一约束;

  6. SQL 建表,评审;

  7. 上线后,随着数据量增长持续优化。


本文原创作者:易君召,详见:https://www.yijunzhao.cn/authors/yijunzhao,转载请注明出处。

原文链接 https://www.yijunzhao.cn/archives/effective-database-table-design-guide

欢迎访问 小易撩挨踢

https://www.yijunzhao.cn/