Go 使用 goqu 查询 SQLite BLOB(16) UUID 为什么没有结果?
结论
SQLite 会按字节内容比较 BLOB。插入和查询时如果绑定的是同一组 16 字节,WHERE id = ? 就应能匹配。十六进制显示相同还不能证明查询参数与表中的 BLOB 完全一致,还需要检查生成的 SQL、参数类型和字节内容,以及查询连接所用的数据库文件和记录。
插入代码将 uuid.New().MarshalBinary() 的结果写入 id,查询代码则把 id.MarshalBinary() 的结果放进 goqu.Ex。需要确认两次 MarshalBinary() 使用的是同一个 UUID,且传给 SQLite 的值都是 []byte。
先检查 goqu 生成的 SQL 和参数
将数据集设为 prepared 模式,并打印 SQL、参数类型和字节内容:
sqlString, args, err := db.From("master").
Select("id", "username").
Where(goqu.Ex{"id": idBin}).
Prepared(true).
ToSQL()
if err != nil {
return err
}
fmt.Printf("SQL: %s\n", sqlString)
fmt.Printf("args: %#v\n", args)
for i, arg := range args {
fmt.Printf("arg[%d]: type=%T value=%x\n", i, arg, arg)
}
检查 SQL 是否包含占位符,参数列表是否包含查询用的 UUID 字节,以及参数类型是否为 []byte。如果 args 为空,或 SQL 已经内嵌了值,就不能认为 QueryRow(args...) 执行的是带 UUID 参数的查询。不同 goqu 方言生成的 SQL 可能不同,应以打印结果为准。
对比数据库中的实际 BLOB
先查看 SQLite 中保存的类型和字节:
SELECT typeof(id), length(id), hex(id)
FROM master;
typeof(id) 应为 blob,length(id) 应为 16。将目标记录的 hex(id) 与 Go 中 idBin 的 %x 输出逐字节比较。如果表中有多条记录,可以按预期的 UUID 十六进制值筛选,确认检查的是目标记录。
用 database/sql 做对照查询
暂时绕开 goqu,直接传入字节切片查询:
var idBytes []byte
var username string
err := conn.QueryRow(
`SELECT id, username FROM master WHERE id = ?`,
idBin,
).Scan(&idBytes, &username)
if err != nil {
return err
}
这里的 conn 应是实际连接到目标数据库的 *sql.DB 或 *sql.Tx。如果直接查询能找到记录、goqu 查询却找不到,检查 goqu 的方言、生成的 SQL 和 args。如果直接查询也找不到,则检查插入和查询是否使用了同一个 UUID 字节,以及连接是否指向同一个数据库文件。
注意事项
uuid.New() 每次都会生成新的 UUID。如果插入和查询时分别调用 uuid.New(),两组字节就会不同。应保存插入时的 UUID,并用同一个值查询。SQLite 的 BLOB(16) 声明不会自动把值转换成 UUID,也不会按 UUID 的文本形式比较。问题描述没有提供实际生成的 SQL、驱动名称,也没有说明插入和查询使用的 UUID 是否相同,因此仅凭现有片段无法确定未匹配的原因。
备注:内容仅供参考。
