谁能告诉我关于django的延迟加载和交易?

时间:2018-03-08 09:02:57

标签: python django transactions lazy-loading

这样的背景:

data = User.objects.get(pk=1) 
if data.age > 20:
    with transaction.atomic():
        data.age -=2
        data.save()

我想知道,如果很多进程同时执行代码,就像那样,每个进程都会在没有事务的情况下同时获取数据,例如,age是30。

然后,一个进程执行下一个进程,使age-2 = 28并保存。

然后下一个进程执行,当它执行data.age - = 2时,数据按数据获取。

年龄,会是18岁还是20岁?如果它是20,这意味着,交易添加错误的地方?或者它意味着,交易不起作用,因为交易会添加到数据做选择行,并且可以更改和保存。但是事务添加没有选择行吗?

第二个问题:

如果我喜欢这样:

data = User.objects.get(pk=1)
with transaction.atomic():
    if data.age > 20:
        data.age -=2
        data.save()

此演示,在data.age>之前添加事务。 20。 对于延迟加载,sql行会在我使用时执行,例如data.age> 20。 但是,当它读取sql行时,之前已经添加了转换。 所以,我想知道,这个演示是否会在sql行上添加事务?

非常感谢,很好的人。

1 个答案:

答案 0 :(得分:2)

我们需要解决两个问题;事务和锁定以及延迟加载(您的代码似乎没有使用)。

你所有的例子都有竞争条件;获取同一用户年龄的多个请求将尝试更新数据库表以设置18,如果他们在任何提交事务之前都已获取20

如果在事务内部或外部提取列,则无关紧要。事务所保证的是,所有写入将一起成功,或者一起失败。读取的数据将保持一致(因此多次读取将产生相同的数据),但事务不会阻止其他事务根据读取的数据进行读取和更新。

那是因为原子事务只是(简要地)在写入数据时锁定行;交易中的所有更改都作为一个单元一起写入。但这并不意味着您写入数据库的内容是正确的,多个事务可以读取20作为年龄,并且当它们轮到它时将全部写入18行锁定并让他们的提交成功。

但是,要解决延迟加载问题,除非您明确marked the age column with defer(),否则您没有使用任何延迟加载。执行age方法时,User值将加载所有其他User.objects.get()数据。这在这里并不重要,因为即使user.age > 20测试触发单独的语句来读取age列,您仍然会读取不一致的数据(您可以阅读{{1}就在另一个事务提交和写入20)之前。

您需要的是在读取之前锁定行,以便其他请求无法读取错误的值。如果先锁定 ,然后读取,然后提交,然后解锁,其他请求必须等到锁定被释放后再读取18列。

您可以使用select_for_update() method来锁定特定行,此时尝试锁定同一行的任何其他请求都必须等到您完成锁定后才会执行:

age

然而,您应该只使用锁定作为最后的手段。锁定将产生性能瓶颈,因为现在请求必须彼此等待。除非您的实际用例更复杂并且涵盖多个读取和写入,所有这些都必须作为一个单元执行,并且您只能使用Python代码来做出决策,否则您不需要使用行锁定。

相反,如果您需要以原子方式更新列,则应使用带有年龄过滤器的update() query,此时确定年龄是否需要更新的数据库。与F() expression一起,然后将整个计算留给数据库,数据库以原子方式执行:

with transaction.atomic():
    data = User.objects.select_for_update().get(pk=1)
    if data.age > 20:
        data.age -=2
        data.save()

对于更复杂的场景,您可以使用conditional expression来确定更新中的最终值。

from django.db.models import F rowcount = User.objects.filter(pk=1, age_gt=20).update(age=F('age') - 2) 语法与适当的过滤器和表达式一起使用,将工作移至数据库,以测试您的条件和值计算,并在提交时执行 ,所以当行被锁定时。这可确保锁定保持在尽可能少的量,从而减少瓶颈。