我偶尔会遇到同样令人讨厌的错误:
OperationalError: (1305, 'SAVEPOINT {{name}} does not exist')
谷歌搜索并没有让它更清楚,除了它是一种“正常”的并发问题。所以它是非确定性的,很难在开发环境中重现。
幸运的是,我能够通过使生产应用程序日志记录足够详细来对其进行本地化。
原因
在 MySQL 中,有一些操作可以隐式结束事务:
- DDL 语句(例如
CREATE TABLE
,ALTER TABLE
等)导致隐式提交。众所周知,MySQL 中的 DDL 不是事务性的,
OperationalError: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
并OperationalError: (1205, 'Lock wait timeout exceeded; try restarting transaction')
导致隐式回滚。
所以第二种情况确实有点“正常”。它可以用以下代码表示:
# db is an example database connection object, which
# - supports nested (stacked) transactions,
# - has autocommit on.
db.begin() # START TRANSACTION
try:
# no-conflict op
db.update()
db.begin() # SAVEPOINT sp1
try:
# conflict op,
# e.g. attempt to change exclusively locked rows by another transaction
db.update()
db.commit() # RELEASE SAVEPOINT sp1
except:
# Everything interesting happens here:
# - the change attempt failed with OperationalError: (1213, 'Deadlock...'),
# - the transaction is rolled back with all the savepoints,
# - next line will attempt to rollback to savepoint which no longer exists,
# - so will raise OperationalError: (1305, 'SAVEPOINT sp1 does not exist'),
# - which will shadow the original exception.
db.rollback() # ROLLBACK TO SAVEPOINT sp1
raise
db.commit() # COMMIT
except:
db.rollback() # ROLLBACK
raise
更新
请注意,上述关于异常阴影的内容是针对 Python 2 进行的。Python 3 实现了异常链接,并且在出现死锁的情况下,回溯将具有所有相关信息。