【问题标题】:Select for update query: Lock wait timeout exceeded error选择更新查询:超过锁定等待超时错误
【发布时间】:2013-07-18 09:37:33
【问题描述】:

为了避免竞争条件,我需要在查询数据库时使用select for update 功能,以便它锁定行直到事务结束。由于 Django 1.3 中不存在 select_for_update 查询,因此我使用类方法解决了这个问题,该方法通过执行原始 sql 查询返回查询集。

#models.py
class AccountDetails(models.Model):
    user = models.OneToOneField(User)
    amount = models.IntegerField(max_length=15,null=True,blank=True)

    @classmethod
    def get_locked_for_update(cls,userid):
        accounts = cls.objects.raw("SELECT * FROM b2b_accountdetails WHERE user_id ="+str(userid)+" FOR UPDATE")
        return accounts[0]

这就是它在视图中的使用方式。

account = AccountDetails.get_locked_for_update(userid)
account.amount = account.amount - fare
account.save()

在最后一行我收到此错误: OperationalError: (1205, 'Lock wait timeout exceeded; try restarting transaction')

在 dbshel​​l 中,运行save() 行后:

mysql> SHOW FULL PROCESSLIST;
+-----+------+-----------+-----------+---------+------+----------+-----------------------------------------------------------------------------------------------------+
| Id  | User | Host      | db     | Command | Time | State    | Info                                                                                                |
+-----+------+-----------+-----------+---------+------+----------+--------------------------    ---------------------------------------------------------------------------+
|  51 | root | localhost | dbname | Query   |    0 | NULL     | SHOW FULL PROCESSLIST                                                                               |
| 767 | root | localhost | dbname | Sleep   |   59 |          | NULL                                                                                                |
| 768 | root | localhost | dbname | Query   |   49 | Updating | UPDATE `b2b_accountdetails` SET `user_id` = 1, `amount` = 68906 WHERE `appname_accountdetails`.`id` = 1 |
+-----+------+-----------+-----------+---------+------+----------+--------------------------    ---------------------------------------------------------------------------+

据我了解,锁应该在第一个数据更改查询(如更新、删除等)时释放。

但是save() 语句被阻塞并一直等待。知道为什么会这样吗?我的想法是,当我调用 account.save() 时,它并没有获取由 select for update 查询启动的上一个事务。

我是否遗漏了一些明显的东西?请帮忙。

【问题讨论】:

  • 你在使用 TransactionMiddleware 吗?这个问题是否发生在 HTTP 请求的上下文中?
  • @AntonisChristofides 不,我没有使用 TransactionMiddleware。是的,它发生在 HTTP 请求的上下文中,但不仅在 HTTP 请求的上下文中。当我从 django shell 运行代码时,也会出现同样的错误。

标签: python mysql django select locking


【解决方案1】:

让 Django 为此类操作保留其默认的类似自动提交的行为很容易导致多种错误(根本不锁定数据库很容易成为另一种结果);详细信息可能取决于该特定 RDBMS 的 RDBMS 和/或 Django 数据库驱动程序。最好使用@commit_on_success or @commit_manually or TransactionMiddleware

【讨论】:

  • 我尝试了这两种方法,但它不起作用。请参阅我更新的问题。当我做account.save() 时,会创建一个新线程。因此它被阻塞了,因为前一个线程已经锁定了该行。
  • 只是为了确定:你尝试@commit_manually来装饰包含account.save()的函数?
  • 是的,我将部分从选择查询移到另一个函数,并仅在该函数上应用了装饰器,而不是视图。
【解决方案2】:

我认为其他线程在某个记录上持有记录锁的时间过长,并且您的线程正在超时,这是不支持 nowait 的 MYSQL 特有的问题。

您可以为innodb_lock_wait_timeout设置更高的值并重新启动mysql

【讨论】:

  • 不,我认为我可以安全地排除任何其他线程持有记录的可能性。因为即使我杀死了SHOW FULL PROCESSLIST; 中显示的所有线程,也会出现此问题。然而,这个调试证实了我最初的猜测:我的选择查询和更新查询在两个不同的线程上运行。我将在我的问题中更新这一发现。
猜你喜欢
  • 2015-10-13
  • 2015-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-05
  • 1970-01-01
  • 2016-12-16
相关资源
最近更新 更多