使用Django事务进行乐观的“锁定”

时间:2017-01-23 02:30:09

标签: python django locking atomic

我有一个函数fn(),它需要原子地执行一些数据库工作,这些数据库工作依赖于在执行期间不会改变的一些数据集(大部分时间都是如此)。

在Django中实现这个的正确方法是什么?基本上我想做这样的事情:

def ensure_fn_runs_successfully():
  # While commit unsuccessful, keep trying
  while not fn():
    pass

@transaction.atomic
def fn():
  data = read_data_that_must_not_change()

  ... do some operations with the data and perform database operations ...

  # Assume it returns true if commit was successful, otherwise false
  return commit_only_if_the_data_actually_didnt_change()

@transaction.atomic负责部分问题(数据库应该只在fn运行之前或fn成功运行之后才会看到状态),但我不确定是否存在一个好的原语来执行commit_only_if_the_data_actually_didnt_change,并在失败时重试该操作。

为了验证数据没有改变,只需检查为查询返回的项目数与该函数开头时的数量相同;但是,我不知道是否有任何原语可以让你同时进行检查和提交决定/没有竞争条件。

1 个答案:

答案 0 :(得分:1)

如果您在事务块中,那么唯一可以更改您正在阅读的数据的是其他操作在同一个事务块中。因此,只要fn()未对data进行任何更改,您就可以确保数据不会更改,除非fn()更改它。这就是交易要解决的问题。

如果data可以在fn()的范围内更改,只需跟踪其更改的位置或跟踪最终结果。

@transaction.atomic
def fn():
  data = read_data_that_must_not_change()
  original_data = copy.copy(data)
  ... do some operations with the data and perform database operations ...

  # Assume it returns true if commit was successful, otherwise false
  if data != original_data:
    raise Exception('Oh no!  Data changed!') 
    # raising in exception is how you prevent transaction.atomic
    # from committing
  return commit_only_if_the_data_actually_didnt_change()

然后像你这样在循环中处理异常:

while True:
    try:
        fn()
        break
    except:
        time.sleep(10) # ten second cool off
        pass