ormcache 边界

Odoo 的 ormcache 为什么会让你觉得“明明改了却没生效”:缓存键、事务命中与 clear_caches 边界讲透

你改了配置、权限、菜单或模板,结果页面还是旧的,很多时候不是你没改对,而是碰到了 Odoo 的 ormcache。本文结合官方源码讲清 ormcache 的键、命中方式,以及 clear_caches 到底该在什么时候用。

Odoo 开发
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先说结论

很多开发者第一次真正意识到 Odoo 有强缓存,不是在读源码时,而是在调试时。

典型场景是:

  • 菜单权限改了,前台还是旧结果
  • 配置参数改了,代码读出来还是旧值
  • 视图或模板动了,表现像没刷新

这类现象背后,经常不是数据库没写进去,而是:

你命中了 ormcache,但还没触发正确的缓存失效。

所以 ormcache 不是“性能小技巧”,而是 Odoo 核心框架里大量使用的结果缓存机制。


官方源码怎么定义 ormcache

/home/ubuntu/odoo-temp/odoo/tools/cache.py 里,ormcache 的注释写得非常直接:

  • 它是一个 给 model method 用的 LRU cache decorator
  • 你传给装饰器的字符串表达式会参与构造缓存 key
  • 返回值不应该是 recordset,因为底层 cursor 迟早会关闭

这三句话已经足够说明它的边界:

1. 它缓存的是“方法结果”,不是单纯字段值

比如:

  • 某个 model 的访问域
  • 某个参数读取结果
  • 某个模板、菜单、资产包计算结果

2. 它的 key 不是随便生成的

源码里会把:

  • self._name
  • 方法对象本身
  • 你在装饰器里写的参数表达式

一起拼成 key。

所以你少放一个影响结果的上下文因素,就可能把本不该复用的结果缓存到一起。

3. 它不是用来缓存 recordset 的

这是很关键的开发提醒。

因为 recordset 绑定 env、cursor、权限和缓存语境,拿它做跨调用缓存非常危险。


为什么它会让人产生“改了没生效”的错觉

因为 ormcache 的目标就是:

同一个 key,后面别再重复算。

源码里的 lookup() 很清楚:

  • 先按 key 去 LRU 里取
  • 命中就直接返回
  • miss 才真正执行方法并把结果写进缓存

这意味着,只要 key 没变、缓存没清:

  • 你就算底层数据改了
  • 结果也可能继续返回旧值

所以很多“没生效”本质上是:

  • 业务数据已变
  • 缓存语义还认为结果可复用

事务命中统计为什么也值得关注

lookup() 里还有一个很容易被忽略的细节:

  • Odoo 还会记录当前 transaction 里这个 key 是不是第一次 lookup
  • 会统计 hit / miss / err 以及事务内第一次命中的情况

这说明 Odoo 不是把缓存当黑盒,而是把它视作框架级性能结构的一部分。

换句话说,ormcache 不只是“有没有缓存”,而是:

  • 哪些方法在高频命中
  • 哪些缓存桶在工作
  • 某个事务里是不是不断重复请求同一结果

这也是为什么服务层还提供了日志统计入口。


Odoo 哪些地方真的在广泛用 ormcache

扫官方源码会发现,使用范围非常广:

  • ir.config_parameter
  • ir.model
  • ir.actions
  • ir.ui.menu
  • ir.ui.view
  • ir.http
  • res.users
  • ir.rule
  • QWeb / assets / routing 等

也就是说,它不是某个边角模块的技巧,而是:

Odoo 基础设施级别的性能机制。

这也解释了为什么很多“权限、菜单、模板、配置改了怎么不立刻反映”的问题,最终都会走到缓存失效边界上。


clear_caches() 真正该怎么理解

很多人把 clear_caches() 理解成“万能刷新按钮”。

这不够准确。

更准确的理解是:

当某类框架级缓存依赖的数据已经变化,而缓存 key 本身不足以自然区分新旧结果时,你需要主动失效缓存。

典型场景包括:

  • 你改了 ir.config_parameter 这类被缓存的方法依赖源
  • 你改了菜单、规则、模型元数据、模板结构
  • 你在自定义代码里对 @ormcache 方法的依赖数据做了写入

此时如果没有同步失效,旧结果就会继续命中。


最容易踩的几个坑

坑 1:缓存 key 漏了上下文因素

比如结果其实跟这些东西有关:

  • 用户
  • 语言
  • 公司
  • context 某个键

但你的装饰器 key 里没带进去。

结果就是:

  • A 用户算出来的结果
  • 被 B 用户直接复用

这不是缓存太强,而是 key 设计不完整。

坑 2:把 recordset 或带游标语义的对象塞进缓存

源码已经明确提醒不要这么做。

因为缓存生命周期常常长于当前 cursor / env 语境。

坑 3:数据变了,却忘了失效

这是最常见的“明明数据库已经变了,页面还是旧的”。

坑 4:一遇到缓存问题就全局乱清

clear_caches() 有时必要,但如果把它当日常兜底:

  • 你会失去缓存带来的性能收益
  • 还会把真正的 key 设计问题掩盖掉

开发时更健康的判断顺序

我更建议你按这个顺序思考:

第一步:这段结果真的值得缓存吗?

如果计算不重、调用不频繁、依赖又很动态,未必适合上 ormcache

第二步:影响结果的因素是否都进入 key?

尤其是:

  • uid
  • su
  • lang
  • company
  • context 中的关键值

第三步:依赖数据变更时,失效点在哪?

也就是:谁写数据,谁负责清相关缓存。

只有这三步都想清楚,ormcache 才是加速器;否则它更像延迟爆炸的 bug 放大器。


一句话收尾

ormcache 的本质不是“缓存一下结果”,而是“在明确 key 和失效边界的前提下复用框架级计算结果”。很多“改了没生效”,其实都是缓存边界没讲清。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。