第三十一章 性能调优

篇别:第七篇 性能优化

本章学习目标

  • 理解 ORM 缓存、prefetch、invalidate 的边界。
  • 高频 domain / 排序 设计 索引 并会用 EXPLAIN ANALYZE
  • 批量 create/write/unlink 替代 Python 循环单条。
  • 使用 Profiling前端资产策略 定位 热点与首屏

导读:先量后改

性能优化最怕 凭感觉加索引。标准路径是:定义 SLA(如列表翻页 < 400ms)→ 用日志 / profiler / EXPLAIN 定位 → 小步验证 → 回归测试。本章与 第四章 ORM第二十八章 调试 直接衔接。


31.1 ORM 缓存

31.1.1 知识要点

  • 环境缓存:同一 请求browseread 会命中缓存;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 本节练习

  1. 简答invalidate_recordset 典型使用场景?
  2. 判断:ORM 缓存可保证 多进程 worker强一致。( )

参考答案提示:2. 错;跨进程靠 DB 与 每次请求重新 load


31.2 数据库索引

31.2.1 知识要点

  • 字段级index=Truebtree_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-1 EXPLAIN ANALYZE 前后对比

31.2.4 本节练习

  1. 实操:为 常用 domain 字段加 index=True,对比 计划是否走 Index Scan
  2. 简答:索引过多有什么 写放大 代价?

参考答案提示: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 本节练习

  1. 实操:将循环 write 改为 单次 writecreate 多字典
  2. 简答:批量 create约束失败 如何 定位是哪条

参考答案提示:2. 二分插入减小 batch临时关闭 deferred constraint(慎用)。


31.4 Prefetch 机制

31.4.1 知识要点

  • 预取:访问 rec.partner_id.name 时,ORM 可能对 同批记录的 partner_id 一次性 read
  • 破坏因素mapped 链式read 指定字段过窄跨事务手动 SQL
  • 工具search_fetchprefetch_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 本节练习

  1. 案例:解释 「循环中访问 m2o.name 为何可能慢。
  2. 实操:用 日志 query count(第二十九章)对比 优化前后

参考答案提示:1. 每条记录触发 独立查询 partner。


31.5 Profiling 工具(新增)

31.5.1 知识要点

  • cProfile / py-spy:Python 函数级热点。
  • Odoo 内置 / 社区模块:部分版本带 统计中间件;以 部署文档 为准。
  • PostgreSQLpg_stat_statements慢 SQL

31.5.2 案例

报表函数cProfile,输出 前 10 cumulative 函数名,定位 Python 层瓶颈。

31.5.3 截图占位

图 31-2 火焰图或 cProfile 排序输出

31.5.4 本节练习

  1. 实操:记录某 报表函数 热点 前三(函数名即可)。
  2. 简答SQL 已优化 仍慢,下一步查什么?

参考答案提示:2. Python 循环、QWeb 渲染、网络、filestore I/O


31.6 前端资产优化(新增)

31.6.1 知识要点

  • Bundleassets 合并与 懒加载lazy_load 等,见第十四章 / 第三十二章)。
  • 缓存hash 文件名 + CDN / 反向代理 Cache-Control
  • FCP关键 CSS减少阻塞脚本OWL 大列表考虑 分页 / 虚拟化(视 Web 模块能力)。

31.6.2 本节练习

  1. 简答lazy loadFCP(首次内容绘制) 的关系?
  2. 判断:生产环境应 ?debug=assets 常开。( )

参考答案提示:1. 推迟非关键 JS 可 提前 FCP;2. 错。


本章综合练习

  1. SLA:列表 1 万条 仍须 「流畅」 的架构方案(分页、domain、虚拟滚动 至少写两条)。
  2. 数据库VACUUM / ANALYZE 运维窗口建议(一句原则 + 一条风险)。
  3. 综合销售订单列表 打开 3sEXPLAIN 显示 Seq Scan——列出 三步 行动。
  4. 实操:画 「读路径」:从 search_readPostgreSQL,标出 prefetch 可能发生点

本章对应白皮书目录:第三十一章 性能调优。