【问题标题】:How can I ensure a write has completed in PostgreSQL before releasing a lock?在释放锁之前,如何确保 PostgreSQL 中的写入已完成?
【发布时间】: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

注意:为了便于阅读,我使用了 ABCDE 而不是对象的数字 ID。

考虑到 DB 锁定,为什么写入不会在读取之前注册?起初我尝试不使用transaction.atomic,但添加它认为它会有所作为。它没有。有没有办法在释放咨询锁之前强制插入真的完成?

【问题讨论】:

  • 在您的事务块中使用pg_advisory_xact_lock()。锁将在提交时自动释放。
  • @NickBarnes 这是否容易受到@icuken 描述的相同问题的影响,其中进程B 可能会在进程A 获得锁之前开始其事务?
  • 如果您使用默认的READ COMMITTED 隔离级别,那么您的事务何时开始并不重要。只有REPEATABLE READSERIALIZABLE 才会在事务开始时冻结您的数据库视图。我认为这里真正的问题是AA 提交更改之前释放了锁(并且B 获得了它),所以B 在更改可见之前进行查询。持有锁直到提交可以解决这个问题。

标签: python django postgresql transactions locking


【解决方案1】:

我认为这是因为 Django(或中间件)的事务管理,我不完全确定,最好在您的代码上测试它,但它看起来像:当您尝试获取锁时,Django 可能会启动一个新的事务,所以当你实际上被锁定在cursor.execute(LOCK_SQL, [self._meta.db_table, self.id]) 时,你已经被隔离了。

当您等待锁时,另一个进程(已获得锁)确实插入数据库并提交其事务,但第一个进程在实际获取锁时不会看到此更改,因为事务之前已启动。

您可以检查您的应用程序设置中的ATOMIC_REQUESTS 或任何可以启用每个请求的事务的中间件。

【讨论】:

  • 啊,就是这样!我的观点被包裹在@transaction.atomic() 中。从整个视图中删除 atomic 解决了这个问题(尽管现在我将不得不重新考虑我的容错性......)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
  • 2022-11-22
  • 1970-01-01
  • 1970-01-01
  • 2020-09-09
相关资源
最近更新 更多