odoo手动提交事务问题探索
背景:
在做项目时,发现数据库中几百条数据的修改时间都是相同的。寻找其中原因,在代码层面为了避免大数据量放在一次修改数据,特意做了分页查询,每一页执行一次更新方法,所有数据放在循环里处理。
理论上就能实现数据的分批处理,更新时调用odoo模型中默认的write方法。
原因:
在循环中加断点,每次执行write方法之后,数据应该被修改,但是数据库中数据并未发生变化,而是等所有数据执行完毕才会统一变化。
其中原因就是单独执行write方法时,只提交了修改命令,但是未提交事务,所以数据库没有发生更新,
在源码中找到提交事务的代码,debug,发现循环过程中并未执行提交,只有在最后返回的时候提交一次事务。就造成了上面说的所有数据的修改时间相同。
问题思考:
如果循环的数据量无论多大,都一次提交,如果其中一条数据发生异常,所有数据都将回滚,这是我们担心的问题。再有,如果数据量百万之巨,那么还是一次提交事务会不会因为数据量太大造成其他问题。
起初我的想法很简单,既然write方法不能自动提交事务,那么就手动提交,self.env.cr.commit() 在数据库连接的脚本中,创建的游标执行sql之后都是手动提交事务的,
果然,加上手动提交事务之后,每次循环结束,都能看到数据库中对应的数据发生了变化。
后来在odoo社区发现其中有一篇帖子视手动提交事务为洪水猛兽,因为手动提交事务没办法确保出现问题能准确回滚,也就是没办法保证数据的安全性和准确性。
甚至说出了无论你在哪里看到了self.env.cr.commit() 这行代码,都要删了它!
他说不能手动提交事务,因为不能保证数据出现问题的回滚,我们还考虑到一点,就是手动提交事务的时候,会不会把其他并行事务的未完成的操作一并提交了。
能不能手动提交事务,还要看并行的事务之间是什么隔离级别。
数据库并行事务时,不同的隔离级别会出现不同的读取问题,脏读、幻读、不可重复读。
脏读:事务b读到事务a修改的数据,但是a还未提交事务。
不可重复读:在事务a中运行同一条sql两次,第二次结果是被事务b修改过的数据。
幻读:事务a查询时没有发现数据,事务b插入数据,事务a插入数据发现主键冲突,再次查询扔未查到数据
介绍一下并行事务的四种隔离级别。
read uncommitted 读未提交:一个事务还未提交,变更就能被别的事务看到。
read commited 读已提交:一个事务提交后,变更才能被别的事务看到。
repeatable read 可重复读:一个事务执行过程中看到的数据,总是跟这个事务启动时看到的数据一致。
seriallizable 可串行化:读写都会加锁,当读写锁冲突时,后访问的事务必须等前一个事务执行完毕才能执行。
odoo采用的是后两种 可重复读和可串行化
确保事务并行过程中的数据安全。既然事务之间的安全级别高,那在一个事务中的提交只与该事务有关。
大事务处理
回到提交事务的问题,查阅资料发现有人对数据库做过大数据量的压力测试,事务提交越频繁效率越低
但是大事务确实也会带来很多问题。比如锁定太多的数据,造成阻塞和锁超时;回滚时间长;执行时间长容易造成主从延迟。
所以取中间合适的数据量提交事务提升效率同时降低风险。
postgresql自动提交事务默认开启状态。
关于postgresql的自动提交事务测试。
关闭自动提交事务
插入数据之后回滚能取消上一步操作,并且使用连接工具看不到数据,说明事务并未提交。
未提交的事务,修改的数据无法让其他事务看到,其实还是未修改成功的状态。
而Postgresql默认是自动提交的,只不过在pgadmain上执行一条sql语句时,执行完毕就会提交。
而当执行多个sql语句时,只有当全部执行完毕,第一个sql语句才会自动提交。
若第一个sql语句时update语句,sql全部执行完毕后,数据表才会进行update的提交,数据表才会改变,否则,数据表是不会改变的。
连续的sql语句会放在一起执行,也就是作为一个sql语句提交,从前到后依次执行。中间遇到错误所有修改一并回滚。
刚好就是背景里遇到的问题。
结合对odoo并行事务隔离级别的了解。加上自动提交事务的特性,手动提交事务确实能避免大事务的提交,同时效率可能有所降低,
但是和请求第三方api的时间相比,微不足道。
对于手动提交事务的风险,是修改不可逆,一旦提交发现问题回滚不了。
在程序中,错误的修改数据,如果不是抛出异常,也无法获取,最后还是一并提交,不能回滚。
关于是否能手动提交,我们还要在不同的使用场景中做实验才能确定是否会发生风险。