先说结论
如果你在 Odoo 里改了一条 XML 数据,升级模块后数据库没变,最常见的根因不是:
- 模块没升级
- 文件没读到
- 服务器没重启
而是:
这条记录背后的 XMLID 被
ir.model.data.noupdate保护住了。
也就是说,Odoo 不是没认出这条记录,而是认出来了,但在 upgrade 模式下故意不改它。
这件事为什么会让人反直觉
很多开发者对 XML 数据的直觉是:
- XML 里这条记录长这样
- 模块升级时再读一次
- 数据库里这条记录就会同步成新样子
但 Odoo 的真实模型不是“文件覆盖数据库”,而是:
- XML 记录通过 XMLID 映射到
ir.model.data - 升级时根据 XMLID 找到原记录
- 再决定这次允不允许更新
所以真正的问题不是“找不找得到”,而是:
找到以后,当前升级语义是否允许覆盖。
而这个“是否允许”,noupdate 正是核心开关。
官方源码是怎么体现这个规则的
在 /home/ubuntu/odoo-temp/odoo/addons/base/models/ir_model.py 里,ir.model.data 的 _update_xmlids() 和 _build_update_xmlids_query() 很关键。
源码里最终构造的是一条 INSERT ... ON CONFLICT (module, name) DO UPDATE ... 查询。
最值得注意的一行是:
- 当
update=True(也就是模块升级语义)时 - SQL 会加上
AND NOT ir_model_data.noupdate
这句话几乎可以直接翻译成人话:
如果这条 XMLID 被标记为 noupdate,那么 upgrade 时就算撞上同一个 XMLID,也不要更新它。
这就是为什么很多人改了 XML、升了模块、结果数据库没变。
不是失效,是规则本来就如此。
noupdate 真正在保护什么
它保护的不是 XML 文件本身,而是:
- 这条 XMLID 对应的数据库记录
- 在后续模块升级时,不被模块文件再次覆盖
你可以把它理解成一种“初始化后转为管理员接管”的语义。
最典型的保护对象包括:
- 基础配置
- 演示数据
- 某些系统初始化记录
- 安装后允许人工调整、以后不想再被升级改回去的记录
所以 noupdate 不是“永不变化”,而是:
不再由模块升级自动改。
这点非常重要。
因为管理员依然可以手工改,代码也依然可以显式改。只是模块升级时,默认不替你覆盖回去。
为什么这套机制是必要的
如果没有 noupdate,很多系统会很难用。
举个典型例子:
- 你安装模块时导入了一套默认邮件模板
- 后来管理员按公司口径改了文案
- 你升级模块时,系统又把模板内容全覆盖回源码版本
这通常不是你想要的。
所以 Odoo 必须区分两种记录:
- 模块升级时应该继续被源码驱动的
- 初始化后应该更像“用户资产”的
noupdate 就是在做这个边界切分。
为什么 XMLID 和 noupdate 总是要一起理解
如果只理解 XMLID,你会知道:
- Odoo 能稳定找到一条记录
但你还是解释不了:
- 为什么有的记录升级会变
- 有的记录升级不变
而如果再加上 ir.model.data.noupdate,整个故事才完整:
- XMLID 负责“找到谁”
noupdate负责“升级时能不能动它”
所以这两个概念其实是一套升级语义里的前后半句。
最常见的 4 个误判
1. 以为 XML 变了,数据库就一定会同步变
不一定。还要看这条 XMLID 的 noupdate。
2. 以为 noupdate 表示记录以后不能改
不是。它只是不让模块升级自动覆盖。
3. 以为删掉数据库记录再升级就一定能恢复
这要看 XMLID、依赖、加载顺序和具体数据文件逻辑,不是简单一删就稳。
4. 以为“升级没生效”都是缓存问题
很多时候根本不是缓存,而是升级语义本来不允许更新。
什么时候你应该主动检查 ir.model.data
只要你遇到下面这些问题,就该第一时间怀疑这里:
- 改了 XML 里的 menu / action / group / template / parameter,升级不生效
- 某条基础数据明明有 XMLID,但更新总像被“吃掉”
- 同一个 XMLID 在新库有效,在老库升级却没变
- 你怀疑系统是在保留管理员曾经改过的结果
这时候排查思路应该是:
- 先确认 XMLID 是否一致
- 再看对应
ir.model.data是否存在 - 再看
noupdate是否为真 - 再判断当前是 install 还是 upgrade 语义
这个顺序通常比“先怀疑服务没重启”有用得多。
install 和 upgrade 的语义为什么不同
前面看 _build_update_xmlids_query() 时还有一个关键点:
- 只有
update=True时,才会额外加AND NOT ir_model_data.noupdate
也就是说,noupdate 的阻断效果,是特别针对升级更新语义体现出来的。
这就意味着:
安装期和升级期,Odoo 对同一条 XMLID 的态度并不完全一样。
这也是为什么很多新库初装效果正常,老库升级却不跟着变。
问题常常不是“源码不同”,而是生命周期阶段不同。
实战上怎么更稳地设计数据文件
一个很实用的原则是:
应该持续由模块控制的记录
别轻易放进会被 noupdate 保护的区域。
安装后允许管理员接管的记录
才考虑 noupdate 语义。
比如:
- 某些系统行为规则、技术性动作、结构性配置,升级最好还能改
- 某些邮件模板、展示文案、客户可读文本,安装后可能更适合交给业务方接手
这不是“技术对不对”的问题,而是谁拥有后续控制权的问题。
和现有文章怎么区分
站里已有一篇在讲 XMLID 与 env.ref 的稳定引用价值。
而本文的切入点不是“如何稳定找到记录”,而是:
- 为什么 XML 数据升级时有时不更新
ir.model.data的noupdate怎样参与升级 SQL 条件- install / upgrade 语义差异会怎样影响结果
也就是从模块升级的数据控制权去讲,而不是从“引用查找”去讲。
最后一句话
很多 Odoo 升级排错卡住,本质上不是你不会升级,而是你还在用“文件覆盖数据库”的直觉理解 XML 数据。
而官方源码真正表达的是:
升级时,Odoo 会先通过 XMLID 找到记录,再根据
noupdate判断你有没有资格改它。
把这句话想明白,很多“为什么就是不生效”的谜团会立刻清楚很多。
DISCUSSION
评论区