MySQL唯一键冲突错误(Error 1062)可通过errors.As(err, &mysqlErr)提取原始驱动错误并判断mysqlErr.Number == 1062精准识别;不可依赖err.Error()字符串匹配,因消息格式易变;GORM默认封装会丢失该码,必须显式解包。

如何识别 MySQL 唯一键冲突错误(Error 1062)
MySQL 唯一键冲突会返回 mysql.MySQLError 类型的底层错误,其 Number 字段值为 1062。GORM 默认封装后丢失该码,必须用 errors.As 提取原始驱动错误才能精准判断。
常见误判是把 db.Create().Error != nil 直接当成业务失败——但这个 error 可能是连接超时、语法错误或字段为空,和唯一冲突无关。
- ✅ 正确做法:对每个写入操作的 error 执行
errors.As(err, &mysqlErr),再检查mysqlErr.Number == 1062 - ⚠️ 注意:
mysqlErr.Message包含冲突字段名(如email),可用于日志定位或用户提示 - ❌ 不要依赖
strings.Contains(err.Error(), "Duplicate")—— 消息格式随 MySQL 版本/语言变化,不可靠
PostgreSQL 唯一约束冲突怎么抓(SQLSTATE 23505)
PostgreSQL 将唯一约束冲突统一归为 SQLSTATE 错误码 23505(“unique_violation”)。它由 *pq.Error 携带,Code 字段长度固定为 5 位,前两位不是连接类的 08,而是 23。
与 MySQL 不同,PostgreSQL 的冲突可能来自主键、UNIQUE 约束或 EXCLUDE 约束,但错误码一致,无需区分来源。
- ✅ 推荐写法:
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code == "23505" - ?
pqErr.Detail通常含具体冲突值(如Key (email)=(a@b.com) already exists),可提取用于审计 - ⚠️ GORM v1.21+ 中若开启
Config.PrepareStmt = true,某些场景下错误类型可能被二次包装,建议始终用errors.As而非直接类型断言
如何统一处理多数据库的唯一冲突(不写 if-else 堆砌)
当服务同时支持 MySQL 和 PostgreSQL 时,硬编码两套判断逻辑会导致维护成本飙升。更可持续的方式是封装一个通用函数,内部按 error 类型分发。
关键点在于:不要试图抽象“冲突字段名”或“冲突值”,只做布尔判断——是否属于唯一性约束失败。业务层再根据需要查表确认是哪个字段重复。
- ✅ 示例函数签名:
func IsUniqueConstraintError(err error) bool - 内部先
errors.As(err, &mysqlErr)判1062;不匹配再试errors.As(err, &pqErr)判"23505";SQLite 可追加sqlite3.ErrConstraint判断 - ? 避免在事务中仅靠此函数就跳过错误——如果上游已开启事务,忽略唯一冲突可能导致后续语句仍执行,破坏一致性
为什么不能用 FirstOrInit 或 FirstOrCreate 替代冲突捕获
这两个方法看似能“避免冲突”,但本质是“先 SELECT 再 INSERT/UPDATE”,存在竞态条件(TOCTOU):两次查询之间,另一并发请求可能已插入相同记录,导致最终仍报唯一冲突。
它们适合低并发、最终一致性要求不高的场景(如缓存预热),但**不能替代对唯一冲突错误的显式捕获与响应**。
- ✅ 真实高并发写入(如注册、下单)必须走“尝试插入 → 捕获冲突 → 分支处理”路径
- ⚠️
FirstOrCreate返回的 error 是gorm.ErrInvalidDB或gorm.ErrRecordNotFound,不会暴露底层唯一冲突细节,无法做精细化反馈 - ? 若需幂等写入,优先考虑
OnConflict(GORM v2)或原生INSERT ... ON DUPLICATE KEY UPDATE/INSERT ... ON CONFLICT DO NOTHING,而非 ORM 层模拟


















