PostgreSQL 数据类型与表设计
PostgreSQL 系统讲解第二篇:类型系统是 PG 最大的优势之一——本篇过一遍"值得用的类型"和表设计时的关键选择。
值得优先掌握的类型
| 类型 | 用途 | 备忘 |
|---|---|---|
numeric(p,s) |
精确小数 | 金额一律用它;float/double 有浮点误差 |
bigint + IDENTITY |
主键 | 见下节自增方案 |
timestamptz |
带时区时间戳 | 存 UTC 内部表示,显示按会话时区——优先用它而不是 timestamp |
uuid |
原生 UUID 类型 | 分布式 id;默认无内置生成函数,用 gen_random_uuid()(pgcrypto/13+ 内置) |
text |
不限长字符串 | PG 里 varchar(n) 与 text 性能相同,长度约束用 CHECK 就行——"都建 text 需要时再限"是常见流派 |
text[] |
数组 | 标签、多值字段一行搞定 |
jsonb |
二进制 JSON | 可索引、可查询;json 只存原文(慢),默认用 jsonb |
bytea |
二进制大对象 | 大文件仍建议对象存储 + URL |
JSONB:PG 的招牌
CREATE TABLE events (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
data jsonb NOT NULL
);
INSERT INTO events (data) VALUES ('{"type": "click", "page": "/home", "meta": {"src": "app"}}');
-- 查询操作符
SELECT data->>'type' FROM events; -- ->> 取文本;-> 取 jsonb
SELECT data->'meta'->>'src' FROM events;
SELECT * FROM events WHERE data @> '{"type": "click"}'; -- @> 包含,可走 GIN 索引
SELECT * FROM events WHERE data ? 'page'; -- 是否存在顶层 key
- jsonb vs json:jsonb 解析后存储(支持索引、去重键、次序不定),json 存原文(只能整体读)。几乎总是选 jsonb
- GIN 索引让 jsonb 查询起飞:
CREATE INDEX ON events USING GIN (data); - 定位:schema 频繁变化的属性数据用 jsonb;核心业务字段老老实实建列(可约束、可统计、类型安全)
数组类型
SELECT * FROM users WHERE 'python' = ANY(tags); -- 包含元素
SELECT * FROM users WHERE tags @> ARRAY['python', 'go']; -- 同时包含
UPDATE users SET tags = tags || '{rust}' WHERE id = 1; -- 追加元素
CREATE INDEX ON users USING GIN (tags); -- 数组查询走 GIN
我的记忆钩子:数组解决"多值属性",jsonb 解决"半结构化"——两者一起上 GIN 索引,是 PG 对 MySQL 最大的一波"原生优势"。
自增三方案:SERIAL vs IDENTITY vs UUID
id serial PRIMARY KEY -- 老写法:int + 默认序列
id integer GENERATED BY DEFAULT AS IDENTITY -- SQL 标准,推荐
id uuid DEFAULT gen_random_uuid() -- 分布式场景
- IDENTITY 优于 SERIAL:权限更严格(防止手滑覆盖序列)、可
GENERATED ALWAYS锁死手工插值 - 序列对象独立存在(
\ds查看),currval/nextval可手动取号 - UUID vs 自增:分布式/无冲突插入选 UUID;单库自增更省空间且索引更友好——别无脑 UUID
DDL 事务性:改表结构不用怕
BEGIN;
ALTER TABLE users ADD COLUMN nickname varchar(64);
-- 看到列名不合适?直接回滚,不用写一堆恢复脚本
ROLLBACK;
MySQL 转过来的第一惊喜:DDL 也是事务的一部分。配合先在测试库演练的习惯,改表结构的心理负担小很多。
⚠️ 但注意:加列是快的,改列类型、加无默认值的唯一约束等操作会锁表重写——大表 DDL 前先查该版本的锁行为(这属于"动手核对"点)。
schema:一个库里的命名空间
CREATE SCHEMA pay; -- 业务分区内聚:public(通用) / shop / log
CREATE TABLE shop.orders (...);
SELECT * FROM shop.orders; -- 跨 schema 用 点 访问
SET search_path TO shop, public; -- 搜索路径决定不带前缀时先找哪个
用途:一个库服务多个模块/多租户隔离(每租户一个 schema)、扩展对象隔离(pgvector 的对象在 extension 的 schema 里)。
⬅️ 01-PostgreSQL 基础与 MySQL 对照 🏠 00-数据库 ➡️ 03-PostgreSQL 查询进阶
💬 评论