很多人第一次绕过 ORM 写 SQL,都会得到一个很迷惑的结果:
- 数据库里已经改了;
- Python 里好像也改了;
- 但某个 stored compute 字段还是旧值;
- 再跑一段逻辑,结果又像“突然自己好了”。
这不是 Odoo 反复无常,而是你绕开了它最重要的一层:缓存失效 + 依赖传播。
从 /home/ubuntu/odoo-temp/odoo/orm/models.py 和 odoo/orm/fields.py 看,Odoo 的一致性不是靠“每次写完立刻把所有结果算到位”,而是靠一条调度管线:
- 先标记哪些字段变了;
- 再找到谁依赖它们;
- 把受影响的 stored compute 字段放进待重算集合;
- 在合适的时机 recompute / flush。
所以,手写 SQL 之后只改数据库是不够的。你还得把 ORM 的那条管线补回来。
一、ORM 写入时,系统会自动做你手写 SQL 不会做的事
当你通过 ORM 执行 write() 时,modified() 会沿着 trigger tree 往后找:
- 哪些字段依赖当前字段;
- 哪些字段是 stored compute;
- 哪些记录需要进入
tocompute。
源码里的注释把这个过程讲得很清楚:字段依赖不是“立刻执行”,而是“建立触发关系”。
换句话说:
ORM 的写入会顺手维护后续重算所需的元数据; 纯 SQL 只会改表,不会改这份元数据。
这就是两者最大的差别。
二、手写 SQL 后,最少要补三步
如果你真的需要原生 SQL,通常要把这三件事补上:
1)让数据库和缓存先别打架
self.env.cr.execute(
"UPDATE sale_order SET state = %s WHERE id IN %s",
['sale', tuple(order_ids)],
)
orders = self.env['sale.order'].browse(order_ids)
orders.invalidate_recordset(['state'])
invalidate_recordset() 做的是缓存清理。没有它,当前环境可能还拿着旧缓存,下一次读到的就不是数据库最新值。
2)告诉 ORM:哪些源字段变了
orders.modified(['state'])
这里的重点不是“把 state 重新写一遍”,而是告诉 ORM:
- 这个字段刚刚被改过;
- 它的依赖链要重新传播;
- 相关 stored compute 字段要进待重算队列。
3)在需要稳定结果前,再触发 flush / recompute
如果你后面马上要读依赖字段,或者要把结果交给别的逻辑,最好显式 flush:
orders.flush_recordset(['amount_total'])
flush_recordset() 会先 _recompute_recordset(),再把值刷向数据库。也就是说,它不是单纯“把缓存写回去”,而是会先把待重算链路走完。
三、为什么 modified() 这么关键
modified() 的源码注释里有一个很重要的概念:trigger tree。
它不是“我只知道一个字段变了”,而是会继续向后找:
- 直接依赖它的字段;
- 依赖这些字段的字段;
- 再往后的链条。
所以如果你改的是基础字段,但结果字段没有跟着更新,通常不是 compute 写错了,而是你没有触发这条 tree。
也正因如此,Odoo 在 compute_value() 里会先把字段从 tocompute 里移走,再执行 compute;如果失败,还会放回去。这样做的目的,是避免重算链路里出现“自己触发自己”的死循环。
四、最容易踩的三个坑
1)以为 depends 能管手写 SQL
不能。
@api.depends 只对 ORM 感知到的修改有用。你绕过 ORM 后,依赖树不会自动跑。
2)以为 sudo() 能解决一致性问题
不能。
sudo() 解决的是权限,不是缓存和依赖传播。
3)在 compute 里做副作用
也不推荐。
compute 的语义是“根据依赖算值”,不是“顺手发消息、建单据、调外部接口”。如果你把副作用塞进去,重算时机一变化,副作用就可能重复、漏跑,或者跑在你没想到的事务阶段。
五、实战判断顺序
以后遇到“SQL 改了但字段没变”的问题,可以直接按这个顺序排:
- 你是不是绕过 ORM 了?
- 有没有
invalidate_recordset()? - 有没有
modified()? - 依赖字段是不是 stored compute?
- 你是不是在读结果前就需要
flush_recordset()?
只要顺序对,很多“看起来像 Odoo 抽风”的问题,其实都只是你还没把 ORM 的那条一致性管线补完整。
结论
手写 SQL 不是不能用,但它的代价是:你要自己承担 ORM 本来会自动完成的那部分工作。
对于 Odoo 来说,最重要的不是“数据库里有没有改”,而是“缓存、依赖、重算、落库是不是还在同一条链上”。
DISCUSSION
评论区