1. 结论
数据库的事务处理具有4个隔离级别:
- read uncommitted | 未提交读
- read committed | 已提交读
- repeatable read | 可重复读
- serializable | 可序列化
odoo的ORM实现了最高的serializable级事务隔离,一般不需要手动管理事务。
2. 案例
增加一条商品进货单将关联两笔数据库操作:一是更新库存表,修改对应商品的库存;二是增加进货单记录。这两笔操作需要通过数据库的事务机制来确保ACID特性。
3. 测试
odoo的sql_db.py文件中封装了Cursor类,该类是odoo中常用的env.cr对象的类型,其中封装了python访问PostgreSQL的插件psycopg2,该插件提供了对数据库的事务性操作。
Cursor类中有两个方法与事务有关:commit()和rollback()
新建进货单记录时会调用进货单模型的父类models.Model中的create方法,为了在增加进货单记录的同时调整库存数据,我在进货单模型中覆写了该方法。
按照传统思维,在对数据库的系列操作之前需要先开启事务,在操作结束后再提交事务,异常处理中回滚事务。我尝试写了如下的代码:
@api.model
def create(self,values):
try:
values['order_code']=self.env['ir.sequence'].next_by_code('sequence.purchase_order')
stock=self.env['sunrise.u.stock']
if 'order_items' in values.keys():
for item in values['order_items']:
stock_commodity=stock.search([('commodity','=',item[2]['commodity'])])
stock_commodity.amount+=item[2]['amount']
result=super().create(values)
self.env.cr.commit()
return result
except:
self.env.cr.rollback()
raise Exception(_('b_purchase_order.py:create() --- SQL Transaction Failed!'))
测试添加一张进货单,进货单记录和库存数据都正确创建和更新了。
为了测试异常情况,我在self.env.cr.commit()之前raise了一个异常,再次添加进货单,弹出异常,查看数据库,两张表的数据都没有改变,事务处理成功。
但我还存在疑问,Cursor类中没有定义begin transaction方法,这样处理事务会不会有问题呢?commit和rollback函数到底有没有起作用?索性将commit和rollback都删掉,看看业务逻辑是否正确。
同样,保留了commit之前的raise异常,并将self.env.cr.commit()和self.env.cr.rollback()注释掉。测试添加一张进货单,弹出异常,查看数据库,两张表数据没有改变,事务处理依然成功。
询问朋友,告知我:odoo的ORM已经实现了事务处理机制,开发应用时不需要关心事务机制。
这是odoo sql_db.py Cursor类的说明,其核心意思就是Cursor已经实现了Serializable level isolation,我们在模型方法中对数据库的操作是满足ACID性质的,可以打消对odoo事务性处理的疑虑。
class Cursor(BaseCursor):
"""Represents an open transaction to the PostgreSQL DB backend,
acting as a lightweight wrapper around psycopg2's
``cursor`` objects.
``Cursor`` is the object behind the ``cr`` variable used all
over the OpenERP code.
.. rubric:: Transaction Isolation
One very important property of database transactions is the
level of isolation between concurrent transactions.
The SQL standard defines four levels of transaction isolation,
ranging from the most strict *Serializable* level, to the least
strict *Read Uncommitted* level. These levels are defined in
terms of the phenomena that must not occur between concurrent
transactions, such as *dirty read*, etc.
In the context of a generic business data management software
such as OpenERP, we need the best guarantees that no data
corruption can ever be cause by simply running multiple
transactions in parallel. Therefore, the preferred level would
be the *serializable* level, which ensures that a set of
transactions is guaranteed to produce the same effect as
running them one at a time in some order.
However, most database management systems implement a limited
serializable isolation in the form of
`snapshot isolation <http://en.wikipedia.org/wiki/Snapshot_isolation>`_,
providing most of the same advantages as True Serializability,
with a fraction of the performance cost.
With PostgreSQL up to version 9.0, this snapshot isolation was
the implementation of both the ``REPEATABLE READ`` and
``SERIALIZABLE`` levels of the SQL standard.
As of PostgreSQL 9.1, the previous snapshot isolation implementation
was kept for ``REPEATABLE READ``, while a new ``SERIALIZABLE``
level was introduced, providing some additional heuristics to
detect a concurrent update by parallel transactions, and forcing
one of them to rollback.
OpenERP implements its own level of locking protection
for transactions that are highly likely to provoke concurrent
updates, such as stock reservations or document sequences updates.
Therefore we mostly care about the properties of snapshot isolation,
but we don't really need additional heuristics to trigger transaction
rollbacks, as we are taking care of triggering instant rollbacks
ourselves when it matters (and we can save the additional performance
hit of these heuristics).
As a result of the above, we have selected ``REPEATABLE READ`` as
the default transaction isolation level for OpenERP cursors, as
it will be mapped to the desired ``snapshot isolation`` level for
all supported PostgreSQL version (8.3 - 9.x).
Note: up to psycopg2 v.2.4.2, psycopg2 itself remapped the repeatable
read level to serializable before sending it to the database, so it would
actually select the new serializable mode on PostgreSQL 9.1. Make
sure you use psycopg2 v2.4.2 or newer if you use PostgreSQL 9.1 and
the performance hit is a concern for you.
.. attribute:: cache
Cache dictionary with a "request" (-ish) lifecycle, only lives as
long as the cursor itself does and proactively cleared when the
cursor is closed.
This cache should *only* be used to store repeatable reads as it
ignores rollbacks and savepoints, it should not be used to store
*any* data which may be modified during the life of the cursor.
"""