为避免竞争条件,我需要在查询数据库时使用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')
在dbshell中,运行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 query启动的先前交易。
我错过了一些明显的东西吗?请帮助。
答案 0 :(得分:1)
将Django留给其默认的类似自动提交行为这样的操作很容易导致几种错误(根本不能锁定数据库很容易成为另一种结果);细节可能取决于特定RDBMS的RDBMS和/或Django数据库驱动程序。最好使用@commit_on_success
or @commit_manually
or TransactionMiddleware
。
答案 1 :(得分:0)
我认为其他一些线程在某些记录上持有记录锁定的时间太长,并且你的线程正在超时这是一个特定于MYSQL的问题,它不支持nowait。
您可以为innodb_lock_wait_timeout
设置更高的值并重新启动mysql