dsn必须加parsetime=true和timezone=utc,否则time.time字段静默错乱;连接池需显式配置maxopenconns等参数;事务须手动判错清理,automigrate在tidb上需规避自增主键。

直接用 gorm.Open 就能连,和连 MySQL 完全一样 —— 但 DSN 参数漏掉 parseTime=true 和 timezone=UTC,时间字段会静默错乱,不是报错,是查不到、插不对、判不准。
DSN 必须加 parseTime=true 和 timezone=UTC
不加这两个参数,time.Time 字段在 Scan 或 Insert 时不会 panic,但值会偏移几小时。TiDB 的 TSO 时间戳是 UTC 纳秒级,而 Go 驱动默认按本地时区解析 DATETIME/TIMESTAMP,导致:
-
SELECT created_at扫到的 struct 字段是本地时间,和数据库实际值对不上 - WHERE 条件用
created_at > time.Now()可能永远不命中 - 事务内基于时间判断版本(如乐观锁)直接失效
正确写法示例:
dsn := "root:123456@tcp(127.0.0.1:4000)/test?parseTime=true&timezone=UTC"
别写 loc=Asia%2FShanghai 或 loc=Local —— 这和 timezone=UTC 冲突,且 TiDB 不依赖客户端时区做计算。
连接池配置要显式控制,不能只靠 GORM 默认值
GORM 的 sql.DB 连接池默认 MaxOpenConns=0(不限制),MaxIdleConns=2,在高并发下极易打满 TiDB 的连接上限(默认 0,但生产通常设为 1000 左右),或因空闲连接过少频繁新建连接。
推荐配置(根据实际 QPS 调整):
-
db.SetMaxOpenConns(100):避免单实例打爆 TiDB 连接数 -
db.SetMaxIdleConns(20):保持一定空闲连接,减少握手开销 -
db.SetConnMaxLifetime(30 * time.Minute):防止连接长期空闲被 TiDB 或中间件(如 HAProxy)断开 -
db.SetConnMaxIdleTime(5 * time.Minute):及时清理长时间空闲连接
注意:这些必须在 gorm.Open 后立刻调用,GORM 不提供封装接口。
事务内不能用 defer tx.Rollback() 简单收尾
TiDB 的两阶段提交(2PC)Prepare 阶段可能卡住(比如跨 Region 协调慢),tx.Commit() 失败后若只靠 defer rollback,会导致:
- 连接未释放,连接池缓慢耗尽
- 未清理的悲观锁或未提交的 prewrite 状态残留
- 后续请求复用该连接时触发
invalid connection
正确模式是手动判错 + 显式清理:
tx := db.Begin()
if tx.Error != nil {
return tx.Error
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Create(&user).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Commit().Error; err != nil {
return err // Commit 失败时,Rollback 已在 defer 中执行
}
关键点:tx.Commit() 成功后,应确保 tx 不再被用于 Rollback;失败路径必须明确终止事务状态。
GORM 的 AutoMigrate 在 TiDB 上要小心主键和分片设计
TiDB 不支持自增主键的强顺序性,AUTO_INCREMENT 是全局分配的,但性能差、易成热点。GORM 默认用 id BIGINT PRIMARY KEY AUTO_INCREMENT,线上应主动替换:
- 用
SHARD_ROW_ID_BITS分片:建表时加SHARD_ROW_ID_BITS=4(生成 16 个分片 ID 段) - 或改用
BIGINT UNSIGNED+ 分布式 ID(如 Snowflake、TiDB 的tidb_nextval) - 禁用 GORM 的
CreatedAt/UpdatedAt自动赋值,改由数据库用NOW()或CURRENT_TIMESTAMP生成,避免客户端时钟漂移影响 MVCC 判断
另外,AutoMigrate 不会修改已有列的分片属性,首次建表后若需调整 SHARD_ROW_ID_BITS,只能 DROP 重建。
最常被忽略的是:时间参数和事务清理逻辑 —— 它们不出错,但会让数据在时间维度上“悄悄错位”,排查成本远高于加两个 DSN 参数或改一行 defer。











