我想要创建一个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,两个进程将创建一个新模型。第一个锁ID 10,然后第二个锁因为10被锁定。第一个是插入11,然后解锁10。然后,第二个打开块,现在它会看到第一个插入的11是最大的,还是仍然看到10,因为这是它阻塞的行?
在select_for_update 文档中,它说:
通常,如果另一个事务已经获得选定行之一的锁,则查询将被阻塞,直到锁被释放。
因此,对于我的例子,我认为这意味着第二个进程将重新运行对最大ID的查询,一旦它解除阻塞并得到11,但我不确定我是否正确地解释了这一点。
注意:我在数据库中使用MySQL。
发布于 2019-03-06 23:47:27
不,我不认为这会奏效。
首先,让我注意,您绝对应该检查您正在使用的数据库的文档,因为Django文档中没有捕获的数据库之间有许多细微的差异。
使用PostgreSQL文档作为指导,问题是,在默认的READ COMMITTED
隔离级别上,阻塞的查询不会被重新运行。当第一个事务提交时,阻塞的事务将能够看到对该行的更改,但它将无法看到添加了新行。
更新命令可以看到不一致的快照:它可以看到并发更新命令对它试图更新的同一行的影响,但它看不到这些命令对数据库中其他行的影响。
因此,10
是返回的内容。
发布于 2019-03-07 04:39:36
编辑:我对这个答案的理解是错误的,只是把它留给文档,以防我想要回到它。
经过一些调查后,我相信这将如预期的那样运作。
原因是这个电话:
MyModel.objects.select_for_update().order_by('id').last().id
SQL Django生成和运行的数据库实际上是:
SELECT ... FROM MyModel ORDER BY id ASC FOR UPDATE;
(对last()
的调用只有在查询集已经评估之后才会发生。)
这意味着,查询在运行两次时都会对所有行进行扫描。这意味着它第二次运行时,它将拾取新行并相应地返回它。
我了解到,这种现象被称为“幻影读取”,这是可能的,因为我的db的隔离级别是REPEATABLE-READ
。
@KevinChristopherHenry“问题是,在释放锁之后,查询不会重新运行;行已经被选中”,您确定它是这样工作的吗?为什么读提交意味着锁释放后没有运行select?我认为隔离级别定义了查询运行时看到的数据快照,而不是~查询运行时看到的快照。在我看来,选择发生在释放锁之前还是之后,这与隔离级别是正交的。根据定义,被阻塞的查询不是在解除阻塞之后才选择行吗?
为了说明它的价值,我尝试通过在shell中打开到我的db的两个单独的连接并发出一些查询来测试它。在第一个过程中,我开始了一个事务,并获得了一个锁'select * from MyModel order by id for update‘。然后,在第二个过程中,我做了同样的操作,导致select块。然后回到第一个,我插入了一个新的行,并完成了事务。然后,在第二个查询中,解除阻塞,并返回新行。这让我觉得我的假设是正确的。
P.S.我终于读到了你读过的“不良结果”文档,我看到了你的观点--在那个例子中,它似乎忽略了没有预选的行,所以这将指向我的第二个查询不会选择新行的结论。但我测试了一个外壳它做到了。现在我不知道该怎么做了。
https://stackoverflow.com/questions/55033473
复制相似问题