【发布时间】:2016-10-17 03:39:47
【问题描述】:
我在 Django 模型上有一个函数,它从 PostgreSQL 9.5 数据库计算一个值,并根据结果确定是否在另一行中添加数据。该函数必须在添加行之前知道该值,并且计算的未来值将取决于新行。
为了执行这些规则,我尝试使用advisory locks。我正在尝试做的简化版本如下:
from django.db import connection, models, transaction
...
def create_usage(self, num_credits):
LOCK_SQL = '''SELECT pg_advisory_lock(1) FROM %s WHERE id = %s'''
UNLOCK_SQL = '''SELECT pg_advisory_unlock(1) FROM %s WHERE id = %s'''
cursor = connection.cursor()
try:
# Create an advisory lock on the instance's row
print('obtaining lock for object {}'.format(id(self))
cursor.execute(LOCK_SQL, [self._meta.db_table, self.id])
print('obtained lock for object {}'.format(id(self))
# Perform some read and update when the lock is obtained
with transaction.atomic():
# -- SELECT SUM(...) FROM table WHERE ...
sum_credits = self.credits_used.aggregate(
sum_credits=models.Sum('num_credits'))['sum_credits']
print('existing credits: {}'.format(sum_credits))
if sum_credits < 100 - num_credits:
print('inserting')
# -- INSERT INTO table VALUE (...)
self.credits_used.add(CreditUsage(num_credits=num_credits))
else:
print('not inserting')
finally:
# Release the lock when done, or when an exception occurs
print('releasing lock for object {}'.format(id(self))
cursor.execute(UNLOCK_SQL, [self._meta.db_table, self.id])
print('released lock for object {}'.format(id(self))
(这是受到this Caktus Group post 的粗略启发)
我在连接到同一个数据库的多个进程中运行此代码。在控制台中,'obtaining' 和 'releasing' 打印语句的顺序是我所期望的(即,在其他进程释放它之前没有获得锁),但我从数据库中获取的数据没有t 似乎像我预期的那样更新。例如,当从不同的线程快速连续运行一个视图(调用obj.create_usage(5))时,我会得到:
obtaining lock for object A
obtained lock for object A
existing credits: 90
inserting
releasing lock for object A
obtaining lock for object B
released lock for object A
obtained lock for object B
existing credits: 90
inserting
obtaining lock for object C
releasing lock for object B
released lock for object B
obtained lock for object C
existing credits: 90
inserting
releasing lock for object C
released lock for object C
obtaining lock for object D
obtained lock for object D
existing credits: 95
obtaining lock for object E
inserting
releasing lock for object D
obtained lock for object E
released lock for object D
existing credits: 100
not inserting
releasing lock for object E
released lock for object E
注意:为了便于阅读,我使用了 A、B、C、D、E 而不是对象的数字 ID。
考虑到 DB 锁定,为什么写入不会在读取之前注册?起初我尝试不使用transaction.atomic,但添加它认为它会有所作为。它没有。有没有办法在释放咨询锁之前强制插入真的完成?
【问题讨论】:
-
在您的事务块中使用
pg_advisory_xact_lock()。锁将在提交时自动释放。 -
@NickBarnes 这是否容易受到@icuken 描述的相同问题的影响,其中进程
B可能会在进程A获得锁之前开始其事务? -
如果您使用默认的
READ COMMITTED隔离级别,那么您的事务何时开始并不重要。只有REPEATABLE READ或SERIALIZABLE才会在事务开始时冻结您的数据库视图。我认为这里真正的问题是A在A提交更改之前释放了锁(并且B获得了它),所以B在更改可见之前进行查询。持有锁直到提交可以解决这个问题。
标签: python django postgresql transactions locking