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中的应用的主要内容,如果未能解决你的问题,请参考以下文章