自定义类型字段能否参与where查询取决于其数据库存储格式和gorm生成sql的能力:uuid.uuid可直接查询;json.rawmessage等需用数据库json函数;加密字段只能go层过滤。

GORM 本身不支持对自定义类型字段(如 json.RawMessage、uuid.UUID、自定义 Scanner/Valuer 类型)直接做分页筛选,必须先明确该字段在数据库中实际存储的格式和可查询方式,再决定是走 SQL 表达式、JSON 函数,还是预处理转换。
自定义类型字段能否参与 WHERE 查询?先看底层存储
自定义类型是否能被用于 WHERE 筛选,取决于它映射到数据库列的类型和 GORM 是否能生成合法 SQL:
- 若字段是
uuid.UUID且数据库列为UUID或CHAR(36),GORM v2 默认支持Where("id = ?", uuidObj)—— 只要Valuer返回字符串,就能正常比较 - 若字段是
json.RawMessage或结构体 + 自定义Scanner/Valuer,但底层存为TEXT或JSON类型,则不能直接Where("meta = ?", val),除非数据库支持 JSON 比较(如 PostgreSQL 的@>、MySQL 5.7+ 的JSON_CONTAINS) - 若字段是加密后存的
[]byte(如 AES 加密的邮箱),则无法在数据库层筛选,只能查出后 Go 层过滤 —— 这种情况分页必须全量加载再截断,不可行
用 Raw SQL 或 Scopes 处理 JSON 字段分页筛选
对 PostgreSQL / MySQL 8.0+ 的 JSON 字段,必须绕过 GORM 的链式 Where,改用 Where("json_col @> ?", jsonbVal) 或 Where("JSON_CONTAINS(json_col, ?)", jsonStr)。GORM 不会自动识别 json.RawMessage 并转义成 JSON 函数调用。
- PostgreSQL 示例:
db.Where("metadata @> ?", `{"status": "active"}`::jsonb).Offset(0).Limit(10).Find(&items) - MySQL 示例:
db.Where("JSON_CONTAINS(metadata, ?)", `"status":"active"`).Order("id ASC").Offset(0).Limit(10).Find(&items) - 别写
db.Where("metadata.status = ?", "active")—— 这会被当普通字符串匹配,不是 JSON 路径查询 - 如果用
Scopes封装,记得把 JSON 条件写进func(db *gorm.DB) *gorm.DB里,而不是依赖结构体 tag
分页时 Count 查询容易漏掉 JSON 条件
对含 JSON 筛选的分页,Count 必须和主查询条件完全一致,否则总数不准。但 GORM 的 Count 会继承前面所有 Where,包括原始结构体字段条件 —— 而 JSON 条件往往在 Raw 或 Where 字符串里,容易遗漏。
- 错误写法:
db.Model(&Item{}).Count(&total)→ 完全没带 JSON 条件,总数是全表 - 正确做法:用新 DB 实例,显式复现全部条件:
db.Session(&gorm.Session{NewDB: true}).Model(&Item{}).Where("metadata @> ?", `{"type": "report"}`).Count(&total) - 更稳妥:手写子查询,避免链式污染:
db.Raw("SELECT COUNT(*) FROM items WHERE metadata @> ?", `{"type": "report"}`).Scan(&total) - 注意:PostgreSQL 的
@>和 MySQL 的JSON_CONTAINS都支持索引加速,但需提前建 GIN 或虚拟列索引,否则Count也会慢
自定义类型字段排序与分页稳定性
用自定义类型字段(如 CreatedAt time.Time 被包装成 CustomTime)做 Order 时,只要底层类型和数据库列兼容,GORM 就能拼出正确 SQL;但如果该字段是 JSON 内嵌值(如 metadata->>'updated_at'),就不能直接 Order("metadata.updated_at")。
- PostgreSQL 正确排序:
db.Order("metadata->>'updated_at' DESC").Offset(0).Limit(10) - MySQL 正确排序:
db.Order("JSON_UNQUOTE(JSON_EXTRACT(metadata, '$.updated_at')) DESC") - 别依赖 GORM 自动推导:它不会把结构体字段名映射成 JSON 路径表达式
- 这类排序字段必须加函数索引(如 PostgreSQL 的
CREATE INDEX ON items ((metadata->>'updated_at'))),否则分页性能随偏移增大而断崖下跌
最易被忽略的是:JSON 字段的分页筛选和排序,本质上已脱离 GORM 的“模型抽象”层,进入原生 SQL 范畴。你写的每个 Where 和 Order 都得按目标数据库语法校验,不能只看 Go 结构体定义。











