You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,执行时会出现非预期结果。
修复方案
  1. 废弃全局Dialect设置,改用绑定Dialect的独立Builder
    不要用全局的sqlf.Update,也不要在方法内调用全局SetDialect,直接用对应Dialect的方法创建Builder,从根源避免并发竞态:
    // 删掉方法第一行的 sqlf.SetDialect(sqlf.PostgreSQL)
    // 替换原来的 b := sqlf.Update(TABLE_NAME)
    b := sqlf.PostgreSQL.Update(TABLE_NAME)
    
  2. 修复时间字段的零值判断
    用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)
    }
    
  3. 增加空更新校验
    所有Set逻辑执行完后,判断是否有需要更新的字段,如果没有字段需要更新,直接查询原记录返回,避免生成非法SQL:
    // 所有Set/SetExpr调用完成后加判断
    // 注意:如果后续加Where条件,要提前判断Set的字段数,不要把Where的参数算进去
    // 可以自己加个计数器统计调用Set的次数,或者判断生成的SQL是否包含SET关键字
    if !strings.Contains(b.String(), "SET") {
        return d.GetParty(ctx, arg.ID)
    }
    
  4. 修复经纬度的零值判断
    把经纬度字段改成指针类型,区分“未传值”和“传入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)
    }
    
  5. 调试时打印完整参数
    执行SQL前同时打印SQL字符串和实际绑定的参数,确认WHERE子句对应的ID和预期一致:
    log.Printf("exec sql: %s, args: %v", b.String(), b.Args())
    

内容的提问来源于stack exchange,提问作者JonasLevin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 10:27:08