03 开发者第一次真正使用 PostgreSQL: 建模、对象与访问方式
大多数数据库问题,不是从慢 SQL 开始的,而是从第一次建表就埋下了。字段类型乱、约束缺失、主键选择随意、索引靠感觉加,这些问题早期通常不会立刻爆炸,但业务一增长,代价会迅速放大。所以,开发者真正开始使用 PostgreSQL 时,最先该学的不是花哨语法,而是怎样设计一份不会轻易背刺你的模式。
建模时先回答四个问题
在写 create table 之前,先想清楚下面四件事:
- 这张表记录的业务事实是什么
- 一行数据由什么标识
- 哪些字段必须正确,哪些字段只是补充信息
- 未来最常见的查询路径是什么
如果这四个问题答不出来,后面的主键、约束、索引设计大概率也会漂。
PostgreSQL 建模的几个硬原则
1. 类型表达语义,不要把一切都存成字符串
最常见的低质量设计,就是能偷懒的地方全用 text 或 varchar。这种设计短期方便,长期会带来三类问题:
- 校验边界模糊
- 索引和统计信息质量下降
- 应用和数据库之间出现大量隐式转换
能用 integer、numeric、timestamp、boolean、jsonb 的地方,就尽量别用字符串硬顶。
2. 约束越早加,系统越省心
数据库约束不是“给 DBA 看的装饰品”,而是最便宜的一致性保证。
至少要优先考虑:
primary keynot nullunique- 必要的
foreign key - 有业务意义的
check
如果这些约束全靠应用层代码约定,最终一定有人绕过去。
3. 主键是标识,不是业务说明书
主键最重要的是稳定、短小、低变更风险。不要把一堆业务字段拼成一个庞大的联合主键,除非你真的非常确定这就是最自然的实体标识。
多数业务表中,一个代理主键加必要的唯一约束,往往比复杂的业务主键更稳。
4. 索引围绕查询路径,而不是围绕“重要字段”
索引设计最容易犯的错,是看到某个字段“经常用”就单独给它建索引。真正应该问的是:
- 查询条件是单列还是多列
- 是等值过滤还是范围过滤
- 是否需要排序
- 是否经常只取少数列
索引是访问路径设计,不是字段荣誉勋章。
一张比较稳的业务表示例
create table orders (
id bigserial primary key,
user_id bigint not null,
status text not null check (status in ('pending','paid','closed','cancelled')),
amount numeric(18,2) not null check (amount >= 0),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index idx_orders_user_created_at
on orders (user_id, created_at desc);这份定义不复杂,但有几个关键点:
- 类型明确
- 状态字段有边界
- 时间字段默认生成
- 索引对应典型查询路径
这种表结构比“先都存进去再说”强太多。
jsonb 很有用,但不是万能垃圾桶
PostgreSQL 的 jsonb 很强,但越强越容易被滥用。适合用 jsonb 的场景通常是:
- 字段形态存在弹性,但核心结构仍稳定
- 部分扩展属性不值得频繁改表
- 查询只涉及少量特定键
不适合的场景是:
- 核心业务字段全塞进去
- 后续需要频繁 join、排序、聚合
- 你其实知道结构,只是懒得建列
一句话: jsonb 适合“扩展属性”,不适合“逃避建模”。
应用访问层的几个好习惯
- 永远使用参数化 SQL,不拼接字符串。
- 非必要不要
select *。 - 深分页尽量用 keyset 分页,不要无限
offset。 - 幂等写入优先考虑
insert ... on conflict。 - 更新时间字段时保持统一策略,不要一半靠应用、一半靠触发器。
最容易被忽视的设计问题
- 用软删除代替一切,最后把查询和索引都拖慢。
- 把状态机逻辑完全放进字符串字段,没有清晰约束。
- 只关心写入成功,不关心后续怎么查。
- 每个表都有很多“预留字段”,但没人知道意义。
真正高质量的 PostgreSQL 设计,往往并不复杂。它只是把“数据是什么、如何保证正确、如何被访问”这三件事在一开始想清楚了。