
Odoo 19 框架增强:技术简报
Odoo 19的更新,侧重于现代化核心组件,以提高性能、增强开发者体验并增加代码可维护性。 最具变革性的更改是引入了新的领域对象 (Domain Object),它将领域表示为一个 Python 抽象语法树 (AST)。该对象是一个新的、强大的领域优化框架的基础,使得能够以编程方式将领域转换为能生成更高效 SQL 的形式。这通过将条件转换为对 PostgreSQL 更友好的结构(例如,用 NOT EXISTS替换 NOT IN子查询)直接带来性能提升。 整个框架都实现了重大的性能优化。通过智能地仅重新计算受新模块影响的模型,模块安装和数据库升级所需的时间已大幅减少——在测试案例中从大约八分钟减少到不到一分钟。通过“首次点击”优化,网站性能得到改善,该优化会预先获取并批量编译所有必要的 QWeb 模板。通过调整 Python 的垃圾回收器 (Garbage Collector) 和重构 ORM 的字段缓存 (field cache),实现了进一步的性能提升。 其他关键进展包括:用于 SQL 约束和索引的更声明式的语法 (declarative syntax)、用于相对日期过滤的可读的领域特定语言 (DSL)、重新设计的用于管理用户组隐含 (用户组继承关系, user group implications) 的系统,以及用于创建更健壮、更高效的计划任务 (cron jobs) 的增强型 API。这些更新共同代表了 Odoo 服务端框架在复杂性和性能方面向前迈出的重要一步。
1. 新的领域对象:抽象语法树 (AST) 方法
近期框架开发的一个核心主题是引入一个新的对象来表示领域 (domains)。这摆脱了传统的基于列表的格式,转向一个实现了抽象语法树 (AST) 的专用 Python 对象,为处理领域逻辑提供了更结构化和更强大的方式。
1.1. 核心概念与实现
新的领域对象为搜索领域提供了一个正式的、面向对象的表示。它可以通过以下几种方式实例化:
- 传递一个传统的基于列表的领域,对象会将其解析成 AST。
- 使用 Python 运算符以编程方式构建条件。
- 传递布尔值
True或False来表示包含所有内容的领域或空领域。
AST 结构会扁平化 (flattens) 复杂的条件;例如,一长串的 OR 条件会产生一个包含许多子节点的单一 OR 节点,而不是一个深度嵌套的二元节点链。这种方法有助于防止 Python 递归错误。
1.2. 程序化构建与分析
AST 方法的一个主要优点是易于程序化构建和分析。该对象利用了重写的 Python 魔术方法 (magic methods),使领域的构建更加直观和可读:
&(与符号) 用于 AND 操作。|(竖线) 用于 OR 操作。~(波浪号) 用于 NOT 操作。
这种面向对象的结构对于开发者来说,比手动解析基于列表的领域的前缀表示法要舒适得多且不易出错。它还在早期阶段提供了对领域结构的验证。
1.3. 领域优化框架
新的领域对象是一个微型“插件式”优化框架的基础,这些优化在 SQL 生成之前应用。其目标是将任何给定的领域转换为逻辑上等效但能产生更高效 SQL 的版本。
关键优化包括:
- 条件简化 (Condition Simplification):简化或移除琐碎为真或假的条件。
- 等式重写 (Equality Rewriting):系统地将等式 (
=) 和不等式 (!=) 重写为in和not in条件。这允许后续的合并。例如,('field', '=', 1) OR ('field', '=', 2)变为('field', 'in', [1, 2])。这对 PostgreSQL 索引更有效。 - 合并优化 (Merge Optimizations):框架分析
AND和OR节点,对其子节点进行排序,并合并兼容的条件。 - 子查询分组 (Subquery Grouping):分析使用
any运算符的条件,以将子查询分组在一起,这是对 PostgreSQL 性能的关键优化。 - 搜索方法预处理 (Search Method Pre-processing):计算字段的
search方法现在始终作为预处理步骤被调用,即使是对于存储字段也是如此,这允许在条件最终确定之前执行诸如清理右侧值之类的任务。
2. 高级 SQL 生成与性能
领域 AST 是实现主要目标的垫脚石:生成更好的 SQL WHERE 子句。框架现在遵循一个清晰的流程:领域首先通过优化函数,然后传递给 to_sql 函数。此过程将 SQL 序列化逻辑委托给相关的字段类型,从而改善了代码的局部性。
2.1. 从领域 AST 到优化 SQL
从领域生成 SQL 的调用栈现在遵循更合理的委托模式:
_search方法接收领域。- 它在领域 AST 上调用优化函数。
- 它在优化后的 AST 上调用
to_sql函数。 to_sql将每个条件的序列化委托给相应的字段类型。- 字段类型(例如,
Date,Char,Many2one)处理其特定的 SQL 序列化逻辑。
这种架构将复杂逻辑局部化。例如,与日期/时间处理相关的所有复杂性都存在于 Date 和 Datetime 字段类中,而不是在一个庞大的、单一的 if 语句中。
2.2. NOT IN 与 NOT EXISTS 的战略性使用
一个显著的性能改进来自处理子查询方式的改变。据观察,PostgreSQL 在执行 NOT IN (sub-select) 查询时通常效率低下,因为它倾向于首先物化整个子查询。框架现在将这些条件转换为使用 NOT EXISTS。虽然在逻辑上是等价的,但 PostgreSQL 对 NOT EXISTS 有更优的启发式方法,从而带来更好的查询计划和更快的执行速度。
2.3. 用于内部访问控制的 any! 操作符
引入了一个新的操作符 any!,作为 any 操作符的一个变体,供框架内部使用。它旨在解决在相关字段上设置条件时,目标模型上的访问权限可能干扰的问题。
any:line_ids any domain表示“存在line_ids中的一条记录满足该领域且当前用户可以访问。”any!(any_bang):line_ids any! domain表示“存在line_ids中的一条记录满足该领域”,绕过该特定步骤的访问权限检查。
该操作符对于在相关字段上实现条件而不被访问规则阻止至关重要。它不通过 RPC 暴露,以防止数据泄漏。作为进一步的优化,当在多对一字段上使用 any! 时,查询会转换为更高效的 LEFT JOIN 而不是子查询。
3. ORM 和 API 增强
除了深层的架构更改之外,还对 ORM 及其 API 进行了若干更新,以改善开发者体验并采用现代实践。
3.1. 约束和索引的声明式语法
声明 SQL 约束和索引的方法已经现代化,变得更加声明式,减少过程式。
- 索引 (Indexes):现在可以直接在模型类中声明,消除了重写
init方法来执行CREATE INDEX语句的需要。 - 约束 (Constraints):新的类级别声明取代了
_sql_constraint元组列表。约束名称是变量名,值是条件和错误消息。对于唯一约束 (unique constraints) 有一个特殊情况,它同时充当索引和约束。
3.2. 用于日期的领域特定语言 (DSL)
为了提高可读性,添加了一个新的 DSL,用于在领域中表达相对日期和 datetime 条件。这为使用 relativedelta 提供了一个更直观的替代方案。
- 示例 1:
('moment', '>', '-5 minutes') - 示例 2:
('moment', '=', 'last week')转换为一个检查moment是否落在前一周(周一到周日)内的领域。
这种语法被解析为标准领域结构,在创建用户定义的过滤器时特别有用。
3.3. API 改进
- 字段访问 (Field Access):检查字段权限的方法已被重构并重命名为
has_field_access和check_field_access。它们现在直接对字段对象操作,而不是字段名称,这在代码中更方便。 - 配置 (Configuration):配置参数已简化。Odoo 现在可以通过标准的操作系统环境变量接受选项。
-
search_fetch方法:这个新方法将搜索(检索记录 ID)和获取(检索字段值)组合到单个 SQL 查询中。它可以与特定的字段列表一起使用,或者如果未提供任何字段,将自动获取所有标记为prefetch=True的字段。这减少了查询次数,并用于性能敏感的区域,如网站页面加载。
4. 主要性能优化
投入了大量精力来识别和解决框架中几个关键领域的性能瓶颈。
4.1. 模块加载与模型设置
安装大型数据库或升级期间的一个主要瓶颈已得到解决。以前,每次加载新模块时,框架都会为注册表 (registry) 中的所有模型重新运行整个设置过程。系统经过重新设计,可以智能跟踪哪些模型受到新加载模块的影响(例如,通过继承或添加字段),并且仅运行这些特定模型的设置过程。这一更改将大规模测试案例中的模型设置时间从近 8 分钟减少到不到 1 分钟。
4.2. QWeb 模板编译(“首次点击”速度)
网站渲染性能,尤其是对页面的首次访问,得到了改善。QWeb 引擎将 .xml 模板编译成 Python 函数以供渲染。以前的行为是在渲染过程中需要时按顺序获取和编译模板。新的优化会分析主页面模板,识别它将依赖的所有子模板,在单个查询中从数据库获取它们,并一起编译它们。这种批处理过程显著减少了初始页面加载的 SQL 查询和延迟。
4.3. 重构字段缓存和垃圾回收器调优
- 字段缓存 (Field Cache):ORM 的字段缓存被重构。一个复杂的、集中式的缓存对象被移除,并被一个通用缓存结构取代。现在,每种字段类型负责管理其自己在缓存中的数据。这改善了代码局部性(例如,翻译字段现在管理其特定语言的缓存逻辑)并导致稍快的字段访问。
- 垃圾回收器 (GC):发现默认的 Python GC 阈值对于 Odoo 的典型工作负载过于激进。例如,仅创建了 700 个新对象后 GC 就会触发,这个数字在迭代典型的记录集时很容易被超越。阈值已增加以防止 GC 运行过于频繁。此外,在注册表加载期间——此阶段会创建许多对象而收集是无用的——GC 现在被临时禁用,并在之后重新启用。
5. 其他关键进展
5.1. 精细化的用户组与隐含关系管理
处理隐含用户组 (implied user groups) 的方式已发生根本性变化,变得更加明确和易于管理。
- 先前行为:将用户分配到像“销售/经理”这样的组会自动添加将该用户链接到所有隐含组(如“销售/用户”)的记录。移除经理组需要复杂的逻辑来清理这些隐含的分配。
- 新行为:数据库现在仅存储管理员做出的直接组分配。当系统执行访问权限检查时,它会动态计算有效组的完整集合(直接 + 隐含)。这确保了数据库反映明确的决策,简化了添加或删除权限的过程。
5.2. 用于健壮性的增强型计划任务 (Cron Job) API
在 Odoo 18 引入的 API 基础上,计划任务的框架得到了改进。该 API 允许一个 cron 向调度器报告其进度(例如,处理的记录数)。这用于自动检测并禁用那些重复失败且没有任何进展的 cron,防止它们消耗系统资源。
更新后的 API 鼓励开发者编写以较小批次处理数据的 cron,在每个片段处理后提交事务。这种策略避免了庞大、长时间运行的事务,这些事务在 PostgreSQL 中更容易出现序列化错误和性能下降。
5.3. 模块结构现代化
顶级的 odoo Python 模块已转换为命名空间包 (namespace package)。这一更改移除了一些内部 hack,并使框架与标准 Python 实践保持一致。一个关键好处是,用于代码分析、linting 和自动补全的现代开发工具现在可以正确地与 Odoo 代码库配合工作。
6. 开发者问答亮点
- 在问答环节中澄清了以下要点:
XML 中的新领域对象,新的 AST 对象仅用于 Python,在 XML 领域语法中不可访问。 - 使用 Lambda 的
filtered,filtered方法可以像以前一样继续与 lambda 函数一起使用。 - 领域中的
all操作符,目前未实现all操作符。然而,它在逻辑上等同于not any not结构。新的领域对象将使得在未来版本中添加all作为语法糖变得容易。 - 自定义代码的升级路径,新的领域对象可以从旧的基于列表的格式实例化。可以通过简单地将传入的领域包装在新的领域类构造函数中来更新方法以接受两种格式。也支持从对象转换回列表。
- Odoo 19 的 Python 版本,所需版本预计是 Python 3.12。
- 改进错误回溯 (Tracebacks),没有简单的方法可以“清理” Python 回溯而不移除导致它们的底层抽象,这将导致代码无法维护。回溯是 Python 调用栈的直接反映。
- QWeb 中的新领域对象,目前在 QWeb 模板中不可访问,但如果确定有明确需求,可以通过一个“小修复”来添加。



