第三十一章 性能调优
篇别:第七篇 性能优化
本章学习目标
- 理解 ORM 缓存、prefetch、invalidate 的边界。
- 为 高频 domain / 排序 设计 索引 并会用 EXPLAIN ANALYZE。
- 用 批量 create/write/unlink 替代 Python 循环单条。
- 使用 Profiling 与 前端资产策略 定位 热点与首屏。
导读:先量后改
性能优化最怕 凭感觉加索引。标准路径是:定义 SLA(如列表翻页 < 400ms)→ 用日志 / profiler / EXPLAIN 定位 → 小步验证 → 回归测试。本章与 第四章 ORM、第二十八章 调试 直接衔接。
31.1 ORM 缓存
31.1.1 知识要点
- 环境缓存:同一 请求 内
browse、read会命中缓存;invalidate_*可 失效 指定记录或字段。 - 何时失效:
write/unlink后通常自动处理;原生 SQL 改表 后必须invalidate或 新请求。 - 误区:
sudo()切换用户 不自动清业务缓存;多 worker 间 无共享 Python 缓存,主要仍靠 数据库一致性。
31.1.2 案例
# 在同一请求中改了 ir.config_parameter 后,若手动 SQL 更新:
self.env["ir.config_parameter"].invalidate_model(["value"])
31.1.3 本节练习
- 简答:
invalidate_recordset典型使用场景? - 判断:ORM 缓存可保证 多进程 worker 间 强一致。( )
参考答案提示:2. 错;跨进程靠 DB 与 每次请求重新 load。
31.2 数据库索引
31.2.1 知识要点
- 字段级:
index=True、btree_not_null(版本支持时)等。 - 复合索引:
(company_id, state, date)类 常一起出现在 domain + order 的字段。 - 部分索引 / 表达式索引:PostgreSQL 高级特性,经 迁移脚本 创建。
- EXPLAIN (ANALYZE, BUFFERS):在 staging 用 生产匿名化快照 验证。
31.2.2 案例
EXPLAIN (ANALYZE, BUFFERS)
SELECT id FROM library_loan
WHERE state = 'open' AND company_id = 1
ORDER BY create_date DESC
LIMIT 80;
31.2.3 截图占位

31.2.4 本节练习
- 实操:为 常用 domain 字段加
index=True,对比 计划是否走 Index Scan。 - 简答:索引过多有什么 写放大 代价?
参考答案提示:2. INSERT/UPDATE/DELETE 维护索引成本上升;Vacuum 压力。
31.3 批量操作
31.3.1 知识要点
create([{...}, {...}]):单条 SQL 多值插入(视版本与路径)。write:同一值 可对 记录集 一次写入;循环for r in recs: r.write({...})常 极慢。unlink:记录集批量;注意 级联、约束、消息 触发器。- SQL 批量:超大批量可用
INSERT ... SELECT迁移脚本,绕过 ORM 需 后续 invalidate。
31.3.2 案例
# 差:N 次 write
for line in lines:
line.write({"qty": line.qty + 1})
# 较好(思路,非唯一写法):
# — 若所有行写入相同字段相同值:一次 lines.write({"state": "done"})
# — 若值逐行不同:用 SQL 批量 UPDATE、或按「相同值分组」减少 write 次数
避免 在循环里 create({...}) 单条 插入千行:改用 create([{...}, ...]) 或 COPY/SQL(大数据迁移)。
31.3.3 本节练习
- 实操:将循环
write改为 单次write或create多字典。 - 简答:批量
create时 约束失败 如何 定位是哪条?
参考答案提示:2. 二分插入、减小 batch、临时关闭 deferred constraint(慎用)。
31.4 Prefetch 机制
31.4.1 知识要点
- 预取:访问
rec.partner_id.name时,ORM 可能对 同批记录的 partner_id 一次性 read。 - 破坏因素:
mapped链式、read指定字段过窄、跨事务、手动 SQL。 - 工具:
search_fetch、prefetch_fields(以版本 API 为准)显式优化。
31.4.2 案例
orders = self.search([(...)])
for o in orders:
_ = o.partner_id.name # 无 prefetch 时可能 N+1;记录集批量时通常优化
修复思路:orders.mapped("partner_id") 预读;或使用 search_fetch 一次拉 partner_id.name。
31.4.3 本节练习
- 案例:解释 「循环中访问
m2o.name」 为何可能慢。 - 实操:用 日志 query count(第二十九章)对比 优化前后。
参考答案提示:1. 每条记录触发 独立查询 partner。
31.5 Profiling 工具(新增)
31.5.1 知识要点
- cProfile / py-spy:Python 函数级热点。
- Odoo 内置 / 社区模块:部分版本带 统计中间件;以 部署文档 为准。
- PostgreSQL:
pg_stat_statements找 慢 SQL。
31.5.2 案例
对 报表函数 跑 cProfile,输出 前 10 cumulative 函数名,定位 Python 层瓶颈。
31.5.3 截图占位

31.5.4 本节练习
- 实操:记录某 报表函数 热点 前三(函数名即可)。
- 简答:SQL 已优化 仍慢,下一步查什么?
参考答案提示:2. Python 循环、QWeb 渲染、网络、filestore I/O。
31.6 前端资产优化(新增)
31.6.1 知识要点
- Bundle:
assets键 合并与 懒加载(lazy_load等,见第十四章 / 第三十二章)。 - 缓存:hash 文件名 + CDN / 反向代理 Cache-Control。
- FCP:关键 CSS、减少阻塞脚本;OWL 大列表考虑 分页 / 虚拟化(视 Web 模块能力)。
31.6.2 本节练习
- 简答:lazy load 与 FCP(首次内容绘制) 的关系?
- 判断:生产环境应
?debug=assets常开。( )
参考答案提示:1. 推迟非关键 JS 可 提前 FCP;2. 错。
本章综合练习
- SLA:列表 1 万条 仍须 「流畅」 的架构方案(分页、domain、虚拟滚动 至少写两条)。
- 数据库:
VACUUM/ANALYZE运维窗口建议(一句原则 + 一条风险)。 - 综合:销售订单列表 打开 3s,EXPLAIN 显示 Seq Scan——列出 三步 行动。
- 实操:画 「读路径」:从
search_read到 PostgreSQL,标出 prefetch 可能发生点。
本章对应白皮书目录:第三十一章 性能调优。