sql.NullString用于区分数据库NULL与空字符串"",因其含Valid字段标识值是否有效;直接使用string无法区分二者,且需注意Scan、JSON序列化及不同驱动的兼容性问题。

sql.NullString 为什么不是直接用 string?
因为数据库字段允许 NULL,而 Go 的 string 类型是零值语义(空字符串 ""),无法区分 “存了空字符串” 和 “根本没存值”。sql.NullString 就是为解决这个歧义设计的:它带一个 Valid 布尔字段,只有 Valid == true 时,String 字段才可信。
常见错误现象:
— 查询到 NULL 字段后,直接取 sql.NullString.String 得到空字符串,误以为数据库里存的是 "";
— 插入时把未初始化的 sql.NullString{} 写进 DB,结果写入的是 NULL 而非预期的空字符串。
- 读取时必须先判断
.Valid,再决定如何处理:if ns.Valid { name = ns.String } else { name = "unknown" } - 插入或更新前,按业务逻辑显式设置
Valid:ns := sql.NullString{String: "alice", Valid: true} // 存 "alice"<br>ns := sql.NullString{Valid: false} // 存 NULL - 别依赖零值 —
sql.NullString{}的Valid是false,String是"",这是有意设计,不是 bug
Scan 时遇到 *string 和 sql.NullString 混用的坑
很多 ORM 或手写 Scan 会把数据库 NULL 映射成 *string(nil 指针表示 NULL),但标准库 database/sql 的 Scan 方法对 *string 的行为是:NULL → nil 指针,非 NULL → 指向新分配的字符串。这和 sql.NullString 的语义不一致,容易引发 panic 或逻辑错乱。
- 如果结构体字段是
*string,扫描 NULL 字段后该字段为nil,直接解引用会 panic:fmt.Println(*s) // panic: runtime error: invalid memory address
-
sql.NullString的Scan方法能安全处理 NULL,且可重复调用;*string的Scan在目标为 nil 时会分配内存,但若多次 Scan 到同一变量,可能覆盖或泄漏 - 统一用
sql.NullString更可靠 — 它是标准库明确支持的 NULL-aware 类型,兼容所有 driver(如mysql、postgres、sqlite)
JSON 序列化时 sql.NullString 的默认行为很危险
原生 json.Marshal 对 sql.NullString 的输出是结构体形式:{"String":"foo","Valid":true},而不是你想要的字符串或 null。API 返回这种格式前端会解析失败,ORM 保存也常出错。
立即学习“go语言免费学习笔记(深入)”;
- 必须自定义 JSON 方法,否则暴露内部结构:
func (ns sql.NullString) MarshalJSON() ([]byte, error) { if !ns.Valid { return []byte("null"), nil } return json.Marshal(ns.String) } - 反序列化也要重写
UnmarshalJSON,否则null输入不会自动设Valid=false - 注意:有些第三方库(如
sqlx)提供NullString的 JSON 友好变体,但标准sql.NullString没有 — 别假设它“开箱即用”
驱动层对 sql.NullString 的实际支持差异
虽然文档说 sql.NullString 是标准方式,但不同 driver 实现程度不同。比如 pgx(PostgreSQL)推荐用原生 pgtype.Text,而 mysql 驱动对 sql.NullString 支持稳定,sqlite 则在某些版本中对 Scan 的 NULL 处理有边界 case。
- MySQL 驱动(
go-sql-driver/mysql):完全兼容,Scan和Value方法都按预期工作 - PostgreSQL 的
lib/pq已归档,pgx默认不接受sql.NullString— 它要求你传*string或自定义类型;强行传sql.NullString可能触发driver.Valuer错误 - SQLite(
mattn/go-sqlite3):对 NULL 支持良好,但要注意TEXT字段在某些旧版本中会被映射为[]byte,导致 Scan 失败
最稳妥的做法:查清你用的 driver 文档,确认它是否真正实现 driver.Valuer 和 sql.Scanner 接口对 sql.NullString 的支持;不要只看示例代码里写了 sql.NullString 就认为没问题。



















