PostgreSQL查询报could not open relation with OID错误排查
问题现象
写了一个PL/pgSQL函数用来批量生成INSERT INTO ... VALUES脚本,只要取消代码里dvp.content行的注释,函数就会执行失败,抛出ERROR: could not open relation with OID ###错误,报错指向函数内部创建的临时表。已知content字段是jsonb类型,找不到排查方向。
相关代码
CREATE OR REPLACE FUNCTION export_docs_as_sql(doc_list uuid[], to_org_id uuid) RETURNS table(id integer, sql text) AS $$ BEGIN ... -- use a temp table to gather all INSERT statements CREATE TEMP TABLE IF NOT EXISTS doc_data_export( id serial PRIMARY KEY, sql text ); ... -- get doc_version_pages INSERT INTO doc_data_export(sql) SELECT 'INSERT INTO doc_version_pages(id, doc_version_id, persona_id, care_category_id, patient_group_id, title, content, created_at, updated_at, is_guide, is_root) VALUES (' || quote_literal(dvp.id::TEXT) || ', ' || quote_literal(dvp.doc_version_id::TEXT) || ', ' || CASE WHEN p.name IS NOT NULL THEN '(SELECT px.id FROM personas px WHERE px.org_id = ' || quote_literal(dv.id::TEXT) || ' AND px.name = ' || quote_literal(p.name) || '), ' ELSE 'NULL, ' END || CASE WHEN c.name IS NOT NULL THEN '(SELECT cx.id FROM care_categories cx WHERE cx.org_id = ' || quote_literal(to_org_id) || ' AND cx.name = ' || quote_literal(c.name) || '), ' ELSE 'NULL, ' END || CASE WHEN g.name IS NOT NULL THEN '(SELECT gx.id FROM patient_groups gx WHERE gx.org_id = ' || quote_literal(to_org_id) || ' AND gx.name = ' || quote_literal(g.name) || '), ' ELSE 'NULL, ' END || quote_literal(dvp.title::TEXT) || ', ' || --dvp.content || ', ' || quote_literal(dvp.created_at::TEXT) || ', ' || quote_literal(now()::timestamp) || ', ' || quote_literal(dvp.is_guide::TEXT) || ', ' || quote_literal(dvp.is_root::TEXT) || ');' FROM unnest(doc_list) l INNER JOIN doc_versions dv ON l = dv.doc_id INNER JOIN doc_version_pages dvp ON dv.id = dvp.doc_version_id LEFT JOIN personas p ON dvp.persona_id = p.id LEFT JOIN care_categories c ON dvp.care_category_id = c.id LEFT JOIN patient_groups g ON dvp.patient_group_id = g.id; ... -- output all inserts RETURN QUERY SELECT * FROM doc_data_export; -- drop temp table DROP TABLE doc_data_export; END; $$ LANGUAGE plpgsql;
故障原因
报错由两个问题共同触发:
- 临时表用法和PL/pgSQL的计划缓存机制冲突:函数末尾手动DROP临时表,建表时又加了
IF NOT EXISTS判断。PL/pgSQL会在函数第一次执行时缓存内部所有语句的执行计划,计划里会存下当时临时表的OID。函数执行完临时表被删掉,下次调用时新建的临时表会生成全新的OID,缓存里的旧OID已经对应不到任何表,就会抛找不到关系的错误。 - jsonb字段的拼接方式错误:直接裸拼
dvp.content没有做转义。jsonb字段内容较大时会走TOAST离线存储,读取TOAST值的时候会校验之前缓存的OID,直接触发报错;就算绕开OID问题,裸拼jsonb值既没有单引号包裹,也没转义内容里的特殊字符,生成的SQL本身也跑不起来。
修复方案
按优先级选一种即可:
- 最省事的方案:直接删掉临时表相关逻辑,从根源规避OID缓存问题。不需要建表、插数、删表,直接把生成INSERT语句的SELECT查询跟在
RETURN QUERY后面返回结果即可。 - 如果必须保留临时表逻辑,改两个地方:
- 建表时去掉
IF NOT EXISTS,加上ON COMMIT DROP参数,让临时表在当前事务结束后自动清理,不用手动在函数末尾写DROP语句。 - 把注释掉的
dvp.content拼接行改成quote_literal(dvp.content::text) || ', ' ||,和其他字段一样做字面量转义。
- 建表时去掉
所有要拼接进SQL字符串的值都必须经过
quote_literal处理,不管是什么类型,否则不仅容易出运行时错误,还存在SQL注入风险。
内容的提问来源于stack exchange,提问作者alphadogg
相关产品推荐
相关产品推荐

