其他数据库对象
表保存核心数据,但完整的数据库还包含许多其他对象。它们可以封装查询、生成编号、加速访问、复用逻辑、约束类型,或扩展数据库能力。
选择对象时应先回答:它解决的是数据存储、数据完整性、查询接口、性能,还是过程自动化?不要因为某种对象功能强大,就把本来简单的需求全部放进数据库内部。
本文以通用概念为主,语法示例采用 PostgreSQL。
对象概览
| 对象 | 主要用途 |
|---|---|
| 视图 | 保存查询接口,不保存普通查询结果 |
| 物化视图 | 保存查询结果,通过刷新更新 |
| 序列 | 并发生成数值 |
| 索引 | 加速查询,部分索引还能保证唯一性 |
| 函数 | 接收参数并返回值或结果集 |
| 过程 | 执行调用式过程逻辑 |
| 触发器 | 在数据或结构事件发生时自动执行逻辑 |
| 类型与域 | 定义可复用的数据结构或受约束标量 |
| 扩展 | 以可管理的软件包安装一组数据库能力 |
| 排序规则 | 定义文本比较和排序语义 |
| 转换与语言 | 支持编码转换或服务端编程语言 |
视图
视图保存一条查询定义,对外表现得像表:
CREATE VIEW active_users AS
SELECT user_id, display_name, created_at
FROM account.users
WHERE disabled_at IS NULL;
查询视图:
SELECT *
FROM active_users
ORDER BY created_at DESC;
视图适合:
- 隐藏复杂连接和过滤;
- 向调用方提供稳定列接口;
- 只暴露允许读取的列;
- 兼容表结构迁移。
普通视图通常不保存结果,每次查询仍会执行底层逻辑。它也不是天然的安全边界:视图所有者、调用者权限、security_barrier、调用函数和 PostgreSQL 版本都会影响安全行为。
可更新视图
简单的单表视图可能自动支持插入、更新和删除。复杂连接、聚合或集合操作通常不可直接更新,可以使用 INSTEAD OF 触发器等机制定义行为。
需要限制写入后仍满足视图条件时,可使用:
CREATE VIEW active_users AS
SELECT user_id, display_name, disabled_at
FROM account.users
WHERE disabled_at IS NULL
WITH LOCAL CHECK OPTION;
物化视图
物化视图保存查询结果:
CREATE MATERIALIZED VIEW daily_sales AS
SELECT
ordered_at::date AS sales_date,
sum(total_amount) AS total_amount
FROM sales.orders
GROUP BY ordered_at::date;
刷新结果:
REFRESH MATERIALIZED VIEW daily_sales;
它适合昂贵、允许一定延迟的报表查询。与普通视图不同,底层数据变化后结果不会自动更新。
PostgreSQL 支持在满足条件时并发刷新:
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_sales;
通常需要适当的唯一索引。并发刷新降低读取阻塞,但消耗更多资源,也不是增量刷新。刷新频率、失败恢复和数据新鲜度必须由任务系统明确管理。
序列
序列是并发安全的数值生成器:
CREATE SEQUENCE invoice_number_seq
START WITH 1000
INCREMENT BY 1;
取下一个值:
SELECT nextval('invoice_number_seq');
查看当前会话最近取到的值:
SELECT currval('invoice_number_seq');
currval 只有在当前会话已经成功调用相应 nextval 后才有效。
序列值通常不会因事务回滚而归还,因此允许空号。需要自动数字主键时,新表一般优先使用标识列;需要多个对象共享生成器或特殊控制时,显式序列仍然有用。
索引
索引提供额外的数据访问路径:
CREATE INDEX idx_orders_customer_time
ON sales.orders (customer_id, ordered_at DESC);
常见 PostgreSQL 索引类型包括:
- B-tree:等值、范围、排序,最常用;
- Hash:等值比较;
- GiST:范围、几何、近邻等可扩展场景;
- SP-GiST:适合某些分区空间结构;
- GIN:数组、全文搜索、
jsonb等多值结构; - BRIN:物理顺序与值相关的超大表范围摘要。
表达式索引
CREATE INDEX idx_users_lower_email
ON account.users (LOWER(email));
查询表达式要与索引表达式兼容,优化器才可能使用。
部分索引
CREATE INDEX idx_orders_unpaid
ON sales.orders (due_at)
WHERE paid_at IS NULL;
它只保存满足条件的行,适合热点子集。查询条件必须能推出索引谓词。
索引会占用空间并增加插入、更新、删除和维护成本。应根据实际查询计划创建,而不是为每一列都建索引。
函数
函数接收参数并返回标量、复合值或结果集:
CREATE FUNCTION sales.net_amount(
gross NUMERIC,
discount NUMERIC
)
RETURNS NUMERIC
LANGUAGE SQL
IMMUTABLE
RETURN gross - discount;
调用:
SELECT sales.net_amount(100.00, 12.50);
函数设计要明确:
- 参数与返回类型;
- 遇到
NULL的行为; - 波动性:
IMMUTABLE、STABLE或VOLATILE; - 调用者权限还是定义者权限;
- 并行安全性和成本估计;
search_path与动态 SQL 安全。
不要为了让函数可用于索引而错误标记为 IMMUTABLE。它必须对相同输入永远返回相同结果,并且不依赖表、时间、配置等可变状态。
过程
过程通过 CALL 执行:
CREATE PROCEDURE maintenance.archive_orders(cutoff DATE)
LANGUAGE SQL
AS $$
INSERT INTO archive.orders
SELECT * FROM sales.orders
WHERE ordered_at < cutoff;
$$;
CALL maintenance.archive_orders(DATE '2025-01-01');
函数主要用于表达式中返回结果,过程更偏向执行操作。事务控制能力取决于调用上下文和过程语言。复杂批处理还需要幂等、批次、锁、日志与失败恢复,不能只靠一个大过程解决。
触发器
触发器在指定事件发生时自动调用触发器函数:
CREATE TRIGGER orders_audit
AFTER INSERT OR UPDATE OR DELETE
ON sales.orders
FOR EACH ROW
EXECUTE FUNCTION audit.log_order_change();
常见维度包括:
BEFORE、AFTER、INSTEAD OF;INSERT、UPDATE、DELETE、TRUNCATE;- 每行触发或每语句触发;
- 条件触发与过渡表。
触发器适合所有写入入口都必须执行的审计、派生维护或兼容逻辑,但会引入隐式副作用。应防止递归、明确触发顺序、控制事务耗时,并让应用能观察触发器错误。
跨系统通知不应直接在触发器中进行不可回滚的网络调用。更可靠的方式通常是在同一事务写入 outbox 表,再由独立进程投递。
自定义类型
PostgreSQL 支持多种用户定义类型。
枚举类型
CREATE TYPE order_status AS ENUM (
'pending',
'paid',
'cancelled'
);
枚举能限制取值并提供清晰类型,但增删、重排业务状态的迁移灵活性有限。状态经常变化或需要附加属性时,引用表可能更合适。
复合类型
CREATE TYPE address AS (
city TEXT,
street TEXT,
postal_code TEXT
);
复合类型可用于函数参数和列,但字段演进、驱动映射与查询便利性需要评估。普通关系通常仍适合拆成列或独立表。
范围与多范围类型
PostgreSQL 内置日期、时间和数值范围类型,也支持自定义范围。它们适合预约区间、有效期等需求,并能配合 GiST 和排斥约束处理重叠。
域
域是在基础类型上添加可复用约束的类型:
CREATE DOMAIN positive_amount AS NUMERIC(12, 2)
CHECK (VALUE >= 0);
CREATE TABLE payments (
amount positive_amount NOT NULL
);
域适合在整个数据库中真正具有相同语义和规则的标量。不要仅因多个列碰巧都是非负数就强行复用:库存数量与支付金额未来可能有不同范围和演进路径。
域约束与列 NOT NULL、空值处理、类型转换之间有细节差异,应在目标版本测试。
扩展
扩展把函数、类型、操作符、表等对象作为一个可安装和升级的单元:
CREATE EXTENSION pg_trgm;
查看已安装扩展:
SELECT extname, extversion
FROM pg_extension
ORDER BY extname;
扩展安装前要确认:
- 来源和代码可信;
- 服务端文件已安装且版本兼容;
- 是否需要超级用户或托管平台授权;
- 升级和回滚路径;
- 备份恢复时目标环境是否具备相同扩展;
- 扩展对象应安装在哪个模式。
扩展不是简单的 SQL 复制包,它可能包含本地代码并运行在数据库进程中。
排序规则
排序规则(collation)决定文本排序和比较行为:
SELECT name
FROM products
ORDER BY name COLLATE "C";
语言环境、操作系统库或 ICU 版本变化可能影响排序结果和相关索引。需要稳定跨环境排序时,应明确使用的排序规则并规划版本升级后的索引维护。
排序规则不会自动提供完整的自然语言搜索。全文搜索、模糊匹配和规范化需要专门设计。
对象所有权与模式
所有这些对象都属于某个所有者,并通常位于某个模式。创建后应统一处理:
- 所有者角色;
- 运行角色权限;
PUBLIC默认权限;search_path安全;- 依赖对象和删除行为;
- 备份、迁移和版本控制。
不要让应用运行角色随意创建函数、操作符或类型到受信任搜索路径中。
选型原则
- 需要稳定查询接口而不保存结果:视图;
- 需要保存昂贵查询结果且允许刷新延迟:物化视图;
- 需要并发生成数字:标识列或序列;
- 需要加速已知查询:合适类型的索引;
- 需要复用确定性计算:函数;
- 需要显式调用的数据库批处理:过程;
- 需要所有写入入口自动执行:谨慎使用触发器;
- 需要复用真正相同的标量规则:域;
- 需要专用数据结构:评估自定义类型;
- 需要安装成套能力:经过审查的扩展。
其他数据库对象的价值在于把明确的职责放到最适合的机制中。每新增一个对象,也同时新增了权限、依赖、迁移和运维责任,应把这些成本与功能收益一起评估。