PostgreSQL12触发器报错:参数类型与预编译计划类型不匹配
PostgreSQL行级触发器类型不匹配问题排查与修复
问题场景
对接PostgreSQL 12数据库开发服务时,为clients表创建了覆盖INSERT/UPDATE/DELETE操作的行级触发器,用于提取受影响行数据生成payload,通过pg_notify向db_event通道发送数据变更通知,初始触发器实现代码如下:
CREATE OR REPLACE FUNCTION notify_trigger() RETURNS trigger AS $trigger$ DECLARE rec RECORD; dat RECORD; payload TEXT; BEGIN -- 根据操作类型设置对应行记录 CASE TG_OP WHEN 'UPDATE' THEN rec := NEW; dat := OLD; WHEN 'INSERT' THEN rec := NEW; WHEN 'DELETE' THEN rec := OLD; ELSE RAISE EXCEPTION 'Unknown TG_OP: "%". Should not occur!', TG_OP; END CASE; -- 构造通知payload payload := json_build_object('timestamp',CURRENT_TIMESTAMP,'action',LOWER(TG_OP),'db_schema',TG_TABLE_SCHEMA,'table',TG_TABLE_NAME,'record',row_to_json(rec), 'old',row_to_json(dat)); -- 发送通道通知 PERFORM pg_notify('db_event', payload); RETURN rec; END; $trigger$ LANGUAGE plpgsql; CREATE TRIGGER clients_notify AFTER INSERT OR UPDATE OR DELETE ON clients FOR EACH ROW EXECUTE PROCEDURE notify_trigger();
问题现象
经psql直接验证,该触发器大部分场景可正常运行,但服务API层执行clients表UPDATE操作时,会抛出如下错误:
DBProgrammingError: type of parameter 15 (clients) does not match that when preparing the plan (record) CONTEXT: PL/pgSQL function notify_trigger() line 22 at assignment
触发错误的UPDATE调用代码如下:
db.executeCommands( [ """ UPDATE clients SET pos = %(pos)s, cos = %(cos)s, rpl = %(rpl)s, obv = %(obv)s, oav = %(oav)s, wth = %(wth)s WHERE id = %(id)s """ ], ( { "id": client["id"], "pos": client["pos"], "cos": client["cos"], "rpl": client["rpl"], "obv": client["obv"], "oav": client["oav"], "wth": client["wth"], }, ), )
clients表结构定义如下:
CREATE TABLE clients ( id uuid DEFAULT gen_random_uuid() PRIMARY KEY, datetime timestamp DEFAULT now(), first VARCHAR(16) NOT NULL, last VARCHAR(16) NOT NULL, alias VARCHAR(32) NOT NULL, pos integer DEFAULT 0, cos numeric(1000,4) DEFAULT 0.0, rpl numeric(1000,4) DEFAULT 0.0, obv integer DEFAULT 0, oav integer DEFAULT 0, wth numeric(1000,4) DEFAULT 0.0 );
该UPDATE逻辑在服务中存在两处调用:
- API层调用:执行时抛出上述类型不匹配错误
- 独立工作线程调用:可正常执行成功
此前尝试参考同类问题方案,将payload强转为text传入pg_notify(即PERFORM pg_notify('db_event', payload::text);),但问题未解决。
根因与修复方案
问题根源
触发器函数中使用通用RECORD类型变量存储NEW/OLD行值,与实际的clients表复合行类型存在泛型匹配冲突。PL/pgSQL会缓存函数执行计划,当预编译计划中记录的变量类型为泛型record,实际传入值为clients复合类型时,就会触发类型校验失败报错,这也是不同执行路径(预编译参数执行/普通执行)下表现不一致的原因。
修复方案
测试coalesce类类型兼容方案未生效,最终通过将触发器函数内的RECORD类型变量修改为明确的clients表行类型解决问题。虽然为单表编写独立触发器处理函数稍显冗余,但可稳定运行。
修复后的核心代码变更如下(-标记为移除行,+标记为新增行):
CREATE OR REPLACE FUNCTION notify_trigger() RETURNS trigger AS $trigger$ DECLARE - rec RECORD; - dat RECORD; + rec clients; + dat clients; payload TEXT; BEGIN -- 根据操作类型设置对应行记录 CASE TG_OP WHEN 'UPDATE' THEN rec := NEW; dat := OLD; WHEN 'INSERT' THEN rec := NEW; WHEN 'DELETE' THEN rec := OLD; ELSE RAISE EXCEPTION 'Unknown TG_OP: "%". Should not occur!', TG_OP; END CASE; -- 构造通知payload payload := json_build_object('timestamp',CURRENT_TIMESTAMP,'action',LOWER(TG_OP),'db_schema',TG_TABLE_SCHEMA,'table',TG_TABLE_NAME,'record',row_to_json(rec), 'old',row_to_json(dat)); -- 发送通道通知 PERFORM pg_notify('db_event', payload); RETURN rec; END; $trigger$ LANGUAGE plpgsql; CREATE TRIGGER clients_notify AFTER INSERT OR UPDATE OR DELETE ON clients FOR EACH ROW EXECUTE PROCEDURE notify_trigger();
内容的提问来源于stack exchange,提问作者jateeq
相关产品推荐
相关产品推荐

