数据库设计基础
一个糟糕的数据库设计,是一笔你付一辈子的税。想想一张把客户地址存成单个逗号分隔字符串的购物表。它上线第一天是好的、帮你省了一次 join、也过了代码评审。一年多后市场部要"某个城市的全部客户",你发现唯一能答的办法是对几百万行做一次 LIKE '%城市%' 全表扫描。这条查询现在要四秒,DBA 建了个只能将就用的索引,高峰时段 App 就卡死。这就是跳过数据库设计真正的代价:不是一次大失败,而是一千个小失败,每一个都在悄悄侵蚀性能、烧掉无数工程师工时。
好的数据库设计不是用聪明工具、也不是背第三范式定理证明。它是一小撮结构性决策——怎么把数据拆成表、怎么衔接,以及怎么为增长做规划——决定你未来查询是轻松还是折磨。本指南从"成本与痛苦"的视角讲基础:哪些设计选择能避免坑、哪些模式能规模化、以及怎么识别一个迟早来向你索债的设计。
忽略规范化的代价(以及过度规范化的代价)
规范化名声不好,因为新手要么完全无视它,要么虔诚到把每条查询都变成六表 join。真相是务实的。规范化的目标是避免三种具体的失败模式:重复数据各说各话、因为依赖数据还没出现而插入失败、以及为改一个事实要动很多行。

- 消除重复(1NF):别在一列里塞重复的一组值。逗号分隔列表是经典原罪——难查询,也几乎没法有效索引。
- 消除部分依赖(2NF):每个非键列都应依赖整个主键,而不是它的某一部分。这在复合键表里最关键。
- 消除传递依赖(3NF):非键列不应依赖于另一个非键列。如果你存了
city和zip_code,而邮编能决定城市,那就是一个传递依赖——数据一打架就会反咬你。
但规范化不是免费的。每多一张表就多 join,join 花时间也增加查询计划复杂度。务实规则:规范化到消除真实完整性风险为止,然后选择性地反规范化——而且只有在测过那条确实有需要的查询之后。成熟系统是刻意做这笔权衡的;不成熟的系统是被动地绊进去的。
选对键
键是关系的主干,而"自然键 vs 代理键"之争是很多设计很早就走偏的地方。对多数系统的最佳实践,短版本是:

- 用代理主键(自增整数或 UUID)做每张表的主键。邮箱、用户名、税号这类自然键会变、或被发现并不唯一——而一旦它们变,所有外键就断了。
- 在自然键上加唯一约束,只要唯一是业务规则,就让数据库去强制它,哪怕代理键会把它藏起来。
- 刻意选择 UUID 还是自增。UUID 适合分布式系统、能避免枚举攻击,但它的索引随机性可能拖慢插入性能、撑大索引体积。自增紧凑又快,但会泄露数量有规律、且跨库可能冲突。
一个经典错误:用邮箱给 users 表做键,然后再支持"修改邮箱"功能。现在你在更新一张被十张表引用的主键——级联、失效引用、噩梦级迁移纷至沓来。稳定的代理键加上邮箱唯一约束,安全与灵活兼得。
关系、join,以及每个设计者必须懂的类型
关系定义表如何连接,搞对了就能从"清爽的查询"和"维护迷宫"之间分出胜负。基础类型很简单,但人们还是会搞错:

- 一对一:拆分大表,或按不同访问模式划分数据。
users表和user_profiles表是典型例子。 - 一对多:一个订单有多个明细项。子表持有指向父表的外键。
- 多对多:一个学生选多门课、一门课有多个学生。关系型数据库里这总需要一张连接表。
多对多是最容易糊弄过去的地方。"一个产品可属于多个分类、一个分类又有多个产品"的那一刻,你就需要第三张表把它拆成两对"一对多"。跳过连接表、试图在分隔列里存分类 ID,就是换了个皮的同款逗号分隔陷阱,在查询压力下会以同样的方式崩掉。
设计选择如何决定你的存储和迁移成本
很少有人真去算数据库设计里的钱,但你今天的选择决定了明天的运维账单。两个都通过同一套单元测试的设计,在规模化后的存储和查询成本上可能差一个数量级:

- 行式 vs 宽表:几百列、大部分为空的宽表,在行式存储上很浪费、也难扫描。把少用的列拆到关联表,让热路径保持窄。
- 索引策略:每多一个索引写入就更慢、也占磁盘。对写入重、增长快的表,大方建索引可能把插入吞吐砍一半、还撑大存储。
- 数据类型纪律:什么都用
VARCHAR(255)浪费存储又丢了类型完整性。真正日期列存成文本就没法做范围优化,之后会悄悄累加扫描。
这些在第一天都不算"错",但每一项都是复利式成本。你要是做数据量和抽取流程要紧的系统,设计成本会比你想的更快变成拦路虎;想提前把数据用起来的,可以先读一篇AI 数据分析,或先补数据分析基础,都有助于你想清楚哪些查询才真的值钱。
按真实权衡对比主流数据库
选数据库是个披着工具决策外衣的设计决策。不同引擎强制不同的一致性、扩展和查询权衡,要对准你的真实负载来选,而不是图省事。

| 平台/数据库 | 核心特性 | 价格参考 |
|---|---|---|
| PostgreSQL | ACID、丰富数据类型、JSONB、强开源生态、出色索引 | 自托管免费;托管云约每月 ¥108–144 起 |
| MySQL | 应用广泛、复制成熟、兼容多数 Web 应用、运维简单 | 自托管免费;托管约每月 ¥72–144 |
| SQLite | 零服务器内嵌库、单文件存储、适合小应用和本地工具 | 免费,随附 |
| MongoDB | 文档模型、灵活 schema、水平扩展、适合缺乏严格关系的数据 | Atlas 有免费层;付费集群约每月 ¥400 起 |
| Amazon DynamoDB | 全托管 NoSQL、无服务器、规模化亚毫秒读取 | 按用量付费,免费层 25GB/25 RCU+WCU |
| MSSQL Server | 企业级特性、强 BI 集成、Windows 生态支持 | Developer/Express 免费;Standard 按核心授权 |
2026 年一个合理的默认:多数新关系型负载用 PostgreSQL(性价比最高),已有运维经验或极简 App 用 MySQL,原型和内嵌用 SQLite,只有在数据确实抗拒固定 schema 时才用 MongoDB 这类文档库。超大规模时 DynamoDB 很亮眼,但你要用更陡的学习曲线和最终一致性的惊喜来付这笔"灵活"。
为你会真正写到的查询来设计
数据库设计和查询规划是同一门手艺。把领域建模得完美却对抗每条常见查询的设计,是个生产力黑洞。务实的设计师会从热查询倒推来设计:
- 列出你的应用每个页面都要跑的 3–5 条查询。
- 确保这些查询命中索引列、避开全表扫描。
- 让列顺序和复合索引匹配这些查询的过滤与排序模式。
- 只对热查询反复需要的派生或预计算值做反规范化。
这就是为什么懂数据库管理基础对设计者也重要——"我怎么组织数据"和"我怎么查询维护它"之间的实操重叠,正是多数真实收益所在。同样的道理也适用于日常管理数据库的习惯:备份、迁移、监控,全都在你设计的 schema 之上跑。
索引入门:多数课程一笔带过的设计能力
索引是设计工具,不是事后的性能补丁。如果觉得索引设计是另一回事,那共同主线是:你对列、键和查询模式的决定,决定了哪些索引才说得通。几条对多数设计都适用的默认:
- 给 join 用到的外键建索引——这是单条性价比最高的索引习惯。
- 把 WHERE 和 ORDER BY 一起用的列做进复合索引,等值列放前面。
- 看选择性——只有两个不同取值的列建索引几乎没用,所以给高选择性列建。
- 别过度索引写多表——每条 insert 都得更新每个索引。日志追加型负载上,一个聚簇索引胜过六个虚荣索引。
这些模式如果觉得抽象,它们直接对应数据库索引基础背后的纪律,能把一条拖沓的查询变成常见情况下的表查找。
建立有效设计原则,纠正孱弱的设计习惯
抛开机制,糟糕的数据库设计通常是糟糕的思考换了个皮。同样的认知错误反复出现:给文档/UI建模而不是给领域建模、假设数据永远不会增长、以及为了漂亮范式而牺牲实用查询。最强的纠正是用一小撮耐用原则,而不是记边缘用例。让你的核心实体保持稳定、让派生数据被计算或缓存而不是冗余存储、偏好显式外键而非反规范化副本,并且始终问"这行变成几百万行时会怎样?"——这些原则,在讲到数据库设计原则的资料里都讲透了。它们能在绝大多数失败浮出成凌晨 4 点事故之前就拦住它们。
糟糕设计悄悄逼你掏钱重做的那一刻
有个没人提醒新手注意的模式。一个用得很少的内部工具,带一个逗号分隔城市列或文本型日期,顺顺当当跑了两年。然后某次集成或某个新功能试图去查它,突然那套"还行"的架构变得挪不动了。在这种压力下重做,意味着冻结新功能、一次高风险迁移、以及团队要重新学一遍他们原本信任的 schema。修设计缺陷最便宜的时刻永远是今天,趁数据还小、表还少。本指南不需要你有先见之明——只需要你在开头花上几分钟的纪律,以及愿意对"现在省我一小时、一年后坑我一个月"的捷径说不。
延伸阅读: 和 MLOps 基础。
常见问题
新手设计第一个 schema 最大的错误是什么?
跳过规范化、把关联数据存成可分隔字符串。存"红、蓝、绿"或一串 ID 的列好写却几乎没法好好查询——它没法有效用索引,每个过滤条件都变成扫描。哪怕只是给多对多关系加一张简单的连接表,几周内就能在查询速度和数据完整性上回本。
应该总是用 UUID 做主键而不是自增整数吗?
不总是。自增整数紧凑、快、递增,最适合大多数单区域关系型应用。你会需要 UUID 的场景是:要合并多个来源的数据、要跨机器分布写入、或想避免泄露记录数量。代价是索引体积和插入性能,所以按你的分布需求选,而不是跟风。
什么时候可以反规范化、存重复数据?
只有在你测出确有真实、反复出现的查询值得为它买单之后才反规范化,并且要把重复控制住。常见类型是预计算聚合(比如订单总额)和"join 成为瓶颈"的重读查询。规则是:反规范化字段应当是派生并被刻意刷新的,不能让它们漂移、与事实源矛盾。
只是做个小内部工具,数据库设计还要紧吗?
要,而且正因为小工具会变大,它才格外要紧。你第一周写的 schema,就是第十二个月你天天要查的 schema,在用户依赖它时再去返修才是最贵的部分。现在花一小时做一次轻量规范化、加一个稳定代理键,将来就能省一次痛苦的迁移——哪怕这个工具永远做不大。
新项目怎么在关系型和非关系型(NoSQL 文档库)之间选?
除非有具体理由,否则先选关系型(PostgreSQL 是很强的默认)。关系型数据库强制完整性,擅长灵活查询和 join,这是多数产品需要的。文档库则在数据确实抗拒固定 schema、你从设计上就要水平扩展、或访问模式纯粹按文档键来命中等情况下才说得通。