产品什么时候需要数据库,表和字段应该怎么设计?
从需要保存的业务事实和查询出发,设计关系、约束和迁移,并用测试数据验证。
本页目录
直接回答
当信息需要跨刷新保留、被多个人共享、按条件查询或可靠地一起更新时,应明确持久化方案。把文章内容从应用代码移到数据库,可以让内容与实现分离;用户与活动记录则体现数据归属关系。并非每个原型都必须接远程数据库,静态内容、浏览器本地状态和多用户业务各有不同需要。设计表时先描述业务事实,再选字段类型、主外键与约束,不要让 AI 根据页面数量机械建表。
适用场景
- 需要保存用户历史、文章、订单或生成记录
- 数据都塞在一个 JSON 中,关系与更新开始混乱
- 准备更换数据库或修改已有结构
一步一步做
列出需要保存的事实与用途
从 SPEC 写出实体,例如用户、文章、游戏记录;再写需要回答的问题,例如“这个用户最近完成了哪些游戏”。只保留为功能和运营确实需要的数据,区分原始输入、生成结果、状态及创建时间。
确定归属关系
每条记录应有稳定标识。一个用户对应多条游戏记录,可由记录中的 user_id 关联用户;学生与选修科目的多对多关系可通过中间表表达。确认删除或停用用户后相关记录怎么办,不要让默认行为替你决定。
用约束表达业务规则
字段类型控制数据形态,NOT NULL、UNIQUE、外键和 CHECK 表达不同限制。官方文档核实:CHECK 对 NULL 的结果不会等同于拒绝空值,必须必填的字段还需要 NOT NULL。整理建议:金额精度、状态转换与重复提交也要按真实业务定义。
根据真实查询考虑索引
先列筛选、排序和关联方式,再为经常使用且需要优化的路径评估索引。索引不是越多越好,也不是把所有字段都勾选就完成优化。数据量小时先保证查询正确、只返回需要的列,并限制列表返回数量。
通过迁移和样例验证结构
用可追踪的 migration 记录结构变化,先在开发或测试数据库验证创建、查询、更新和拒绝无效数据。涉及已有生产数据时先检查备份与恢复,评估旧代码兼容性。回退代码不一定能撤销已执行的数据修改。
在隔离库完整练一次增查改删
先确认连接的是练习库,再建临时表、插入两行、按稳定排序查询。UPDATE 或 DELETE 前用相同条件 SELECT,预计一行就核对实际影响一行;用 RETURNING 看改了什么,练习以 ROLLBACK 结束。应用输入使用参数化查询或 ORM 参数,不能拼进 SQL。生产变更还需要影响范围、备份、并发与权限判断,不能因为练习成功就直接执行。
把统计口径翻成可检查查询
排行榜按“每个用户最高分”还是“每一局成绩”展示,结果不同。先写口径、时间范围、同分顺序和公开字段,再用几条有重复用户与同分的样例手算结果,与 GROUP BY、MAX、ORDER BY 查询对比。LIMIT 不定义顺序,稳定分页需明确排序及唯一的后备排序字段;外键关联和索引都不能替代用户权限。
让结构变更兼容已有数据
PostgreSQL 中 database 内还有 schema 命名空间,例如 public;它与“数据库结构设计”这个泛称不同。字段演进先查看真实表与旧记录,优先新增兼容字段,按可解释规则回填,再校验空值、重复值和关系,最后加严格约束并切换代码。金额采用明确货币与精度,跨时区事件保留时区含义。回退应分别说明旧代码能否读新结构、已回填数据如何恢复;迁移文件有编号并不等于可无损逆转。
案例拆解
假设练习:用户、文章和游戏记录
假设 blog_posts 存内容,users 存用户,game_records 通过 user_id 记录归属。一次游戏结束后应能新增记录,个人历史只显示自己的记录,排行榜按定义的公开字段展示。整理建议:用两个测试用户验证归属,再故意提交不存在的用户 ID 和不允许的状态,检查数据库与服务端是否拒绝,而不是只看新增一行成功。
补充假设示例:A 的两局成绩为 70、90,B 为 90。逐局榜有三条,用户最高分榜只有两条;按 user_id 升序处理同分才有稳定结果。另一个用户表要新增必填昵称时,先加可空字段、回填旧用户、核对空值为零,再加 NOT NULL,避免旧账号突然无法使用。
行动检查
可复制模板
从 SPEC 设计数据库
工作模板
请先读当前 SPEC、数据库 schema 和查询代码,不要直接执行迁移。
列出:实体、字段含义、类型、必填与唯一约束、关系、数据归属、保留与删除规则。
为[核心用户流程]给出需要的查询与写入;用正常、空值、重复、无效关联样例检查设计。
沿用已有数据库和 ORM;新增字段必须说明用途。最后提出最小迁移方案、兼容风险和验证步骤。
理解约束与查询的 SQL 小练习
整理改编教学示例,仅用于隔离练习库,未执行验证。正式项目先检查已有 schema 与迁移工具。
CREATE TABLE practice_products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
price NUMERIC(10, 2) NOT NULL CHECK (price > 0)
);
INSERT INTO practice_products (name, price) VALUES ('练习产品', 9.90);
SELECT id, name, price FROM practice_products ORDER BY id LIMIT 10;
观察:主键识别记录,UNIQUE 阻止重名,NOT NULL 拒绝空价格,CHECK 拒绝非正价格。查询写明字段和数量,先理解影响范围,再学习 UPDATE 与 DELETE。
增查改删事务练习
教学 SQL,仅供隔离 PostgreSQL 练习库;本站未在真实库执行。确认客户端使用同一连接按顺序执行;报错时先结束当前事务。
BEGIN;
CREATE TEMP TABLE practice_profiles (
id INTEGER PRIMARY KEY,
display_name TEXT NOT NULL
) ON COMMIT DROP;
INSERT INTO practice_profiles VALUES (1, '测试甲'), (2, '测试乙');
SELECT id, display_name FROM practice_profiles ORDER BY id LIMIT 10;
-- 先查:预计只影响 id=1
SELECT id, display_name FROM practice_profiles WHERE id = 1;
UPDATE practice_profiles SET display_name = '测试甲改名'
WHERE id = 1 RETURNING id, display_name;
-- 先查:预计只删除 id=2
SELECT id, display_name FROM practice_profiles WHERE id = 2;
DELETE FROM practice_profiles WHERE id = 2 RETURNING id;
SELECT id, display_name FROM practice_profiles ORDER BY id;
ROLLBACK;
填写:连接库[];更新影响行数[];删除影响行数[];最后查询结果[];事务是否回滚[]。回滚后临时表创建也被撤销,不把本练习改成真实业务表操作。
关系、统计与迁移核对卡
实体/稳定ID/归属:
一对多:[父表、子表、外键、删除规则]
多对多:[中间表及两列联合唯一或主键,防止重复关系]
查询口径:[谁的哪些记录、时间窗口、聚合方式、同分顺序、返回列]
手算样例:[至少覆盖同用户多条与并列]
迁移目标/当前schema命名空间:
新增兼容字段→旧记录回填规则→空值/重复/关系核验→约束→代码切换:
回退:代码兼容[];数据恢复[];备份恢复验证[]
只列计划与查询证据,不直接对生产执行修改。
易错点
- 按每个页面建一张表:页面展示方式与业务实体不是一回事。
- 复制练习中的删除全部数据示例到真实库:练习应使用隔离数据,先看影响范围。
- 认为有外键就有用户权限:外键维护关系,访问权限仍需业务层控制。
相关指南
- 页面能登录就够了吗,怎样保证用户只能看到自己的数据?
- AI 生成图片链接会过期,怎样保存文件并控制谁能看?
- 怎样让网站每天自动做一件事,而且不会重复执行或假报成功?
- 怎样把在线工具做出的原型,迁到本地和自己的服务?
官方参考
-
PostgreSQL:Constraints — 说明 CHECK、非空、唯一、主键及外键的不同职责,尤其是 NULL 与 CHECK 的关系 资料核查记录日期:2026-10-18。
-
PostgreSQL:Transactions — 说明BEGIN、COMMIT与ROLLBACK的事务含义;练习SQL未执行 资料核查记录日期:2026-10-18。
-
PostgreSQL:Schemas — 说明database内schema命名空间与表的关系 资料核查记录日期:2026-10-18。