跳到主要内容

其他数据库对象

表保存核心数据,但完整的数据库还包含许多其他对象。它们可以封装查询、生成编号、加速访问、复用逻辑、约束类型,或扩展数据库能力。

选择对象时应先回答:它解决的是数据存储、数据完整性、查询接口、性能,还是过程自动化?不要因为某种对象功能强大,就把本来简单的需求全部放进数据库内部。

本文以通用概念为主,语法示例采用 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 安全;
  • 依赖对象和删除行为;
  • 备份、迁移和版本控制。

不要让应用运行角色随意创建函数、操作符或类型到受信任搜索路径中。

选型原则​

  1. 需要稳定查询接口而不保存结果:视图;
  2. 需要保存昂贵查询结果且允许刷新延迟:物化视图;
  3. 需要并发生成数字:标识列或序列;
  4. 需要加速已知查询:合适类型的索引;
  5. 需要复用确定性计算:函数;
  6. 需要显式调用的数据库批处理:过程;
  7. 需要所有写入入口自动执行:谨慎使用触发器;
  8. 需要复用真正相同的标量规则:域;
  9. 需要专用数据结构:评估自定义类型;
  10. 需要安装成套能力:经过审查的扩展。

其他数据库对象的价值在于把明确的职责放到最适合的机制中。每新增一个对象,也同时新增了权限、依赖、迁移和运维责任,应把这些成本与功能收益一起评估。