Odoo19 内置缓存原理、用法与避坑指南
适用版本:Odoo 19、Odoo 企业版、Odoo 社区版
Odoo 性能问题的表现往往十分典型:开发环境运行一切正常,测试环境性能略有下降,而在高负载场景下则会出现严重卡顿。几乎所有这类问题,根源都是对数据库发起了大量冗余查询——反复读取几乎不会变动的相同数据。
Odoo 内置了一套完善的缓存框架,但多数开发者仅用到了其极少一部分功能。本文将全面讲解 Odoo 缓存相关内容,从基础的 ORM 缓存到进阶的 Redis 共享缓存,帮助你大幅提升系统运行性能。
一、Odoo 中的缓存是什么?
简单来说,缓存就是将高开销计算的结果保存下来以供重复使用。借助缓存,相同数据无需每次请求都查询数据库,仅需执行一次计算,后续请求直接从缓存中读取数据。
在 Odoo 中,缓存主要应用于以下场景:
- 避免对静态数据、低频变动数据重复执行数据库查询
- 加快系统配置参数的读取速度
- 缩短复杂业务逻辑的计算耗时
- 缓存外部 API 的响应结果
Odoo 的缓存逻辑定义在 odoo/tools/cache.py 文件中,缓存作用于进程级别。也就是说,每一个 Odoo 进程都拥有独立缓存,进程之间的缓存互不共享。
二、理解 @tools.ormcache 缓存装饰器
ormcache 是 Odoo 实现缓存的核心方式,它会根据方法的入参,对方法的执行结果进行缓存。
基础用法
以下是根据 ISO 编码查询国家信息的简单示例:
from odoo import tools
class ResCountry(models.Model):
_name = 'res.country'
@tools.ormcache('code')
def _get_country_by_code(self, code):
return self.search([('code', '=', code)], limit=1).id
当使用某个编码调用该方法后,执行结果会存入缓存。后续使用同一编码再次调用时,会直接返回缓存中的 ID,不再执行数据库查询。
ormcache 的不同变体
Odoo 提供了多种 ormcache 变体,可根据实际业务场景选用,具体用法如下:
1. @tools.ormcache(*args)
标准装饰器,根据指定的参数名作为缓存键。
@tools.ormcache('self.env.uid', 'model_name')
def _get_access_rights(self, model_name):
# 按用户、模型分别缓存结果
...
2. @tools.ormcache_context(*args, keys=())
用法与基础装饰器类似,同时会结合指定的上下文键生成缓存。如果方法返回结果受用户语言、当前公司等上下文影响,建议使用该装饰器。
@tools.ormcache('product_id', 'self.env.context.get("lang")')
def _get_product_name(self, product_id):
return self.browse(product_id).with_context(
lang=self.env.context.get('lang')).name
三、缓存失效机制
ormcache 一大优势是无需手动管理缓存失效,ORM 会自动处理。当模型执行数据写入操作时,对应的缓存条目会自动清空。
| 触发事件 | 是否清空缓存 |
|---|---|
| 调用模型 create() 方法 | 是 |
| 调用模型 write() 方法 | 是 |
| 调用模型 unlink() 方法 | 是 |
| Odoo 工作进程重启 | 是 |
| 手动调用 clear_caches() | 是 |
如果需要手动强制清空缓存(例如执行数据迁移脚本、直接修改数据库后),可使用以下代码:
# 清空指定模型的缓存 self.env['res.country'].invalidate_model()
四、Odoo 源码中的实际应用案例
参考 Odoo 原生代码,是掌握 ormcache 最佳用法的有效方式,以下为两个高频应用案例:
1. 系统配置参数(IR Config Parameters)
这是 Odoo 源码中 ormcache 最核心的应用场景。系统全局调用 get_param() 读取配置参数时,会根据参数键做缓存。
# 代码位置:ir.config_parameter 模型
@tools.ormcache('key')
def get_param(self, key, default=False):
self.env.cr.execute(
"SELECT value FROM ir_config_parameter WHERE key = %s", [key])
result = self.env.cr.fetchone()
return result[0] if result else default
这也是生产环境中系统配置参数读取速度极快的原因:首次请求加载数据后,后续全部从内存缓存读取。
2. 公司货币(Company Currency)
# 代码位置:res.company 模型
@tools.ormcache('self.env.uid')
def _get_user_currency(self):
return self.env.user.company_id.currency_id
五、通过 store=True 实现字段级缓存
除 ormcache 外,还有一种简单方式可以减少重复查询:将计算字段结果持久化到数据库,避免每次读取都重复计算。
total_amount = fields.Float(
string='Total Amount',
compute='_compute_total_amount',
store=True # 结果存入数据库,读取时不再重复计算
)
@api.depends('order_line.price_subtotal')
def _compute_total_amount(self):
for record in self:
record.total_amount = sum(
record.order_line.mapped('price_subtotal'))
六、缓存使用最佳实践
推荐做法
- 对静态数据、低频变动数据启用缓存,例如货币、国家、系统配置参数
- 为每一处自定义缓存明确失效触发条件
- 若结果受语言、公司上下文影响,使用 ormcache_context
- 读写比例悬殊(读多写少)的计算字段,设置 store=True 持久化
- 上线前后做性能对比分析,验证缓存的实际优化效果
禁忌做法
- 不要缓存用户相关的交易类动态数据
- 不要缓存要求实时更新的数据(例如实时库存)
- 不要缓存大批量数据集,内存开销会抵消缓存带来的性能提升
- 直接操作数据库后,务必在迁移脚本中执行缓存清空操作
Odoo 的缓存功能十分强大,但 @tools.ormcache 只是基础。理解其原理并遇到性能瓶颈后,可组合多种缓存方案:计算字段、各类 ormcache 衍生装饰器、多进程架构下的 Redis 缓存、公开接口的 HTTP 头部缓存等。
优秀的 Odoo 性能优化开发者,都会先定位性能问题、找出慢查询,再按需选择合适的缓存方案,并验证优化效果。盲目全局启用缓存,往往无法达到理想效果。