INSTEAD 规则
INSTEAD 规则用规则动作取代原命令,而不是在原命令之外附加执行。它最常见的用途是让视图「看起来可写」:在视图上定义 ON INSERT / ON UPDATE / ON DELETE 的 INSTEAD 规则,把对视图的写操作改写到真实基表上。另一个用途是保护性屏蔽:用 DO INSTEAD NOTHING 让某些操作静默失效。
前置知识
阅读本节前,建议先了解:CREATE RULE 规则、可更新视图
基础概念
INSTEAD 的含义
DO ALSO :原命令 + 规则动作 (1 + N)
DO INSTEAD :规则动作取代原命令 (只有规则动作,原命令消失)
对视图的写操作:
INSERT INTO v_shoelace ... ──► 应用 INSTEAD 规则 ──► 改写为
INSERT INTO shoelace_data ...
(视图本身无存储,"写视图"这个动作实际不存在)为什么需要 INSTEAD 规则
| 场景 | 为什么需要 |
|---|---|
| 复杂视图(含 JOIN、聚合、表达式)不能自动更新 | 系统无法推断如何把写入映射回基表,需手工指定 |
| 需要把一个视图的写操作分发到多张表 | 一条规则可含多个动作 |
| 需要拦截并改写写入语义(如写日志表代替写原表) | 用 INSTEAD 替换行为 |
| 需要「只读保护」某些操作 | DO INSTEAD NOTHING |
与 INSTEAD OF 触发器对比
| 维度 | INSTEAD 规则 | INSTEAD OF 触发器 |
|---|---|---|
| 执行时机 | 查询重写阶段(执行前) | 语句执行阶段,按行回调 |
| 粒度 | 一次命令改写 | FOR EACH ROW(视图上必须按行) |
| 写法复杂度 | 改写规则,较隐晦 | 过程函数,直观 |
支持 RETURNING | 需要在规则里写 RETURNING | 通过触发器返回新行 |
| 官方倾向 | 视图定义(_RETURN)必用 | 视图更新推荐 |
官方建议
官方文档明确指出:视图更新「用 INSTEAD OF 触发器实现通常比用规则更容易」。原因是触发器语义简单、可控;规则容易有意外。但对于简单映射(单表、字段一一对应),规则的性能通常更好(一条额外命令 vs 逐行回调)。
自动可更新视图的优先级
一个重要的前提:本身可自动更新的视图不需要用户建规则。
视图能否自动更新?
简单视图(单表、无聚合/窗口/DISTINCT/集合运算,
无 GROUP BY/HAVING/LIMIT,列均为直接引用)
│
├─ 是 → 系统自动改写,无需自建规则
│ (显式建规则也能用,但自动改写通常更快)
│
└─ 否 → 需要 INSTEAD 规则或 INSTEAD OF 触发器-- 简单视图:自动可更新,无需规则
CREATE VIEW v_simple AS
SELECT order_id, buyer_id, amount FROM orders;
INSERT INTO v_simple (order_id, buyer_id, amount) VALUES (9001, 1001, 88.00);
-- 系统自动改写为 INSERT INTO orders ...显式规则 vs 自动改写
虽然可以给「本身已可自动更新」的视图再显式建规则,但自动更新变换通常性能更好(它会尽量下推条件、生成更优的计划)。能靠自动更新就不要建规则:
-- 不推荐:给简单视图画蛇添足地建 INSTEAD 规则
CREATE RULE v_simple_ins AS ON INSERT TO v_simple
DO INSTEAD INSERT INTO orders (order_id, buyer_id, amount)
VALUES (NEW.order_id, NEW.buyer_id, NEW.amount);语法与用法
CREATE [ OR REPLACE ] RULE name AS ON event
TO view_name [ WHERE condition ]
DO INSTEAD { NOTHING | command | ( command ; command ... ) }
-- event 取值:INSERT | UPDATE | DELETE
-- INSTEAD 规则动作里可用 NEW / OLD 引用视图的行最简示例——让一个「含派生的视图」可插入:
CREATE TABLE shoelace_data (
sl_name text PRIMARY KEY,
sl_avail integer,
sl_color text
);
-- 带表达式的视图(sl_len_cm 是派生列),本身不可自动更新
CREATE VIEW v_shoelace AS
SELECT sl_name,
sl_avail,
sl_color,
sl_avail * 2 AS sl_avail_double
FROM shoelace_data;
-- 用 INSTEAD 规则让插入可用
CREATE RULE v_shoelace_ins AS ON INSERT TO v_shoelace
DO INSTEAD INSERT INTO shoelace_data (sl_name, sl_avail, sl_color)
VALUES (NEW.sl_name, NEW.sl_avail, NEW.sl_color);插入并验证:
INSERT INTO v_shoelace (sl_name, sl_avail, sl_color) VALUES ('sl1', 5, 'black');
SELECT * FROM v_shoelace ORDER BY sl_name; sl_name | sl_avail | sl_color | sl_avail_double
---------+----------+----------+-----------------
sl1 | 5 | black | 10
(1 row)详细说明
用规则实现可更新视图
要让一个复杂视图支持 INSERT/UPDATE/DELETE,需要为每种想支持的操作各建一条 INSTEAD 规则。三条规则分别对应三种写入:
CREATE TABLE shoelace_data (
sl_name text PRIMARY KEY,
sl_avail integer,
sl_color text,
sl_len integer,
sl_unit text
);
CREATE VIEW shoelace AS
SELECT sl_name, sl_avail, sl_color, sl_len, sl_unit,
sl_len * CASE sl_unit WHEN 'cm' THEN 1 WHEN 'inch' THEN 2.54 ELSE 0 END AS sl_len_cm
FROM shoelace_data;
-- INSERT
CREATE RULE shoelace_ins AS ON INSERT TO shoelace
DO INSTEAD INSERT INTO shoelace_data (sl_name, sl_avail, sl_color, sl_len, sl_unit)
VALUES (NEW.sl_name, NEW.sl_avail, NEW.sl_color, NEW.sl_len, NEW.sl_unit);
-- UPDATE
CREATE RULE shoelace_upd AS ON UPDATE TO shoelace
DO INSTEAD UPDATE shoelace_data
SET sl_name = NEW.sl_name,
sl_avail = NEW.sl_avail,
sl_color = NEW.sl_color,
sl_len = NEW.sl_len,
sl_unit = NEW.sl_unit
WHERE sl_name = OLD.sl_name;
-- DELETE
CREATE RULE shoelace_del AS ON DELETE TO shoelace
DO INSTEAD DELETE FROM shoelace_data WHERE sl_name = OLD.sl_name;验证三种操作都作用到了基表:
INSERT INTO shoelace VALUES ('sl9', 10, 'white', 80, 'cm');
UPDATE shoelace SET sl_avail = 20 WHERE sl_name = 'sl9';
DELETE FROM shoelace WHERE sl_name = 'sl9';
SELECT count(*) FROM shoelace_data WHERE sl_name = 'sl9'; count
-------
0
(1 row)定位规则的要点
INSERT/UPDATE规则用NEW.<列>取新值。UPDATE/DELETE规则用OLD.<列>定位要操作的行。UPDATE规则的WHERE通常用OLD.<主键>,而非NEW.<主键>,否则改名后可能定位不到。
RETURNING 子句的处理
如果希望视图支持 INSERT RETURNING / UPDATE RETURNING / DELETE RETURNING,规则动作里必须包含一个合适的 RETURNING 子句:
CREATE OR REPLACE RULE shoelace_ins AS ON INSERT TO shoelace
DO INSTEAD INSERT INTO shoelace_data (sl_name, sl_avail, sl_color, sl_len, sl_unit)
VALUES (NEW.sl_name, NEW.sl_avail, NEW.sl_color, NEW.sl_len, NEW.sl_unit)
RETURNING
shoelace_data.sl_name,
shoelace_data.sl_avail,
shoelace_data.sl_color,
shoelace_data.sl_len,
shoelace_data.sl_unit,
shoelace_data.sl_len * CASE shoelace_data.sl_unit
WHEN 'cm' THEN 1 WHEN 'inch' THEN 2.54 ELSE 0 END AS sl_len_cm;RETURNING 的三条硬性规则:
| 规则 | 说明 |
|---|---|
只有无条件 INSTEAD 规则可含 RETURNING | 条件规则或 ALSO 规则都不行 |
同一事件最多一个 RETURNING | 保证只有一个候选来算结果 |
若所有可用规则都没有 RETURNING | 视图上的 RETURNING 查询会被拒绝 |
-- 没有为视图提供 RETURNING 时:
INSERT INTO shoelace (sl_name, sl_avail) VALUES ('x', 1) RETURNING *;
-- ERROR: cannot perform INSERT RETURNING on relation "shoelace"
-- HINT: You need an unconditional ON INSERT DO INSTEAD rule with a RETURNING clause.OLD/NEW 在规则 RETURNING 中的特殊含义
在规则的 RETURNING 子句中,OLD 和 NEW 指的是被加入重写查询的额外范围表条目,而不是结果关系的旧/新行。因此对 UPDATE 规则,若 RETURNING 里写 old.sl_name,返回的永远是旧名,无论查询里写的是 OLD 还是 NEW。
推荐做法:RETURNING 里直接引用结果关系的列(如 shoelace_data.sl_name),避免歧义:
CREATE OR REPLACE RULE shoelace_upd AS ON UPDATE TO shoelace
DO INSTEAD UPDATE shoelace_data
SET sl_name = NEW.sl_name, sl_avail = NEW.sl_avail
WHERE sl_name = OLD.sl_name
RETURNING shoelace_data.sl_name, shoelace_data.sl_avail;条件规则与无条件兜底规则
这是 INSTEAD 规则最微妙、最容易踩坑的一点。对于复杂视图:
每一个你想允许的写操作,都必须存在一条无条件的
INSTEAD规则。
原因:如果规则是有条件的或不是 INSTEAD,系统会认为在某些情况下可能仍会尝试在视图的哑表上执行写操作,于是直接拒绝。
视图 v 上只有条件 INSTEAD 规则:
CREATE RULE r AS ON UPDATE TO v
WHERE NEW.status = 'active'
DO INSTEAD UPDATE base SET ...;
UPDATE v SET ... ; -- 若某行条件不成立,系统担心落到哑表上 → 直接拒绝整个 UPDATE解决方案:加一条无条件 DO INSTEAD NOTHING 兜底,再让条件规则非 INSTEAD(即 ALSO),在条件成立时"加上"动作:
-- ① 无条件兜底:保证系统知道哑表永远不会被写
CREATE RULE shoelace_protect AS ON UPDATE TO shoelace
DO INSTEAD NOTHING;
-- ② 条件规则改为 ALSO(非 INSTEAD),条件成立时追加动作
CREATE RULE shoelace_cond_upd AS ON UPDATE TO shoelace
WHERE NEW.sl_avail > 0
DO ALSO UPDATE shoelace_data SET sl_avail = NEW.sl_avail
WHERE sl_name = OLD.sl_name;条件规则不支持 RETURNING
上述「无条件 INSTEAD NOTHING + 条件 ALSO」的组合目前不支持 RETURNING 查询。如果需要 RETURNING,就只能在无条件 INSTEAD 规则里写 RETURNING。
-- 用 INSTEAD NOTHING 也能"无条件屏蔽"某个操作:
CREATE RULE shoe_ins_protect AS ON INSERT TO shoe DO INSTEAD NOTHING;
CREATE RULE shoe_upd_protect AS ON UPDATE TO shoe DO INSTEAD NOTHING;
CREATE RULE shoe_del_protect AS ON DELETE TO shoe DO INSTEAD NOTHING;
-- 之后对 shoe 的这三种操作都会变成"什么都不做"
DELETE FROM shoe WHERE ...; -- 返回 DELETE 0,基表无变化用 DO INSTEAD NOTHING 保护视图
DO INSTEAD NOTHING 让某个事件变成一个空操作——查询树列表为空,整个查询「什么也不做」。这是保护视图不被误写的轻量手段:
-- 只允许查、不允许写
CREATE VIEW v_report AS
SELECT region_id, sum(amount)::numeric(14,2) AS sales_amt
FROM orders GROUP BY region_id;
CREATE RULE v_report_no_ins AS ON INSERT TO v_report DO INSTEAD NOTHING;
CREATE RULE v_report_no_upd AS ON UPDATE TO v_report DO INSTEAD NOTHING;
CREATE RULE v_report_no_del AS ON DELETE TO v_report DO INSTEAD NOTHING;DELETE FROM v_report WHERE region_id = '01';DELETE 0静默失败的风险
DO INSTEAD NOTHING 是静默的——操作返回成功但什么都没做,调用方可能误以为写入生效了。若需要明确报错,必须改用触发器(在触发器里 RAISE EXCEPTION):
CREATE OR REPLACE FUNCTION deny_write() RETURNS trigger AS $$
BEGIN
RAISE EXCEPTION 'view % is read-only', TG_TABLE_NAME;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER v_report_deny
INSTEAD OF INSERT OR UPDATE OR DELETE ON v_report
FOR EACH ROW EXECUTE FUNCTION deny_write();一个视图连多表:多动作规则
INSTEAD 规则可以包含多条命令,用括号和 ; 分隔。这让一个视图的写入可以分发到多张基表——这是 JOIN 视图更新的常见做法:
CREATE TABLE buyers (
buyer_id bigint PRIMARY KEY,
name text
);
CREATE TABLE orders (
order_id bigint PRIMARY KEY,
buyer_id bigint REFERENCES buyers(buyer_id),
amount numeric(14,2)
);
-- JOIN 视图:同时展示买家名与订单金额
CREATE VIEW v_order_detail AS
SELECT o.order_id, o.buyer_id, b.name AS buyer_name, o.amount
FROM orders o JOIN buyers b ON b.buyer_id = o.buyer_id;
-- 插入时同时写两张表(若买家已存在,则靠主键冲突处理,这里简化为直接写)
CREATE RULE v_order_detail_ins AS ON INSERT TO v_order_detail
DO INSTEAD (
INSERT INTO buyers (buyer_id, name) VALUES (NEW.buyer_id, NEW.buyer_name)
ON CONFLICT (buyer_id) DO NOTHING;
INSERT INTO orders (order_id, buyer_id, amount)
VALUES (NEW.order_id, NEW.buyer_id, NEW.amount);
);INSERT INTO v_order_detail (order_id, buyer_id, buyer_name, amount)
VALUES (5001, 1001, '张伟', 199.00);
SELECT * FROM v_order_detail WHERE order_id = 5001; order_id | buyer_id | buyer_name | amount
----------+----------+------------+--------
5001 | 1001 | 张伟 | 199.00
(1 row)规则动作里可以再触发规则
规则动作生成的查询树会再次被丢回重写系统,因此动作中的命令本身也可能激活其他规则。上例中写 buyers/orders 若另有规则,会继续被改写。这也意味着要小心循环。
规则 vs INSTEAD OF 触发器:选型
两者都能实现视图更新,选型取决于:
| 判断 | 选规则 | 选触发器 |
|---|---|---|
| 逻辑是简单字段映射 | ✅ 更简洁、性能更好 | 也可以,但更啰嗦 |
| 需要复杂 PL/pgSQL 逻辑 | ❌ 规则里写不了过程逻辑 | ✅ |
| 需要基于多行/聚合做决策 | ❌ | ✅(FOR EACH ROW 内可查询) |
需要 RETURNING 且逻辑复杂 | 受限(只能无条件 INSTEAD) | ✅ 更自然 |
| 需要明确拒绝并报错 | ❌ 只能静默 NOTHING | ✅ RAISE EXCEPTION |
| 大批量受影响、逻辑简单 | ✅ 一条额外命令,快 | ❌ 逐行回调,慢 |
性能上的核心差异在官方文档中说明得很清楚:规则一次命令一条额外命令;触发器每个受影响行都要调用一次。因此当一条语句影响大量行时,规则通常更快;但若规则动作产生「大量且限定不良的 JOIN」,规划器处理不好,规则也可能更慢:
-- 规则方案(批量):一条 DELETE,一次性处理
CREATE RULE computer_del AS ON DELETE TO computer
DO INSTEAD DELETE FROM software WHERE hostname = OLD.hostname;
-- 触发器方案(逐行):影响 2000 行 → 2000 次触发器调用
CREATE TRIGGER computer_del_trg
AFTER DELETE ON computer
FOR EACH ROW EXECUTE FUNCTION delete_software();-- 批量删除场景下,对比两种方案
DELETE FROM computer WHERE hostname ~ '^old';规则方案:生成一条带 JOIN 的 DELETE,一次执行完毕
触发器方案:computer 侧 2000 次行删除 → software 侧 2000 次索引扫描依赖规则做「只写日志」的拦截
INSTEAD 规则可以把一次写操作完全改成写另一张表,例如「不真正删除,只记录删除请求」:
CREATE TABLE delete_requests (
id bigserial PRIMARY KEY,
order_id bigint,
requested_at timestamptz,
requested_by text
);
CREATE RULE order_soft_delete AS ON DELETE TO orders
DO INSTEAD INSERT INTO delete_requests (order_id, requested_at, requested_by)
VALUES (OLD.order_id, now(), current_user);SELECT count(*) FROM orders;
DELETE FROM orders WHERE order_id = 5001;
SELECT count(*) FROM orders;
SELECT order_id, requested_by FROM delete_requests; count
-------
84210
(1 row)
DELETE 0
count
-------
84210
(1 row)
order_id | requested_by
----------+--------------
5001 | appuser
(1 row)注意返回的仍是 DELETE 0——原命令被取代,orders 一行未删,删除请求被记录了下来。
规则与权限的相互作用
由于规则动作以规则所属表的所有者权限执行,用 INSTEAD 规则做视图更新时,可以做到「调用者无需基表权限」:
-- 视图所有者:alice;调用者:bob
CREATE VIEW v_shoelace AS SELECT sl_name, sl_avail, sl_color FROM shoelace_data;
CREATE RULE v_shoelace_ins AS ON INSERT TO v_shoelace
DO INSTEAD INSERT INTO shoelace_data (sl_name, sl_avail, sl_color)
VALUES (NEW.sl_name, NEW.sl_avail, NEW.sl_color);
GRANT INSERT ON v_shoelace TO bob; -- bob 只需视图的 INSERT 权限
-- bob 执行:
INSERT INTO v_shoelace (sl_name, sl_avail, sl_color) VALUES ('b1', 3, 'red');
-- 成功:对 shoelace_data 的写入以 alice(视图所有者)的权限检查-- 确认视图所有者
SELECT relname, pg_get_userbyid(relowner) AS owner
FROM pg_class WHERE relname = 'v_shoelace';权限设计需谨慎
规则以所有者身份运行,意味着授予视图写权限等于授予规则动作所需的全部权限。若规则动作访问了敏感表,必须确保动作本身的语义不会被滥用(例如条件不严导致越权写入)。此外,若需要行级安全(RLS),视图应设置 security_barrier 选项:
CREATE VIEW v_phone WITH (security_barrier) AS
SELECT person, phone FROM phone_data WHERE phone NOT LIKE '412%';security_barrier 会阻止恶意函数在视图过滤之前被下推,代价是性能可能显著变差(最快的计划若威胁安全就必须被拒绝执行),因此默认不开启。
规则的系统视图排查
用 pg_rules 与 pg_get_ruledef() 审查 INSTEAD 规则:
SELECT tablename, rulename, definition
FROM pg_rules
WHERE tablename IN ('v_shoelace', 'shoelace')
ORDER BY tablename, rulename; tablename | rulename | definition
------------+-----------------+-----------------------------------------------------------------
shoelace | shoelace_del | CREATE RULE shoelace_del AS ON DELETE TO public.shoelace (...) +
shoelace | shoelace_ins | CREATE RULE shoelace_ins AS ON INSERT TO public.shoelace (...) +
shoelace | shoelace_upd | CREATE RULE shoelace_upd AS ON UPDATE TO public.shoelace (...) +
(3 rows)-- 只列出 INSTEAD 规则及其事件类型
SELECT r.rulename,
CASE r.ev_type WHEN 1 THEN 'SELECT' WHEN 2 THEN 'UPDATE'
WHEN 3 THEN 'INSERT' WHEN 4 THEN 'DELETE' END AS event,
r.is_instead,
r.ev_enabled
FROM pg_rewrite r
WHERE r.ev_class = 'shoelace'::regclass
ORDER BY r.rulename; rulename | event | is_instead | ev_enabled
--------------+--------+------------+------------
shoelace_del | DELETE | t | O
shoelace_ins | INSERT | t | O
shoelace_upd | UPDATE | t | O
(3 rows)临时禁用与替换规则
排障时可以临时禁用规则,或直接替换:
-- 临时禁用
ALTER TABLE shoelace DISABLE RULE shoelace_del;
-- 替换(等价于删了重建)
CREATE OR REPLACE RULE shoelace_del AS ON DELETE TO shoelace
DO INSTEAD NOTHING;
-- 彻底删除
DROP RULE shoelace_del ON shoelace;-- 注意:CREATE OR REPLACE RULE 不能改变事件类型
-- 下面这条会报错(原规则是 ON DELETE,这里改成 ON INSERT)
CREATE OR REPLACE RULE shoelace_del AS ON INSERT TO shoelace DO INSTEAD NOTHING;
-- ERROR: cannot convert rule for relation "shoelace" to view实战示例
场景:把「多表 JOIN 的订单明细视图」做成可写视图,支持插入订单并自动补建买家,同时禁止删除。
第一步:准备基表与视图。
CREATE TABLE buyers (
buyer_id bigserial PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE orders (
order_id bigserial PRIMARY KEY,
buyer_id bigint NOT NULL REFERENCES buyers(buyer_id),
product_id bigint NOT NULL,
amount numeric(14,2) NOT NULL,
status text NOT NULL DEFAULT 'active'
);
CREATE VIEW v_orders AS
SELECT o.order_id, o.buyer_id, b.name AS buyer_name,
o.product_id, o.amount, o.status
FROM orders o JOIN buyers b ON b.buyer_id = o.buyer_id;第二步:让插入可用(多动作:补买家 + 建订单),并支持 RETURNING。
CREATE RULE v_orders_ins AS ON INSERT TO v_orders
DO INSTEAD (
INSERT INTO buyers (name) VALUES (NEW.buyer_name)
ON CONFLICT DO NOTHING;
INSERT INTO orders (buyer_id, product_id, amount, status)
VALUES (NEW.buyer_id, NEW.product_id, NEW.amount, NEW.status);
);多动作 INSTEAD 不支持 RETURNING
含多个动作的 INSTEAD 规则不能带 RETURNING。若需要 RETURNING,规则动作必须只有一条命令。
第三步:改为单动作 + RETURNING 的写法(推荐做法)。
CREATE OR REPLACE RULE v_orders_ins AS ON INSERT TO v_orders
DO INSTEAD
INSERT INTO orders (buyer_id, product_id, amount, status)
VALUES (NEW.buyer_id, NEW.product_id, NEW.amount, NEW.status)
RETURNING orders.order_id, orders.buyer_id, orders.product_id,
orders.amount, orders.status;INSERT INTO v_orders (buyer_id, buyer_name, product_id, amount, status)
VALUES (1001, '李娜', 2001, 199.00, 'active')
RETURNING order_id, amount; order_id | amount
----------+--------
1 | 199.00
(1 row)第四步:更新规则——只允许改金额。
CREATE RULE v_orders_upd AS ON UPDATE TO v_orders
DO INSTEAD
UPDATE orders SET amount = NEW.amount
WHERE order_id = OLD.order_id
RETURNING orders.order_id, orders.buyer_id, orders.product_id,
orders.amount, orders.status;UPDATE v_orders SET amount = 259.00 WHERE order_id = 1 RETURNING order_id, amount; order_id | amount
----------+--------
1 | 259.00
(1 row)第五步:禁止删除(保护性规则)。
CREATE RULE v_orders_no_del AS ON DELETE TO v_orders DO INSTEAD NOTHING;DELETE FROM v_orders WHERE order_id = 1;DELETE 0第六步:验证多动作规则的分发效果。
CREATE RULE v_orders_ins_multi AS ON INSERT TO v_orders
DO INSTEAD (
INSERT INTO buyers (name) VALUES (NEW.buyer_name);
INSERT INTO orders (buyer_id, product_id, amount, status)
VALUES (NEW.buyer_id, NEW.product_id, NEW.amount, NEW.status);
);-- 检查某次插入是否同时写了两张表
SELECT (SELECT count(*) FROM buyers) AS buyers_cnt,
(SELECT count(*) FROM orders) AS orders_cnt; buyers_cnt | orders_cnt
------------+------------
1 | 1
(1 row)第七步:规则清单核对。
SELECT tablename, rulename, definition
FROM pg_rules
WHERE tablename = 'v_orders'
ORDER BY rulename; tablename | rulename | definition
-----------+-------------------+-------------------------------------------------------------
v_orders | v_orders_ins | CREATE RULE v_orders_ins AS ON INSERT TO public.v_orders ...+
v_orders | v_orders_no_del | CREATE RULE v_orders_no_del AS ON DELETE TO public.v_orders +
v_orders | v_orders_upd | CREATE RULE v_orders_upd AS ON UPDATE TO public.v_orders ...+
(3 rows)实战小结
- 每个想支持的写操作都要有独立规则;
- 需要
RETURNING→ 规则动作必须是单条命令 + 无条件INSTEAD; - 需要多动作分发 → 放弃
RETURNING; - 复杂逻辑(校验、报错、条件分支)→ 改用
INSTEAD OF触发器。
注意事项
错误!给视图只建条件
INSTEAD规则。 没有无条件INSTEAD规则时,系统会拒绝该写操作。sql-- 不推荐:只有条件规则 CREATE RULE r AS ON UPDATE TO v WHERE NEW.status = 'active' DO INSTEAD UPDATE base SET ...; UPDATE v SET status = 'x'; -- 被拒绝错误!给多动作
INSTEAD规则加RETURNING。sql-- 非法 CREATE RULE r AS ON INSERT TO v DO INSTEAD ( INSERT INTO a (...) VALUES (...); INSERT INTO b (...) VALUES (...); ) RETURNING a.id;错误!在同事件上放多个
RETURNING。 同一事件的所有规则中最多只能有一个RETURNING。错误!在
UPDATE规则中用NEW.<主键>定位旧行。 若该列被更新,将定位不到原行。sql-- 不推荐:主键被改名后匹配失败 CREATE RULE r AS ON UPDATE TO v DO INSTEAD UPDATE base SET sl_name = NEW.sl_name WHERE sl_name = NEW.sl_name; -- ❌错误!以为
RETURNING里的OLD.<列>会随查询里的OLD/NEW变化。 它永远指向额外范围表条目,行为固定,容易误解。不推荐:用
DO INSTEAD NOTHING做需要报错的校验。 数据被静默丢弃,调用方无从察觉。sql-- 不推荐:静默丢弃非法插入 CREATE RULE validate AS ON INSERT TO t WHERE NEW.amount < 0 DO INSTEAD NOTHING;不推荐:给「本身可自动更新」的简单视图再建规则。 自动改写通常计划更优。
sql-- 不推荐:简单单表视图画蛇添足 CREATE RULE v_simple_ins AS ON INSERT TO v_simple DO INSTEAD INSERT INTO t ...;不推荐:在
INSTEAD规则里写复杂过程逻辑。 规则是查询改写,不是过程代码;复杂逻辑应交给INSTEAD OF触发器。sql-- 不推荐:试图在规则里做多步条件判断与异常处理错误!制造循环规则。
v1的规则指向v2,v2的规则又指回v1,查询时报「infinite recursion」。不推荐:在有
INSERT/UPDATE规则的表/视图上使用ON CONFLICT。 官方明确不支持。sql-- 报错 INSERT INTO v_orders (...) VALUES (...) ON CONFLICT (order_id) DO NOTHING;不推荐:忽略规则与权限的组合效应。 规则以所有者权限运行,授予视图写权限即等于授予了规则动作所需的权限。
错误!指望
CREATE OR REPLACE RULE改变事件类型。 事件类型(ON INSERT/UPDATE/DELETE)不能通过OR REPLACE改变,需先DROP。
最佳实践
优先考虑
INSTEAD OF触发器。 官方建议:视图更新用触发器通常更容易、更不易出错。sqlCREATE TRIGGER v_orders_ins_trg INSTEAD OF INSERT ON v_orders FOR EACH ROW EXECUTE FUNCTION v_orders_insert();能用自动可更新视图就别建规则。 先检查视图是否本身可更新。
sqlSELECT is_updatable, is_insertable_into FROM information_schema.views WHERE table_name = 'v_orders';为每个要支持的操作建一条规则,并在文档中列出「支持哪些写操作」。
sqlCREATE RULE v_x_ins AS ON INSERT TO v_x DO INSTEAD ...; CREATE RULE v_x_upd AS ON UPDATE TO v_x DO INSTEAD ...; CREATE RULE v_x_del AS ON DELETE TO v_x DO INSTEAD ...;需要
RETURNING时保持规则单动作、无条件INSTEAD,并在RETURNING中直接引用结果关系列名。sqlDO INSTEAD INSERT INTO orders (...) VALUES (...) RETURNING orders.order_id, orders.amount;UPDATE规则用OLD.<稳定键>定位行,避免因主键被更新而定位失败。sqlUPDATE base SET sl_name = NEW.sl_name WHERE sl_name = OLD.sl_name;条件规则务必配无条件
DO INSTEAD NOTHING兜底,保证哑表永不被写。sqlCREATE RULE v_protect AS ON UPDATE TO v DO INSTEAD NOTHING;需明确拒绝操作时改用触发器报错,不要依赖静默
NOTHING。sqlRAISE EXCEPTION 'view % is read-only', TG_TABLE_NAME;用
pg_rules/pg_get_ruledef()定期审查规则清单,纳入上线检查。大批量、逻辑简单的改写优先用规则(一条额外命令);复杂逻辑用触发器(逐行回调)。
涉及安全隔离的视图加
security_barrier,并接受其性能代价。sqlCREATE VIEW v_secure WITH (security_barrier) AS SELECT ... ;改动规则前先禁用而非删除,便于回滚与排障。
sqlALTER TABLE v_orders DISABLE RULE v_orders_upd;
下一节
继续学习:规则与触发器对比