Django和MariaDB/MySQL数据库:select_for_update是否锁定子查询中的行?是否导致死锁?

dkqlctbz  于 2022-11-08  发布在  Go
关注(0)|答案(1)|浏览(207)

软件:Django 2.1.0,Python 3.7.1,Linux操作系统Ubuntu 18 LTS
我们最近给一个新的应用程序增加了一些负载,并开始观察到很多死锁。经过大量的挖掘,我发现Django select_for_update查询导致了一个带有多个子查询(3或4个)的SQL。在我目前看到的所有死锁中,至少有一个事务涉及到带有多个子查询的SQL。

**我的问题是... select_for_udpate是否锁定了所涉及的每个表中的记录?**在我的示例中,是否锁定了主SELECT表中的记录以及子查询所使用的其他表中的记录?还是仅锁定了主SELECT表中的记录?

来自Django文档:
默认情况下,select_for_update()会锁定查询选择的所有行。例如,除了查询集模型的行之外,select_related()中指定的相关对象的行也会被锁定。
但是,我并没有使用select_related(),至少我没有明确地把它放进去。
我的应用程序摘要:

with transaction.atomic():
   ModelName.objects.select_for_update().filter(...)
   ...
   update record that is locked
   ...
  • 50多个客户端同时向数据库发送查询
  • 其中一些查询要求相同的记录。这意味着不同的事务将同时运行相同的SQL。

在阅读了大量的资料之后,我做了下面的事情来试图控制死锁:
1-Try/Catch异常错误“1213”(死锁)。发生这种情况时,请等待30秒,然后重试查询。在这里,我依赖于数据库引擎的ROLLBACK函数。此外,打印SHOW ENGINE INNODB STATUS和SHOW PROCESSLIST的输出。但SHOW PROCESSLIST并不提供有用的信息。
2-修改Django select_on_update,使其不再构建包含子查询的SQL。现在,生成的SQL只包含一个WHERE,其中包含值,但不包含子查询。
还有什么可以减少死锁的吗?

mjqavswn

mjqavswn1#

如果在事务内执行select_for_update,则只有在提交或回退整个事务时才会释放该事务。如果将nowait设置为true,则其他并发请求将立即失败,并显示以下消息:3572,'由于无法立即获取锁且设置了NOWAIT,语句已中止。')
因此,如果我们不能使用乐观锁,也不能使事务更短,我们可以在select_for_update中设置nowait=true,如果我们的假设是正确的,我们将看到许多失败。这里,我们可以捕捉死锁失败,并使用回退策略重试它们。这是基于这样的假设:所有人都试图写入同一个东西,如拍卖品。如果不是这样的话,考虑稍微改变一下数据库的设计,使死锁变得常见

相关问题