gorm本身不缓存查询结果,每次find、first都执行真实sql;所谓“绕过缓存”实为绕过外部缓存层或误判行为,需通过新变量、显式字段选择、避免事务快照及检查数据库级缓存来确保读取最新数据。

GORM 默认不带查询级缓存,所谓“绕过缓存”其实是绕过开发者自己加的缓存层或误判了 GORM 行为。GORM v2 本身不缓存查询结果(不像 Hibernate 的一级/二级缓存),它每次 Find、First 都发真实 SQL 到数据库。如果你发现数据没更新,大概率是缓存加在了外面(比如 Redis)、事务隔离级别导致读到旧快照,或者用了 Session 复用导致对象被复用。
为什么 db.First(&u, 1) 看起来像有缓存?
这不是 GORM 缓存,而是你重复传入了同一个结构体变量,且该结构体字段已被赋值:
- 若
u是已初始化的 struct(如var u User),GORM 会把查到的值写进u字段,但不会清空未返回字段——比如u.Name原来是 "old",DB 返回Name: "new",那没问题;但如果 DB 没返回Name(比如 SELECT id FROM users),u.Name还是 "old",看起来像“缓存” - 更隐蔽的是:你在事务中查了一次,又在同个 struct 上查第二次,而事务的隔离级别(如 REPEATABLE READ)让第二次读仍看到第一次的快照
- 别用
db.Session(&gorm.Session{NewDB: true})以外的方式复用*gorm.DB实例做多次查询——它不共享状态,但容易让人误以为“连接复用=结果复用”
如何确保每次都从数据库取最新行?
关键不是“绕缓存”,而是切断所有可能的旧值残留路径:
- 每次查询前用新变量:
var u User,而不是复用已有变量 - 显式指定要查的字段,避免漏字段导致旧值残留:
db.Select("id,name,updated_at").First(&u, 1) - 确认没在事务里——事务内默认读已提交快照,用
db.Session(&gorm.Session{SkipHooks: true}).First()也绕不开隔离级别 - 如果真用了外部缓存(如 Redis 封装的
GetUserByID),那就得手动cache.Delete("user:1")或跳过缓存层直连db
别踩 Session 和 InstanceID 的坑
GORM 的 Session 不是缓存控制开关,而是用于隔离配置(如 PrepareStmt、Context)。有人误以为 db.Session(&gorm.Session{NewDB: true}) 能“刷新连接”,其实它只是新建一个无状态的 *gorm.DB 实例,和缓存无关:
-
NewDB: true只影响是否继承父 DB 的回调(Callbacks)、命名策略等,不影响查询行为 -
InstanceID是内部标识符,不能用来控制缓存 - 真正影响“是否走新连接”的是底层
*sql.DB连接池——GORM 不控制这个,靠db.Config.PrepareStmt或事务Begin()触发新连接获取
最常被忽略的一点:你以为在查数据库,其实中间挡着一层服务(如 ProxySQL 的 query cache、MySQL 自身的 query_cache_type=ON)。GORM 发出的 SQL 如果完全一样,MySQL 5.7 及以前可能直接返回服务端缓存结果——这时得关掉 query_cache_type 或加 SELECT SQL_NO_CACHE ...,而不是折腾 GORM 配置。











