Golang操作PostgreSQL执行DO块报参数绑定数量不匹配如何解决
问题成因
PostgreSQL的DO关键字包裹的是匿名PL/pgSQL代码块,不属于支持预编译参数绑定的语句类型。Go的pq驱动在解析待执行SQL、识别参数占位符时,不会穿透到DO $$ ... $$的代码块内部,因此会判定这条SQL不需要传入任何绑定参数。但实际调用Exec方法时你传入了1个参数,就触发了「绑定消息提供了1个参数,但预编译语句要求0个参数」的报错。
另外你手动写IF EXISTS判断再分支执行UPDATE/INSERT的逻辑本身存在并发安全问题:两个并发请求同时判断到目标日期不存在时,会同时触发INSERT,大概率导致唯一键冲突报错。
修复方案
优先使用PostgreSQL原生的INSERT ... ON CONFLICT语法实现upsert(不存在则插入、存在则更新)逻辑,这是性能最好、并发安全性最高的官方推荐方案,完全不需要使用匿名代码块:
newDate := "2022-06-22" // 注意:需要提前给systemtable表的date字段创建唯一约束,否则冲突判定逻辑无法生效 query := ` INSERT INTO systemtable (date, is_latest) VALUES ($1::date, TRUE) ON CONFLICT (date) DO UPDATE SET is_latest = EXCLUDED.is_latest; ` if _, err := txi.Exec(query, newDate); err != nil { return err }
如果你的业务逻辑后续需要扩展更复杂的PL/pgSQL判断、无法用单条upsert语句实现,不要直接在DO块内使用$1占位符,可以通过会话自定义变量传递参数,避免参数绑定不识别的问题:
newDate := "2022-06-22" // 先将参数写入会话级配置变量 if _, err := txi.Exec(`SELECT set_config('app.target_date', $1, false)`, newDate); err != nil { return err } // 匿名块内部直接读取会话变量,不需要外部传参 query := ` DO $$ DECLARE new_date date := current_setting('app.target_date')::date; BEGIN IF EXISTS (SELECT * FROM systemtable WHERE date = new_date) THEN UPDATE systemtable SET is_latest = TRUE WHERE date = new_date; ELSE INSERT INTO systemtable (date, is_latest) VALUES (new_date, TRUE); END IF; END$$;` if _, err := txi.Exec(query); err != nil { return err }
注意:第二种传参方式要确保同一个数据库连接不会被多个请求并发复用,否则会出现参数串扰问题,你当前在事务(代码中txi为事务对象)场景下使用是安全的,因为事务内连接是独占的。
内容的提问来源于stack exchange,提问作者DietPepsi
相关产品推荐
相关产品推荐

