取消阻塞后,select_for_update是否看到另一个select_for_update事务添加的行?

时间:2019-03-06 22:52:04

标签: mysql django django-models django-queryset django-orm

我想创建一个ID等于该模型当前最大ID加上一个(例如自动递增)的模型。我正在考虑使用select_for_update进行此操作,以确保当前最大ID没有竞争条件,如下所示:

with transaction.atomic():
    greatest_id = MyModel.objects.select_for_update().order_by('id').last().id
    MyModel.objects.create(id=greatest_id + 1)

但是我想知道,如果两个进程尝试同时运行该进程,则第二个进程一旦解除阻塞,它将看到第一个进程插入的新的最大ID,还是仍然看到旧的最大ID?

例如,假设当前最大ID为10。创建新模型需要两个过程。第一个锁定ID10。然后第二个锁定,因为10被锁定。第一个插入11,然后解锁10。然后,第二个解锁,现在它会看到第一个插入的11是最大的,还是仍然看到10,因为那是被阻塞的行?

在select_for_update docs中,它表示:

  

通常,如果另一个事务已经获得了对所选行之一的锁定,则查询将阻塞,直到释放该锁定为止。

因此,对于我的示例,我认为这意味着第二个过程将在取消阻塞并获得11后重新运行最大ID的查询。但是我不确定我是在解释这种权利。

注意:我正在使用MySQL作为数据库。

2 个答案:

答案 0 :(得分:1)

不,我认为这行不通。

首先,请注意,您绝对应该检查所用数据库的文档,因为Django文档中未捕获的数据库之间存在许多细微的差异。

使用PostgreSQL documentation作为指导,问题在于,在默认的READ COMMITTED隔离级别下,被阻止的查询将不会重新运行。当第一个事务提交时,被阻止的事务将能够看到对该行的更改,但是将无法看到已添加新行。

  

更新命令可能会看到不一致的快照:它可以看到并发更新命令对它尝试更新的同一行的影响,但看不到这些命令对数据库中其他行的影响

所以10将返回。

答案 1 :(得分:0)

编辑:我对这个答案的理解是错误的,只是出于文档考虑,以防万一我想回到它。

经过一些调查,我相信这会按预期进行。

原因是进行此调用:

MyModel.objects.select_for_update().order_by('id').last().id

SQL Django生成并针对数据库运行的SQL实际上是:

SELECT ... FROM MyModel ORDER BY id ASC FOR UPDATE;

(对last()的调用仅在查询集已被评估之后发生。)

意思是,查询在两次运行时都扫描所有行。这意味着它将第二次运行,它将拾取新行并相应地返回它。

我了解到这种现象称为“幻像读取”,这是可能的,因为我的数据库的隔离级别为REPEATABLE-READ


@KevinChristopherHenry“问题是释放锁后,查询不会重新运行;已经选择了行”您确定这是这样吗?为什么READ COMMITTED表示释放锁定后选择未运行?我认为隔离级别定义了查询运行时看到的数据快照,而不是查询运行时看到的快照。在我看来,选择是在锁定释放之前还是之后进行的,都与隔离级别正交。而且,根据定义,被阻止的查询只有在取消阻止后才选择行吗?

对于它的价值,我尝试通过在外壳程序中打开与数据库的两个单独连接并发出一些查询来对其进行测试。首先,我开始交易,并得到了一个锁“从ID的MyModel订单中选择*以进行更新”。然后,在第二步中,我做了同样的事情,导致选择被阻塞。然后回到第一行,插入新行,并提交事务。然后在第二个中,查询解除阻塞,并返回新行。这使我认为我的假设是正确的。

P.S。我终于真正地阅读了您阅读的“不良结果”文档,并明白了这一点-在该示例中,它似乎忽略了未预先选择的行,因此这将得出结论,我的第二个查询不会选择在新行上。但是我在外壳中进行了测试。现在我不确定该怎么做。