POSTGRESQL中的行级锁及ODOO中的应用

Posted Odoo中文

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了POSTGRESQL中的行级锁及ODOO中的应用相关的知识,希望对你有一定的参考价值。

PosgreSQL行级锁可以是排他的或者是共享的。特定行上的排他行级锁是在行被更新的时候自动请求的。 该锁一直保持到事务提交或者回滚。行级锁不影响对数据的查询,它们只阻塞对同一行的写入。

行级锁命令语法

SELECT ... FOR lock_strength [OF table_name [, ...]] [ NOWAIT ]

其中lock_strenth 行级锁强度参数值为下列之一,其影响SELECT如何从表中锁定行:

  • UPDATE

  • NO KEY UPDATE

  • SHARE

  • KEY SHARE

    • FOR UPDATE

    • 令那些被SELECT检索出来的行被锁住,就像在更新一样。这样就避免它们在当前事务结束前被其它事务修改或者删除;也就是说, 其它企图UPDATE,DELETE,SELECT FOR UPDATE,SELECT FOR NO KEY UPDATE,SELECT FOR SHARE或SELECT FOR KEY SHARE这些行的事务将被阻塞,直到当前事务结束。同样, 如果一个来自其它事务的UPDATE,DELETE,SELECT FOR UPDATE已经锁住了某个或某些选定的行,SELECT FOR UPDATE将等到那些事务结束,并且将随后锁住并返回更新的行(或者不返回行,如果行已经被删除)。但是,在REPEATABLE READ或SERIALIZABLE事务内部,如果在事务开始时要被锁定的行已经改变了,那么将抛出一个错误。更多的讨论参阅Chapter 13。

    • FOR NO KEY UPDATE

    • 的行为类似于FOR UPDATE,只是获得的锁比较弱:该锁不阻塞尝试在相同的行上获得锁的SELECT FOR KEY SHARE命令。该锁模式也可以通过任何不争取FOR UPDATE锁的UPDATE获得。

    • FOR SHARE

    • 的行为类似于FOR NO KEY UPDATE,只是它在每个检索出来的行上获得一个共享锁,而不是一个排它锁。一个共享锁阻塞其它事务在这些行上执行UPDATE,DELETE,SELECT FOR UPDATE或SELECT FOR NO KEY UPDATE,却不阻止他们执行SELECT FOR SHARE或SELECT FOR KEY SHARE。

    • FOR KEY SHARE

    • 的行为类似于FOR SHARE,只是获得的锁比较弱: 阻塞SELECT FOR UPDATE但不阻塞SELECT FOR NO KEY UPDATE。一个共享锁阻塞其他事务执行DELETE或任意改变键值的UPDATE, 但是不阻塞其他UPDATE,也不阻止SELECT FOR NO KEY UPDATE, SELECT FOR SHARE 或SELECT FOR KEY SHARE。

为了避免操作等待其它事务提交,使用NOWAIT选项。如果被选择的行不能立即被锁住, 那么语句将会立即汇报一个错误,而不是等待。请注意,NOWAIT只适用于行级别的锁。 如果需要申请表级别的锁同时又不等待,那么你可以使用LOCK的 NOWAIT选项。

在Odoo中的应用

在Odoo官方模块中,行级锁的应用一共出现了四处:

位于odoo/addons/base/ir/ir_sequence.py:

def _update_nogap(self, number_increment):
    number_next = self.number_next
    self._cr.execute("SELECT number_next FROM %s WHERE id=%s FOR UPDATE NOWAIT" % (self._table, self.id)) 
    self._cr.execute("UPDATE %s SET number_next=number_next+%s WHERE id=%s " % (self._table, number_increment, self.id))
    self.invalidate_cache(['number_next'], [self.id])
    return number_next

当更新某个序列号记录的下一个流水号时加行级锁

位于odoo/addons/base/ir/ir_cron.py:

for job in jobs:
    lock_cr = db.cursor()
    try:
        # Try to grab an exclusive lock on the job row from within the task transaction
        # Restrict to the same conditions as for the search since the job may have already
        # been run by an other thread when cron is running in multi thread
        lock_cr.execute("""SELECT *                           FROM ir_cron                           WHERE numbercall != 0                              AND active                              AND nextcall <= (now() at time zone 'UTC')                              AND id=%s                           FOR UPDATE NOWAIT""",   
                       (job['id'],), log_exceptions=False)

当执行定时任务时,用行级锁来防止多线程情况下定时任务被重复执行

位于addons/stock/wizard/stock_scheduler_compute.py:

def _procure_calculation_orderpoint(self):
    with api.Environment.manage():
        # As this function is in a new thread, I need to open a new cursor, because the old one may be closed
        new_cr = self.pool.cursor()
        self = self.with_env(self.env(cr=new_cr))
        scheduler_cron = self.sudo().env.ref('stock.ir_cron_scheduler_action')
        # Avoid to run the scheduler multiple times in the same time
        try:
            with tools.mute_logger('odoo.sql_db'):
                self._cr.execute("SELECT id FROM ir_cron WHERE id = %s FOR UPDATE NOWAIT", (scheduler_cron.id,)) 
        except Exception:
            _logger.info('Attempt to run procurement scheduler aborted, as already running')
            self._cr.rollback()
            self._cr.close()
            return {}

与定时任务的行级锁一样,当运行mrp运算的定时任务时,行级锁保障不被重复运行

位于addons/stock/models/stock_quant.py:

try:
    with self._cr.savepoint():
        self._cr.execute("SELECT 1 FROM stock_quant WHERE id = %s FOR UPDATE NOWAIT", [quant.id], log_exceptions=False) 
        quant.write({
            'quantity': quant.quantity + quantity,
            'in_date': in_date,
        })
        # cleanup empty quants
        if quant.quantity == 0 and quant.reserved_quantity == 0:
            quant.unlink()
        breakexcept OperationalError as e:
    if e.pgcode == '55P03':  # could not obtain the lock
        continue
    else:
        raiseelse:self.create({
    'product_id': product_id.id,
    'location_id': location_id.id,
    'quantity': quantity,
    'lot_id': lot_id and lot_id.id,
    'package_id': package_id and package_id.id,
    'owner_id': owner_id and owner_id.id,
    'in_date': in_date,})

当更新产品库存可用数量时加行级锁,以防止同时的更新操作。


以上是关于POSTGRESQL中的行级锁及ODOO中的应用的主要内容,如果未能解决你的问题,请参考以下文章

转 MySQL中的共享锁与排他锁

[数据库事务与锁]详解六: MySQL中的共享锁与排他锁

Mysql中的行级锁表级锁页级锁

MySQL中的行级锁,表级锁,页级锁

mysql中的行级锁,表级锁和页级锁

MySQL中的行级锁表级锁页级锁