问题
To avoid race conditions, I need to use select for update
functionality while querying database so that it locks the row until the end of the transaction. Since select_for_update query is not present in Django 1.3, I have done a workaround it by using a class method which returns query sets by doing a raw sql query.
#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]
This is how it's used in views.
account = AccountDetails.get_locked_for_update(userid)
account.amount = account.amount - fare
account.save()
On the last line I'm getting this error:
OperationalError: (1205, 'Lock wait timeout exceeded; try restarting transaction')
In dbshell, after running the save()
line:
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 |
+-----+------+-----------+-----------+---------+------+----------+-------------------------- ---------------------------------------------------------------------------+
According to my understanding the lock should be released on the first data-altering query like update, delete etc.
But the save()
statement is getting blocked and is kept on wait. Any idea why this is happening? What I think is when I'm calling account.save()
it's not picking up the previous transaction started by select for update query.
Am I missing something obvious? Kindly help.
回答1:
Leaving Django to its default autocommit-like behaviour for such an operation can easily lead to several kinds of errors (not locking the database at all could easily be another outcome); the details probably depend on the RDBMS and/or Django database driver for that particular RDBMS. It would be better to use @commit_on_success or @commit_manually or TransactionMiddleware.
回答2:
I think some other thread is holding a record lock on some record for too long, and your thread is being timed out this a problem specific to MYSQL which does not support nowait.
You can set higher value for innodb_lock_wait_timeout
and restart mysql
来源:https://stackoverflow.com/questions/17719789/select-for-update-query-lock-wait-timeout-exceeded-error