根本原因是规则匹配失败而非规则错误,因重写发生在parse后、plan前,仅作用于原始语法树,不感知视图展开或优化器改写。
为什么 pg_rewrite 或查询重写规则没触发
根本原因通常是规则匹配失败,而不是规则本身写错了。postgresql 的查询重写在 parse 阶段后、plan 阶段前执行,只作用于原始语法树,不感知视图展开、cte 提取或优化器改写后的形式。
常见错误现象:SELECT * FROM old_table 没被重写成 SELECT * FROM new_table;或者重写后报错 relation "new_table" does not exist(说明规则生效了,但目标对象不可见)。
- 确保规则定义在正确 schema 下,且当前
search_path包含该 schema - 规则只能重写
SELECT/INSERT/UPDATE/DELETE,不能重写WITH子句内部的查询 - 如果原查询带
FOR UPDATE或行级锁,重写目标必须支持相同锁定语义,否则静默跳过 - 用
\dR+查看规则是否启用(Enabled列为O表示 on),禁用状态不会触发
pg_statistic 和统计信息对重写有影响吗
没有直接影响。查询重写发生在优化器介入之前,完全不读取 pg_statistic,也不依赖任何统计信息或直方图。
但容易混淆的点在于:你可能误以为「重写后查询变慢」是重写问题,实际是重写目标表(比如新分区表)缺少统计信息,导致后续执行计划劣化。这时 ANALYZE 有用,但跟重写机制无关。
- 重写是否生效,看
EXPLAIN输出里是否出现目标表名,而不是看执行时间 - 若重写后
EXPLAIN显示扫描了new_table却很慢,先ANALYZE new_table,再对比EXPLAIN (ANALYZE) - 不要在重写规则里引用尚未
ANALYZE过的表——不是语法错误,但会误导你怀疑重写失效
外键、NOT NULL、CHECK 约束会阻止重写生效吗
不会。约束检查发生在重写之后的执行阶段,不影响重写规则是否被应用。
真正会中断流程的是:重写生成的新查询本身语法/语义不合法。比如原查询 SELECT a,b FROM t 被重写为 SELECT x,y,z FROM u,而列数不匹配或类型无法隐式转换,就会在重写后校验时报错 column "z" does not exist,看起来像“没生效”,其实是“生效了但挂了”。
- 用
EXPLAIN (VERBOSE)看重写结果:输出中Output:和Relation Name:会暴露最终访问的表和列 - 如果目标表有
NOT NULL列,而重写后的INSERT没提供值,错误发生在执行期,不是重写期 - 跨 schema 重写时,注意目标表的权限:重写不检查权限,但执行时会报
permission denied for table new_table
如何确认重写是否真正在生产中起作用
不能只靠日志或 EXPLAIN 一次就下结论。真实负载下,连接、事务隔离级别、prepared statement 缓存都会干扰判断。
最可靠的方式是结合 pg_stat_statements 和手工注入标记。比如在重写规则里把 SELECT 改成 SELECT 'rewritten'::text AS marker, *,然后查 pg_stat_statements 里是否出现带该 marker 的 queryid。
- 临时开启
log_statement = 'mod'并过滤关键词rewritten,比盲猜更直接 - 避免用
PREPARE+EXECUTE测试:预编译语句在 parse 阶段完成,重写只对原始文本生效,预编译后无法再触发 - 如果用了连接池(如 pgbouncer),确认它没开启
transaction池化模式——该模式下 parse 可能被复用,导致重写失效
重写机制本身很简单,复杂点全在边界:schema 可见性、权限继承链、prepare 生命周期、以及你误把执行期错误当成重写失败。










