2021-01-20 odoo事务处理测试

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.

    """
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容