CREATE RULE 规则
PostgreSQL 的规则系统(rule system,更准确地说是查询重写规则系统)在查询被规划(planning)之前改写查询树,把规则纳入考虑。CREATE RULE 就是定义这种重写规则:当某张表或视图上发生 SELECT/INSERT/UPDATE/DELETE 时,附加执行另一条命令,或用另一条命令取代原命令。规则不是触发器——它在命令开始执行之前就完成了改写。
前置知识
阅读本节前,建议先了解:CREATE / DROP VIEW、创建表(CREATE TABLE)
基础概念
规则是什么
一条查询的生命周期(带规则):
SQL 文本
│
▼
解析器(Parser) ──► 查询树(query tree)
│
▼
重写器(Rewriter) ──► 查询树 + 规则 → 改写后的查询树列表 ← 规则在这里起作用
│
▼
规划器(Planner) ──► 执行计划
│
▼
执行器(Executor) ──► 结果三个关键认识:
| 认识 | 说明 |
|---|---|
| 规则是命令宏 | 本质是命令变换机制,把一条命令改写成另一条(或几条)命令 |
| 改写发生在执行前 | 不是每行触发,而是先把查询树改掉,再交给规划器 |
| 规则不修改原查询树本身 | 对于 INSERT/UPDATE/DELETE 规则,它是生成零个或多个新查询树,并可丢弃原查询树 |
规则 vs 触发器
| 维度 | 规则(Rule) | 触发器(Trigger) |
|---|---|---|
| 触发时机 | 命令执行前,改写查询 | 语句执行中,按行或按语句回调 |
| 粒度 | 一次命令改写 | FOR EACH ROW / FOR EACH STATEMENT |
| 实现 | 查询树变换 | PL/pgSQL 等函数过程 |
| 视图更新 | _RETURN 规则即视图本质 | INSTEAD OF 触发器 |
| 语义复杂度 | 较高,有意外行为 | 较低,易于理解 |
官方建议
对于很多可以用规则完成的任务,触发器通常是更好的选择。触发器写法稍复杂,但语义简单得多。规则在原查询包含易变函数(volatile function)时容易产生意外结果。
规则的四种事件
| 事件 | 可用的伪关系 | 常见用途 |
|---|---|---|
SELECT | 无(仅限视图,规则名必须是 _RETURN) | 定义视图内容 |
INSERT | NEW | 把插入改写到其他表 |
UPDATE | NEW、OLD | 记录变更日志、改写更新目标 |
DELETE | OLD | 级联删除保护、改写删除目标 |
语法与用法
CREATE [ OR REPLACE ] RULE name AS ON event
TO table_name [ WHERE condition ]
DO [ ALSO | INSTEAD ] { NOTHING | command | ( command ; command ... ) }
-- event 取值:
-- SELECT | INSERT | UPDATE | DELETE
-- 要点:
-- ALSO :在原命令之外,附加执行规则动作(默认)
-- INSTEAD :用规则动作取代原命令
-- NOTHING :什么都不做(常与 INSTEAD 连用,用于"屏蔽"某操作)
-- condition:规则限定条件,只能引用 NEW / OLD,不能含聚合最简示例——给 shoelace_data 的库存变更记一条日志:
CREATE TABLE shoelace_data (
sl_name text PRIMARY KEY,
sl_avail integer,
sl_color text
);
CREATE TABLE shoelace_log (
sl_name text,
sl_avail integer,
log_who text,
log_when timestamptz
);
CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data
WHERE NEW.sl_avail <> OLD.sl_avail
DO ALSO INSERT INTO shoelace_log
VALUES (NEW.sl_name, NEW.sl_avail, current_user, now());执行一次更新:
UPDATE shoelace_data SET sl_avail = 6 WHERE sl_name = 'sl7';查看日志表(规则自动补写了记录):
SELECT * FROM shoelace_log; sl_name | sl_avail | log_who | log_when
---------+----------+---------+-------------------------------
sl7 | 6 | appuser | 2026-09-28 10:12:41.882117+08
(1 row)查看表上定义的规则:
SELECT rulename, ev_type, is_instead, ev_enabled
FROM pg_rewrite
WHERE ev_class = 'shoelace_data'::regclass; rulename | ev_type | is_instead | ev_enabled
--------------+---------+------------+------------
log_shoelace | 2 | f | O
(1 row)详细说明
规则如何被应用
重写器在规划之前工作。它拿到查询树后,检查「结果关系 + 命令类型」是否与某条 CREATE RULE 声明的对象与事件匹配。对 INSERT/UPDATE/DELETE 这类更新规则,重写器会新建一个查询树列表(初始为空),然后按规则生成零个到多个新查询树:
一条规则(一个动作)的三种情形:
① 无条件 + ALSO / INSTEAD
→ 规则动作的查询树,附加原查询的限定条件
② 有条件 + ALSO
→ 规则动作的查询树,附加"规则条件 + 原查询条件"
→ 再加上未改动的原查询树(共 2 个)
③ 有条件 + INSTEAD
→ 规则动作的查询树,附加"规则条件 + 原查询条件"
→ 再加上"原查询树 + 规则条件的否定"(共 2 个)一个容易忽视但很重要的顺序约定:
| 事件 | 原命令与规则动作的先后 |
|---|---|
ON INSERT | 原命令(若未被 INSTEAD 抑制)先执行,规则动作后执行,这样动作能看到刚插入的行 |
ON UPDATE / ON DELETE | 规则动作先执行,原命令后执行,这样动作能看到「将要被更新/删除」的行 |
如果顺序反过来,日志类规则就会失效——UPDATE 先执行的话,0 <> sl_avail 这样的条件将永远不成立:
-- 假设规则是"先 UPDATE 再记日志",则下面这条日志永远写不出来
-- 因为行已被改成 0,OLD 与 NEW 相等
UPDATE shoelace_data SET sl_avail = 0 WHERE sl_color = 'black';生成的规则动作查询树会被再次丢回重写系统,可能触发更多规则。因此规则的动作必须与规则本身「命令类型不同」或「结果关系不同」,否则会陷入无限递归:
-- 循环示例:两条 _RETURN 规则互相引用(PostgreSQL 会接受定义,但查询时报错)
CREATE RULE "_RETURN" AS ON SELECT TO t1 DO INSTEAD SELECT * FROM t2;
CREATE RULE "_RETURN" AS ON SELECT TO t2 DO INSTEAD SELECT * FROM t1;
SELECT * FROM t1; -- ERROR: infinite recursion detected in rules for relation "t1"伪关系 NEW 与 OLD
在规则的条件与动作中,可以用特殊表名 NEW 和 OLD 引用相关行:
| 伪关系 | 可用于 | 含义 |
|---|---|---|
NEW | ON INSERT、ON UPDATE | 正在被插入/更新后的新行 |
OLD | ON UPDATE、ON DELETE | 已存在、将被更新/删除的旧行 |
-- ON UPDATE 同时用到 NEW 与 OLD 做差异判断
CREATE RULE track_price_change AS ON UPDATE TO products
WHERE NEW.price <> OLD.price
DO ALSO INSERT INTO price_history (product_id, old_price, new_price, changed_at)
VALUES (OLD.product_id, OLD.price, NEW.price, now());-- ON DELETE 只能用 OLD
CREATE RULE archive_deleted AS ON DELETE TO orders
DO ALSO INSERT INTO orders_archive
SELECT OLD.*, now(), current_user;-- 用错伪关系会直接报错:
CREATE RULE bad_rule AS ON INSERT TO t DO ALSO INSERT INTO log VALUES (OLD.id);
-- ERROR: invalid reference to FROM-clause entry for table "old"ALSO 与 INSTEAD 的区别
这是规则最容易混淆的一点:
DO ALSO :原命令照常执行,规则动作"额外"执行 → 1 + N
DO INSTEAD :规则动作"取代"原命令,原命令不再执行 → 只有规则动作对比示例——记录删除并保留原行,与记录删除并阻止原行:
-- ALSO:真的删了,同时留一条归档
CREATE RULE del_also AS ON DELETE TO orders
DO ALSO INSERT INTO orders_archive SELECT OLD.*, now();
-- INSTEAD:不删原行,只做别的操作
CREATE RULE del_instead AS ON DELETE TO orders
DO INSTEAD INSERT INTO delete_requests (order_id, requested_at)
VALUES (OLD.order_id, now());不显式写 ALSO 或 INSTEAD 时,默认是 ALSO。验证效果——用 INSTEAD 后,orders 的行数不变:
SELECT count(*) FROM orders;
DELETE FROM orders WHERE order_id = 1001;
SELECT count(*) FROM orders; -- 数量不变,因为删除被 INSTEAD 规则取代了 count
-------
84210
(1 row)
DELETE 0
count
-------
84210
(1 row)注意 psql 返回的是 DELETE 0——因为原 DELETE 已被取代,规则动作插入到的是另一张表。
规则条件的限制
规则的 WHERE 条件是一个返回 boolean 的 SQL 表达式,但有两条硬性限制:
| 限制 | 说明 |
|---|---|
只能引用 NEW / OLD | 不能引用其他任何表 |
| 不能包含聚合函数 | 不能写 sum(...)、count(...) 等 |
-- 合法:只引用 NEW / OLD,无条件聚合
CREATE RULE r_ok AS ON UPDATE TO t
WHERE NEW.status <> OLD.status AND NEW.amount > 0
DO ALSO INSERT INTO status_log VALUES (OLD.id, OLD.status, NEW.status, now());-- 非法:引用了另一张表
CREATE RULE r_bad AS ON UPDATE TO t
WHERE NEW.id IN (SELECT id FROM blacklist);
-- ERROR: invalid reference to FROM-clause entry for table "new"
-- 非法:含聚合
CREATE RULE r_bad2 AS ON UPDATE TO t
WHERE count(*) > 1;
-- ERROR: aggregate functions are not allowed in WHERE此外,规则条件引用的伪关系具有特殊替代规则:NEW 会先尝试在原查询的目标列表中查找对应项,找不到时在 UPDATE 中等同于 OLD,在 INSERT 中则被替换为 NULL。这解释了为什么「未修改的列」不会误触发日志规则:
-- 原查询没有修改 sl_avail,NEW.sl_avail 被替换成 shoelace_data.sl_avail
-- 于是 NEW.sl_avail <> OLD.sl_avail 恒为假,不会写日志
UPDATE shoelace_data SET sl_color = 'green' WHERE sl_name = 'sl7';规则动作支持的命令
规则动作里可以出现的命令是有限集合:
| 命令 | 说明 |
|---|---|
SELECT | 仅 _RETURN 视图规则 |
INSERT | 常用于改写插入目标 |
UPDATE | 常用于改写更新目标 |
DELETE | 常用于改写删除目标 |
NOTIFY | 发送通知 |
NOTHING | 空动作 |
一条规则可以有多个动作,用括号加 ; 分隔:
CREATE RULE multi_action AS ON UPDATE TO orders
DO ALSO (
INSERT INTO orders_audit (order_id, changed_at) VALUES (NEW.order_id, now());
INSERT INTO orders_status_log (order_id, status) VALUES (NEW.order_id, NEW.status);
NOTIFY orders_changed
);NOTIFY 的实现限制
目前如果规则动作包含 NOTIFY,NOTIFY 会无条件执行——即使没有任何行匹配规则条件也会发送。这是当前的实现限制:
CREATE RULE notify_me AS ON UPDATE TO mytable DO ALSO NOTIFY mytable;
-- 即使 id = 42 的行不存在,也会发出一次 NOTIFY
UPDATE mytable SET name = 'foo' WHERE id = 42;_RETURN 规则:视图的本质
在 PostgreSQL 里,视图就是一条名为 _RETURN 的 ON SELECT 规则。这类规则要求极严:
| 要求 | 说明 |
|---|---|
名称必须是 "_RETURN" | 固定名字 |
必须是无条件的 INSTEAD | 不能带 WHERE |
动作必须是单条 SELECT | 只允许一个 SELECT 命令 |
| 只能挂在视图上 | ON SELECT 规则不能挂在普通表上 |
视图本身是一张「无存储的哑表」,其可见内容完全由 _RETURN 规则定义:
CREATE VIEW v_active_orders AS
SELECT order_id, buyer_id, amount
FROM orders
WHERE status = 'active';
-- 上面等价于在视图上定义了一条 _RETURN 规则
SELECT rulename, ev_type, is_instead
FROM pg_rewrite
WHERE ev_class = 'v_active_orders'::regclass; rulename | ev_type | is_instead
----------+---------+------------
_RETURN | 1 | t
(1 row)可以用 CREATE OR REPLACE RULE "_RETURN" 来重定义视图,但更好的写法是 CREATE OR REPLACE VIEW:
-- 不推荐:用规则重定义视图(虽然可行)
CREATE OR REPLACE RULE "_RETURN" AS ON SELECT TO v_active_orders
DO INSTEAD SELECT order_id, buyer_id, amount FROM orders WHERE status = 'paid';
-- 推荐:用视图语法
CREATE OR REPLACE VIEW v_active_orders AS
SELECT order_id, buyer_id, amount FROM orders WHERE status = 'paid';甚至可以直接用 _RETURN 规则手工「造」一个视图:
CREATE TABLE t_dummy (x int);
-- 先建一张空表,再加 _RETURN 规则,它就"变成"了视图
CREATE RULE "_RETURN" AS ON SELECT TO t_dummy
DO INSTEAD SELECT 1 AS x, 2 AS y;-- 注意:这个技巧会让 t_dummy 无法再作为普通表写入
INSERT INTO t_dummy VALUES (1);
-- ERROR: cannot insert into view "t_dummy"规则的系统目录:pg_rewrite
所有规则都存放在 pg_rewrite 系统目录中。常用查询字段如下:
| 字段 | 含义 |
|---|---|
rulename | 规则名 |
ev_class | 规则所属的表/视图 OID |
ev_type | 事件类型(1=SELECT、2=UPDATE、3=INSERT、4=DELETE) |
is_instead | 是否为 INSTEAD 规则 |
ev_enabled | 启用状态(O=origin 默认、D=disabled、A=always、R=replica) |
ev_qual | 规则条件(内部形式) |
ev_action | 规则动作(内部形式) |
查看某张表的所有规则:
SELECT r.rulename,
r.ev_type,
r.is_instead,
r.ev_enabled,
pg_get_ruledef(r.oid) AS definition
FROM pg_rewrite r
WHERE r.ev_class = 'orders'::regclass
ORDER BY r.rulename;使用更友好的 pg_rules 视图:
SELECT schemaname, tablename, rulename, definition
FROM pg_rules
WHERE tablename = 'orders'; schemaname | tablename | rulename | definition
------------+-----------+-----------------+---------------------------------------------------------------
public | orders | archive_deleted | CREATE RULE archive_deleted AS ON DELETE TO public.orders (...)
public | orders | del_also | CREATE RULE del_also AS ON DELETE TO public.orders DO ALSO ...
(2 rows)用 pg_get_ruledef() 还原规则的可读定义:
SELECT pg_get_ruledef(oid)
FROM pg_rewrite
WHERE rulename = 'log_shoelace'; pg_get_ruledef
-------------------------------------------------------------------------------------------
CREATE RULE log_shoelace AS ON UPDATE TO public.shoelace_data +
WHERE new.sl_avail <> old.sl_avail DO ALSO INSERT INTO public.shoelace_log (...) +
VALUES (new.sl_name, new.sl_avail, CURRENT_USER, now())
(1 row)规则的执行顺序、启用与禁用
同一条规则名在同一张表上必须唯一,但同一张表、同一事件类型上可以有多条规则,它们的执行顺序是按规则名字母序:
-- 两条都作用于 INSERT,按名称字母序执行:aaa_first 先于 zzz_last
CREATE RULE aaa_first AS ON INSERT TO t DO ALSO INSERT INTO log1 VALUES (NEW.id);
CREATE RULE zzz_last AS ON INSERT TO t DO ALSO INSERT INTO log2 VALUES (NEW.id);SELECT rulename, ev_type FROM pg_rewrite
WHERE ev_class = 't'::regclass AND ev_type = 3 ORDER BY rulename;
rulename | ev_type
------------+---------
aaa_first | 3
zzz_last | 3这条顺序约定很重要:不要依赖规则之间的隐式依赖关系,它们按名字排序,而非按创建时间。
ALTER TABLE ... ENABLE/DISABLE RULE 可以在不删除规则的情况下临时开关它:
ALTER TABLE orders DISABLE RULE archive_deleted; -- 关闭
ALTER TABLE orders ENABLE RULE archive_deleted; -- 重新启用
SELECT rulename, ev_enabled FROM pg_rewrite WHERE ev_class = 'orders'::regclass;
-- archive_deleted | Dev_enabled 的取值含义:
| 值 | 含义 |
|---|---|
O | origin(默认,正常启用) |
D | disabled(已禁用) |
R | replica(仅在副本模式启用) |
A | always(总是启用,含副本模式) |
replica/always 与逻辑复制/会话复制角色有关,常用于让副本上的同步行为与主库不同。
规则与 ON CONFLICT 的冲突
一个容易踩的坑:带有一级或二级 INSERT/UPDATE 规则的表,不能用于含 ON CONFLICT 的 INSERT:
-- t 上有 INSERT 或 UPDATE 规则
INSERT INTO t (id, v) VALUES (1, 'a') ON CONFLICT (id) DO UPDATE SET v = excluded.v;
-- ERROR: INSERT with ON CONFLICT clause cannot be used with table that has
-- INSERT or UPDATE rules-- 替代方案:改用可更新视图,或改用触发器
-- 建可更新视图(无需自建规则,系统自动改写)
CREATE VIEW v_t AS SELECT id, v FROM t;
INSERT INTO v_t (id, v) VALUES (1, 'a') ON CONFLICT (id) DO UPDATE SET v = excluded.v;规则与权限
规则的权限语义与触发器不同:规则以规则所属表的所有者权限执行,而不是以触发者的权限执行。因此:
| 事实 | 影响 |
|---|---|
| 你必须拥有该表才能创建/修改其规则 | 需要表的 owner 身份 |
| 规则动作以表所有者身份运行 | 可访问触发者本身无权访问的对象 |
| 规则的权限不随调用者变化 | 需谨慎设计,避免越权 |
-- 查看表的所有者
SELECT relname, pg_get_userbyid(relowner) AS owner
FROM pg_class
WHERE relname = 'orders';安全提醒
由于规则动作以表所有者权限执行,用规则实现「视图更新」或「数据分发」时,要确保动作本身不会绕过预期的权限边界。把敏感表的写权限与规则定义分离会更安全。
实战示例
场景:为订单表建立「软删除 + 变更审计 + 库存联动」的规则体系。
第一步:准备表结构。
CREATE TABLE orders (
order_id bigserial PRIMARY KEY,
buyer_id bigint NOT NULL,
product_id bigint NOT NULL,
qty integer NOT NULL,
amount numeric(14,2) NOT NULL,
status text NOT NULL DEFAULT 'active',
deleted_at timestamptz
);
CREATE TABLE orders_archive (
order_id bigint,
buyer_id bigint,
amount numeric(14,2),
archived_at timestamptz,
archived_by text
);
CREATE TABLE orders_audit (
id bigserial PRIMARY KEY,
order_id bigint,
field text,
old_value text,
new_value text,
changed_at timestamptz,
changed_by text
);第二步:删除时归档。
CREATE RULE order_archive AS ON DELETE TO orders
DO ALSO INSERT INTO orders_archive (order_id, buyer_id, amount, archived_at, archived_by)
VALUES (OLD.order_id, OLD.buyer_id, OLD.amount, now(), current_user);第三步:改状态时记审计。
CREATE RULE order_status_audit AS ON UPDATE TO orders
WHERE NEW.status <> OLD.status
DO ALSO INSERT INTO orders_audit (order_id, field, old_value, new_value, changed_at, changed_by)
VALUES (NEW.order_id, 'status', OLD.status, NEW.status, now(), current_user);第四步:改数量时记审计(多条规则,按名字排序)。
CREATE RULE order_qty_audit AS ON UPDATE TO orders
WHERE NEW.qty <> OLD.qty
DO ALSO INSERT INTO orders_audit (order_id, field, old_value, new_value, changed_at, changed_by)
VALUES (NEW.order_id, 'qty', OLD.qty::text, NEW.qty::text, now(), current_user);第五步:验证规则效果。
INSERT INTO orders (buyer_id, product_id, qty, amount)
VALUES (1001, 2001, 2, 199.00);
UPDATE orders SET status = 'paid' WHERE order_id = 1;
UPDATE orders SET qty = 3 WHERE order_id = 1;SELECT order_id, field, old_value, new_value, changed_by
FROM orders_audit
ORDER BY id; order_id | field | old_value | new_value | changed_by
----------+--------+-----------+-----------+------------
1 | status | active | paid | appuser
1 | qty | 2 | 3 | appuser
(2 rows)第六步:删除并检查归档。
DELETE FROM orders WHERE order_id = 1;
SELECT order_id, archived_at, archived_by FROM orders_archive; order_id | archived_at | archived_by
----------+------------------------------+-------------
1 | 2026-09-28 10:31:02.774+08 | appuser
(1 row)orders 中原行已被删除,同时归档表新增一行——这正是 DO ALSO 的效果。
第七步:用规则实现「写入即改写目标表」的模式(日志分区路由)。
-- 应用只写 app_events,规则按分区键把它路由到对应分区
CREATE TABLE app_events (
event_id bigserial,
region text NOT NULL,
payload jsonb,
occurred timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE app_events_cn (LIKE app_events INCLUDING ALL);
CREATE TABLE app_events_other (LIKE app_events INCLUDING ALL);
CREATE RULE route_cn AS ON INSERT TO app_events
WHERE NEW.region = 'CN'
DO INSTEAD INSERT INTO app_events_cn (region, payload, occurred)
VALUES (NEW.region, NEW.payload, NEW.occurred);
CREATE RULE route_other AS ON INSERT TO app_events
WHERE NEW.region <> 'CN'
DO INSTEAD INSERT INTO app_events_other (region, payload, occurred)
VALUES (NEW.region, NEW.payload, NEW.occurred);INSERT INTO app_events (region, payload) VALUES ('CN', '{"a":1}');
INSERT INTO app_events (region, payload) VALUES ('US', '{"b":2}');
SELECT 'cn' AS t, count(*) FROM app_events_cn
UNION ALL
SELECT 'other', count(*) FROM app_events_other; t | count
-------+-------
cn | 1
other | 1
(2 rows)条件 INSTEAD 规则的陷阱
用条件 INSTEAD 规则改写「必须支持的操作」时,必须为每个操作提供一条无条件 INSTEAD 规则。否则当条件不成立时,系统仍会认为可能落到视图的哑表上,从而拒绝该操作(详见 INSTEAD 规则)。
注意事项
错误!把规则当成逐行触发器。 规则是命令改写,不是每行回调;需要逐行处理时应使用触发器。
sql-- 不推荐:需要逐行逻辑却用规则 CREATE RULE row_logic AS ON INSERT TO t DO ALSO SELECT some_per_row_function(NEW.id); -- ❌ 语义与预期不符错误!在规则条件里引用其他表或聚合。
sql-- 非法 CREATE RULE r AS ON UPDATE TO t WHERE NEW.x > (SELECT max(y) FROM other);错误!忘记
_RETURN规则的严格约束。ON SELECT规则必须命名为"_RETURN"、必须无条件INSTEAD、动作只能是一条SELECT。sql-- 非法:名字不对 / 带条件 / 多动作 CREATE RULE my_select AS ON SELECT TO v DO INSTEAD SELECT * FROM t;错误!制造循环规则。 相互引用的规则会在查询时报「infinite recursion」。
sql-- 不要这样 CREATE RULE a AS ON SELECT TO v1 DO INSTEAD SELECT * FROM v2; CREATE RULE b AS ON SELECT TO v2 DO INSTEAD SELECT * FROM v1;不推荐:依赖多规则之间的执行顺序。 同表同事件的规则按名字字母序执行,不是按创建顺序。
sql-- 不推荐:假设"后建的规则后执行" CREATE RULE z_rule AS ON INSERT TO t DO ALSO INSERT INTO log2 ...; CREATE RULE a_rule AS ON INSERT TO t DO ALSO INSERT INTO log1 ...; -- 实际执行顺序:a_rule 先,z_rule 后错误!在有
INSERT/UPDATE规则的表上使用ON CONFLICT。sql-- 报错 INSERT INTO t (id) VALUES (1) ON CONFLICT (id) DO NOTHING;不推荐:在原查询含 volatile 函数时依赖规则。 volatile 函数可能被多次执行,导致行为意外。
sql-- 不推荐:random()、nextval() 等在规则改写后可能被多次求值 CREATE RULE r AS ON INSERT TO t DO ALSO INSERT INTO log VALUES (random());错误!依赖规则动作里的
NOTIFY只在匹配行存在时发送。 目前NOTIFY会无条件执行。不推荐:用规则实现外键/值校验类的约束。 规则只能把命令改写成
NOTHING(静默丢弃数据),无法报错——这类需求必须用触发器。sql-- 不推荐:把非法数据静默丢弃 CREATE RULE validate AS ON INSERT TO t WHERE NEW.ref NOT IN (...) DO INSTEAD NOTHING;不推荐:认为规则比触发器总是更快或更慢。 取决于场景:大批量受影响时规则通常更快(一条额外命令),但规则动作若产生大量「限定不良的 JOIN」,反而可能更慢。
最佳实践
优先考虑触发器。 除视图定义(
_RETURN)这类特殊场景外,能不用规则就不用。sql-- 优先用 INSTEAD OF 触发器实现视图更新 CREATE TRIGGER trg_v_ins INSTEAD OF INSERT ON v_orders FOR EACH ROW EXECUTE FUNCTION v_orders_insert();规则动作指向不同的结果关系或命令类型,避免无限递归。
sqlCREATE RULE r AS ON UPDATE TO orders DO ALSO INSERT INTO orders_audit (...) VALUES (...);规则名使用有意义的字母序前缀,让多规则执行顺序可控、可预测。
sqlCREATE RULE audit_10_status AS ON UPDATE TO orders ...; CREATE RULE audit_20_qty AS ON UPDATE TO orders ...;条件规则务必配上无条件兜底规则,确保系统知道不会落到哑表。
sqlCREATE RULE protect_noop AS ON UPDATE TO v DO INSTEAD NOTHING;用
pg_rules/pg_get_ruledef()审查现有规则,把规则纳入代码评审。sqlSELECT tablename, rulename, definition FROM pg_rules WHERE schemaname = 'public';给规则相关的临时开关留出
ENABLE/DISABLE RULE通道,便于排障。sqlALTER TABLE orders DISABLE RULE order_archive;规则涉及的审计/日志表考虑分区或按时间清理,避免无限增长。
在文档里显式记录每条规则的目的(尤其
DO ALSO与DO INSTEAD的区别),规则比触发器更难被后人看懂。测试规则时同时验证「原命令是否生效」,用
DELETE n/UPDATE n的行数确认ALSO与INSTEAD的实际行为:sqlDELETE FROM orders WHERE order_id = 1; -- 观察返回的是 DELETE 1 还是 DELETE 0规则与逻辑复制/副本模式共存时,检查
ev_enabled设置,确保副本行为符合预期。
下一节
继续学习:INSTEAD 规则