DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS

Go MySQL 写入 nil RawMessage 报 Error 3140,检查参数插值

go-sql-driver/mysql 1.10.0 开启 interpolateParams 时,带类型的 nil RawMessage 会被写成空字符串。1.10.1 已补上 NULL 分支;旧环境可在参数边界明确传 nil,但不能把非空的无效 JSON 一起吞掉。

MySQL 的 JSON 列允许 SQL NULL,Go 代码也传了一个 nil json.RawMessage,执行 INSERT 却报:

Error 3140 (22032): Invalid JSON text: "The document is empty."

若使用 github.com/go-sql-driver/mysql v1.10.0,并在连接配置中开启 interpolateParams=true,要检查驱动的参数插值分支。这个组合会把带类型的 nil RawMessage 拼成 SQL 空字符串 '',MySQL 随后把它当 JSON 文本解析,才会出现“文档为空”。这并不表示 JSON 列不能保存 SQL NULL。

nil 在转换成 SQL 文本时丢了含义

下面的 payload 是 nil 切片,但传入接受 any 的参数列表后,接口中仍带有 json.RawMessage 类型:

var payload json.RawMessage
_, err := db.ExecContext(ctx,
    "INSERT INTO events (id, metadata) VALUES (?, ?)",
    id, payload,
)

片段假定 db、ctx 和 id 来自应用,目标列 metadata 是允许 NULL 的 JSON 列。这里的接口值不等于无类型的 nil;这一点与带类型 nil 的接口判断相同,但还需要继续检查驱动把它写成了什么。

1.10.0 的转换器保留 RawMessage 类型。插值器先判断 arg == nil,随后进入专门的 json.RawMessage 分支;该分支没有 nil 判断,直接转义并加引号。相邻的普通 []byte 分支已有 NULL 处理,因此两种看似相同的空切片会走出不同结果。

1.10.1 补上了专门的 NULL 分支

正式 1.10.1 的 RawMessage 分支会先判断 v == nil,为真时直接生成 SQL NULL,不再调用字符串转义。使用该版本线的项目可采用 1.10.1 或已确认包含同项修正的兼容版本,并核对最终构建使用的模块版本。

1.10.1 要求 Go 1.24 或更新工具链;这里的故障组合使用 Go 1.26.4。旧项目应同时核对工具链支持范围,不能只改驱动版本。

这项改动针对 nil RawMessage。长度为零但非 nil 的 json.RawMessage{} 仍不是有效 JSON 文档,不应因为升级后某条空值输入成功,就认定所有空字节都能入库。

暂不能升级时,可以在应用明确负责空值语义的绑定边界处理:

func jsonParameter(raw json.RawMessage) any {
    if raw == nil {
        return nil
    }
    return raw
}

_, err := db.ExecContext(ctx,
    "INSERT INTO events (id, metadata) VALUES (?, ?)",
    id, jsonParameter(payload),
)

函数应与 encoding/json 的导入放在同一 Go 文件中;后半段仍是已有请求处理函数里的数据库调用。这里只把真正的 nil 转成无类型 nil,非 nil 内容继续交给 JSON 校验,不能写成“只要长度为零就返回 nil”,否则会把无效输入悄悄变成缺失值。

关闭 interpolateParams 也会改变执行路径,使带参数的调用走服务端预处理,而不是这段客户端插值。若采用这个分支,按原连接配置重新构造连接池,并评估额外往返和预处理行为;不要直接删掉其他 DSN 参数。更改池配置不会自动改变已经建立的旧连接。

SQL NULL 与 JSON null 要分别验收

SQL NULL 表示列值缺失;字节内容为 null 的 RawMessage 则是一个有效 JSON 值。二者在 JSON 提取、比较和业务默认值处理中不一定等价,不能用 json.RawMessage("null") 作为所有 nil 的通用替换。

回查 nil、JSON null、一个正常对象,以及非 nil 空字节四种输入。预期是 nil 按业务约定保存 SQL NULL,正常 JSON 保留内容,无效空文档继续失败。若列为 NOT NULL,应遵循原来的必填约定,而不是扩大允许空值的范围。写入失败后的重试也要沿用原有主键或幂等规则,避免把修正参数绑定变成重复插入。