Golang pgx 带RETURNING的Update语句Scan报no rows in result set错误
问题原因
返回no rows in result set的核心原因只有一个:程序执行UPDATE语句时,WHERE条件没有匹配到任何行,PostgreSQL没有返回RETURNING结果。你在SQL客户端执行能正常返回行,本质是客户端执行时用的参数和程序实际绑定的参数不一致。结合代码看,有3个直接诱因:
- 全局Dialect配置的并发竞态问题
你在方法内部调用sqlf.SetDialect(sqlf.PostgreSQL)修改的是全局配置,如果服务是并发处理请求(比如Web服务),多个goroutine同时修改全局Dialect会产生竞态,会导致SQL占位符生成顺序、参数绑定顺序错乱,最终WHERE子句拿到的参数不是你传入的ID,自然匹配不到行。 - 零值判断逻辑错误
你对时间字段的判断逻辑完全失效:Go中time.Time类型的零值是公元1年(0001-01-01),不是1970年,只有time.Unix(0,0)才对应1970年,你写的if !(startYear == 1970)判断几乎永远为真,会把未初始化的零值时间写入数据库;同时判断endYear时错误读取了arg.StartDate.Year(),属于笔误,会导致EndDate更新逻辑完全不符合预期。
另外经纬度的零值判断也有漏洞:经纬度(0,0)是真实存在的坐标点(几内亚湾),用arg.Lat !=0 && arg.Long !=0判断是否更新坐标,会导致用户传入(0,0)坐标时无法更新。 - 空更新场景生成非法SQL
如果传入的UpdatePartyParams所有字段都命中你的零值判断(即没有调用任何Set/SetExpr方法),sqlf生成的UPDATE语句会缺失SET子句,变成UPDATE parties WHERE id = $1 RETURNING ...这类非法SQL,执行时会出现非预期结果。
修复方案
- 废弃全局Dialect设置,改用绑定Dialect的独立Builder
不要用全局的sqlf.Update,也不要在方法内调用全局SetDialect,直接用对应Dialect的方法创建Builder,从根源避免并发竞态:// 删掉方法第一行的 sqlf.SetDialect(sqlf.PostgreSQL) // 替换原来的 b := sqlf.Update(TABLE_NAME) b := sqlf.PostgreSQL.Update(TABLE_NAME) - 修复时间字段的零值判断
用IsZero()方法判断时间是否为零值,同时修正EndDate的笔误:// 替换原来的startYear判断逻辑 if !arg.StartDate.IsZero() { b = b.Set("start_date", arg.StartDate) } // 替换原来的endYear判断逻辑,注意读取的是arg.EndDate if !arg.EndDate.IsZero() { b = b.Set("end_date", arg.EndDate) } - 增加空更新校验
所有Set逻辑执行完后,判断是否有需要更新的字段,如果没有字段需要更新,直接查询原记录返回,避免生成非法SQL:// 所有Set/SetExpr调用完成后加判断 // 注意:如果后续加Where条件,要提前判断Set的字段数,不要把Where的参数算进去 // 可以自己加个计数器统计调用Set的次数,或者判断生成的SQL是否包含SET关键字 if !strings.Contains(b.String(), "SET") { return d.GetParty(ctx, arg.ID) } - 修复经纬度的零值判断
把经纬度字段改成指针类型,区分“未传值”和“传入0值”的场景:type UpdatePartyParams struct { // 其余字段不变 Lat *float32 Long *float32 } // 对应判断逻辑改成 if arg.Lat != nil && arg.Long != nil { b = b.SetExpr("location", "ST_SetSRID(ST_MakePoint(?, ?), 4326)", *arg.Long, *arg.Lat) } - 调试时打印完整参数
执行SQL前同时打印SQL字符串和实际绑定的参数,确认WHERE子句对应的ID和预期一致:log.Printf("exec sql: %s, args: %v", b.String(), b.Args())
内容的提问来源于stack exchange,提问作者JonasLevin
相关产品推荐
相关产品推荐

