Skip to content

CREATE RULE 规则 ​

PostgreSQL 的规则系统(rule system,更准确地说是查询重写规则系统)在查询被规划(planning)之前改写查询树,把规则纳入考虑。CREATE RULE 就是定义这种重写规则:当某张表或视图上发生 SELECT/INSERT/UPDATE/DELETE 时,附加执行另一条命令,或用另一条命令取代原命令。规则不是触发器——它在命令开始执行之前就完成了改写。

前置知识

阅读本节前,建议先了解:CREATE / DROP VIEW、创建表(CREATE TABLE)

基础概念 ​

规则是什么 ​

text
一条查询的生命周期(带规则):

  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)定义视图内容
INSERTNEW把插入改写到其他表
UPDATENEW、OLD记录变更日志、改写更新目标
DELETEOLD级联删除保护、改写删除目标

语法与用法 ​

text
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 的库存变更记一条日志:

sql
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());

执行一次更新:

sql
UPDATE shoelace_data SET sl_avail = 6 WHERE sl_name = 'sl7';

查看日志表(规则自动补写了记录):

sql
SELECT * FROM shoelace_log;
text
 sl_name | sl_avail | log_who |           log_when
---------+----------+---------+-------------------------------
 sl7     |        6 | appuser | 2026-09-28 10:12:41.882117+08
(1 row)

查看表上定义的规则:

sql
SELECT rulename, ev_type, is_instead, ev_enabled
FROM pg_rewrite
WHERE ev_class = 'shoelace_data'::regclass;
text
  rulename    | ev_type | is_instead | ev_enabled
--------------+---------+------------+------------
 log_shoelace | 2       | f          | O
(1 row)

详细说明 ​

规则如何被应用 ​

重写器在规划之前工作。它拿到查询树后,检查「结果关系 + 命令类型」是否与某条 CREATE RULE 声明的对象与事件匹配。对 INSERT/UPDATE/DELETE 这类更新规则,重写器会新建一个查询树列表(初始为空),然后按规则生成零个到多个新查询树:

text
一条规则(一个动作)的三种情形:

  ① 无条件 + ALSO / INSTEAD
       → 规则动作的查询树,附加原查询的限定条件

  ② 有条件 + ALSO
       → 规则动作的查询树,附加"规则条件 + 原查询条件"
       → 再加上未改动的原查询树(共 2 个)

  ③ 有条件 + INSTEAD
       → 规则动作的查询树,附加"规则条件 + 原查询条件"
       → 再加上"原查询树 + 规则条件的否定"(共 2 个)

一个容易忽视但很重要的顺序约定:

事件原命令与规则动作的先后
ON INSERT原命令(若未被 INSTEAD 抑制)先执行,规则动作后执行,这样动作能看到刚插入的行
ON UPDATE / ON DELETE规则动作先执行,原命令后执行,这样动作能看到「将要被更新/删除」的行

如果顺序反过来,日志类规则就会失效——UPDATE 先执行的话,0 <> sl_avail 这样的条件将永远不成立:

sql
-- 假设规则是"先 UPDATE 再记日志",则下面这条日志永远写不出来
-- 因为行已被改成 0,OLD 与 NEW 相等
UPDATE shoelace_data SET sl_avail = 0 WHERE sl_color = 'black';

生成的规则动作查询树会被再次丢回重写系统,可能触发更多规则。因此规则的动作必须与规则本身「命令类型不同」或「结果关系不同」,否则会陷入无限递归:

text
-- 循环示例:两条 _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 引用相关行:

伪关系可用于含义
NEWON INSERT、ON UPDATE正在被插入/更新后的新行
OLDON UPDATE、ON DELETE已存在、将被更新/删除的旧行
sql
-- 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());
sql
-- ON DELETE 只能用 OLD
CREATE RULE archive_deleted AS ON DELETE TO orders
  DO ALSO INSERT INTO orders_archive
     SELECT OLD.*, now(), current_user;
text
-- 用错伪关系会直接报错:
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 的区别 ​

这是规则最容易混淆的一点:

text
DO ALSO     :原命令照常执行,规则动作"额外"执行      → 1 + N
DO INSTEAD  :规则动作"取代"原命令,原命令不再执行     → 只有规则动作

对比示例——记录删除并保留原行,与记录删除并阻止原行:

sql
-- 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());
text
不显式写 ALSO 或 INSTEAD 时,默认是 ALSO。

验证效果——用 INSTEAD 后,orders 的行数不变:

sql
SELECT count(*) FROM orders;

DELETE FROM orders WHERE order_id = 1001;

SELECT count(*) FROM orders;   -- 数量不变,因为删除被 INSTEAD 规则取代了
text
 count
-------
 84210
(1 row)

DELETE 0

 count
-------
 84210
(1 row)

注意 psql 返回的是 DELETE 0——因为原 DELETE 已被取代,规则动作插入到的是另一张表。

规则条件的限制 ​

规则的 WHERE 条件是一个返回 boolean 的 SQL 表达式,但有两条硬性限制:

限制说明
只能引用 NEW / OLD不能引用其他任何表
不能包含聚合函数不能写 sum(...)、count(...) 等
sql
-- 合法:只引用 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());
text
-- 非法:引用了另一张表
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。这解释了为什么「未修改的列」不会误触发日志规则:

text
-- 原查询没有修改 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空动作

一条规则可以有多个动作,用括号加 ; 分隔:

sql
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 会无条件执行——即使没有任何行匹配规则条件也会发送。这是当前的实现限制:

sql
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 规则定义:

sql
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;
text
 rulename | ev_type | is_instead
----------+---------+------------
 _RETURN  | 1       | t
(1 row)

可以用 CREATE OR REPLACE RULE "_RETURN" 来重定义视图,但更好的写法是 CREATE OR REPLACE VIEW:

sql
-- 不推荐:用规则重定义视图(虽然可行)
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 规则手工「造」一个视图:

sql
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;
text
-- 注意:这个技巧会让 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规则动作(内部形式)

查看某张表的所有规则:

sql
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 视图:

sql
SELECT schemaname, tablename, rulename, definition
FROM pg_rules
WHERE tablename = 'orders';
text
 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() 还原规则的可读定义:

sql
SELECT pg_get_ruledef(oid)
FROM pg_rewrite
WHERE rulename = 'log_shoelace';
text
                                        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)

规则的执行顺序、启用与禁用 ​

同一条规则名在同一张表上必须唯一,但同一张表、同一事件类型上可以有多条规则,它们的执行顺序是按规则名字母序:

sql
-- 两条都作用于 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);
text
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 可以在不删除规则的情况下临时开关它:

sql
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 | D

ev_enabled 的取值含义:

值含义
Oorigin(默认,正常启用)
Ddisabled(已禁用)
Rreplica(仅在副本模式启用)
Aalways(总是启用,含副本模式)

replica/always 与逻辑复制/会话复制角色有关,常用于让副本上的同步行为与主库不同。

规则与 ON CONFLICT 的冲突 ​

一个容易踩的坑:带有一级或二级 INSERT/UPDATE 规则的表,不能用于含 ON CONFLICT 的 INSERT:

text
-- 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
sql
-- 替代方案:改用可更新视图,或改用触发器
-- 建可更新视图(无需自建规则,系统自动改写)
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 身份
规则动作以表所有者身份运行可访问触发者本身无权访问的对象
规则的权限不随调用者变化需谨慎设计,避免越权
sql
-- 查看表的所有者
SELECT relname, pg_get_userbyid(relowner) AS owner
FROM pg_class
WHERE relname = 'orders';

安全提醒

由于规则动作以表所有者权限执行,用规则实现「视图更新」或「数据分发」时,要确保动作本身不会绕过预期的权限边界。把敏感表的写权限与规则定义分离会更安全。

实战示例 ​

场景:为订单表建立「软删除 + 变更审计 + 库存联动」的规则体系。

第一步:准备表结构。

sql
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
);

第二步:删除时归档。

sql
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);

第三步:改状态时记审计。

sql
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);

第四步:改数量时记审计(多条规则,按名字排序)。

sql
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);

第五步:验证规则效果。

sql
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;
sql
SELECT order_id, field, old_value, new_value, changed_by
FROM orders_audit
ORDER BY id;
text
 order_id |  field | old_value | new_value | changed_by
----------+--------+-----------+-----------+------------
        1 | status | active    | paid      | appuser
        1 | qty    | 2         | 3         | appuser
(2 rows)

第六步:删除并检查归档。

sql
DELETE FROM orders WHERE order_id = 1;

SELECT order_id, archived_at, archived_by FROM orders_archive;
text
 order_id |         archived_at          | archived_by
----------+------------------------------+-------------
        1 | 2026-09-28 10:31:02.774+08   | appuser
(1 row)

orders 中原行已被删除,同时归档表新增一行——这正是 DO ALSO 的效果。

第七步:用规则实现「写入即改写目标表」的模式(日志分区路由)。

sql
-- 应用只写 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);
sql
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;
text
   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();
  • 规则动作指向不同的结果关系或命令类型,避免无限递归。

    sql
    CREATE RULE r AS ON UPDATE TO orders DO ALSO INSERT INTO orders_audit (...) VALUES (...);
  • 规则名使用有意义的字母序前缀,让多规则执行顺序可控、可预测。

    sql
    CREATE RULE audit_10_status AS ON UPDATE TO orders ...;
    CREATE RULE audit_20_qty    AS ON UPDATE TO orders ...;
  • 条件规则务必配上无条件兜底规则,确保系统知道不会落到哑表。

    sql
    CREATE RULE protect_noop AS ON UPDATE TO v DO INSTEAD NOTHING;
  • 用 pg_rules / pg_get_ruledef() 审查现有规则,把规则纳入代码评审。

    sql
    SELECT tablename, rulename, definition FROM pg_rules WHERE schemaname = 'public';
  • 给规则相关的临时开关留出 ENABLE/DISABLE RULE 通道,便于排障。

    sql
    ALTER TABLE orders DISABLE RULE order_archive;
  • 规则涉及的审计/日志表考虑分区或按时间清理,避免无限增长。

  • 在文档里显式记录每条规则的目的(尤其 DO ALSO 与 DO INSTEAD 的区别),规则比触发器更难被后人看懂。

  • 测试规则时同时验证「原命令是否生效」,用 DELETE n / UPDATE n 的行数确认 ALSO 与 INSTEAD 的实际行为:

    sql
    DELETE FROM orders WHERE order_id = 1;   -- 观察返回的是 DELETE 1 还是 DELETE 0
  • 规则与逻辑复制/副本模式共存时,检查 ev_enabled 设置,确保副本行为符合预期。

下一节

继续学习:INSTEAD 规则

参考链接 ​