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

pgx驱动操作PostgreSQL时PlaceholderFormat未替换$参数占位符问题

问题根因说明

你观察到的pgAdmin日志中$占位符未被替换属于PostgreSQL参数化查询的正常机制,参数是和SQL模板分开传输的,不会拼接到SQL字符串中,这是数据库防SQL注入的原生设计,不是问题。
你实际遇到的查询失败是代码逻辑错误导致的,具体问题和修正方案如下:

代码存在的问题

  • 执行SQL拿到rows结果后,没有调用rows.Next()就直接执行Scan方法,永远无法读取到数据,会直接返回sql.ErrNoRows,你误以为是占位符替换失败导致的查询无结果
  • 执行查询拿到sqlerr之后,错误地判断了之前JSON解析的err变量,没有正确处理数据库查询的错误
  • 用完rows对象后没有调用Close方法,会造成数据库连接泄漏
  • squirrel构造查询的写法冗余,不需要手动写$1占位符,squirrel配置sq.Dollar格式后会自动转换占位符

修正后的代码

func Signin(c *gin.Context) {
    // 解析请求参数
    creds := &Credentials{}
    err := json.NewDecoder(c.Request.Body).Decode(creds)
    if err != nil {
        c.Writer.WriteHeader(http.StatusBadRequest)
        return
    }
    // 构造查询语句,用?做占位符,squirrel会自动转成$1格式
    query, args, err := sq.
        Select("password").
        From("users").
        Where("username = ?", creds.Username).
        PlaceholderFormat(sq.Dollar).
        ToSql()
    if err != nil {
        c.Writer.WriteHeader(http.StatusInternalServerError)
        return
    }
    // 单条查询用QueryRow更合适
    storedCreds := &Credentials{}
    err = db.QueryRow(query, args...).Scan(&storedCreds.Password)
    if err != nil {
        if err == sql.ErrNoRows {
            c.Writer.WriteHeader(http.StatusUnauthorized)
            return
        }
        c.Writer.WriteHeader(http.StatusInternalServerError)
        return
    }
    // 校验密码
    if err = bcrypt.CompareHashAndPassword([]byte(storedCreds.Password), []byte(creds.Password)); err != nil {
        c.Writer.WriteHeader(http.StatusUnauthorized)
        return
    }
    c.Writer.WriteHeader(http.StatusOK)
}

额外优化建议

  • 可以直接用pgx的原生驱动,不需要通过database/sql的stdlib封装,性能更好
  • 密码校验逻辑建议加常量时间比对的处理,避免计时攻击
  • 数据库操作建议加超时控制,避免慢查询卡住请求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.17 17:45:07