代码非常简单,如下所示:
from pony.orm import Required, Set, Optional, PrimaryKey
from pony.orm import Database, db_session
import time
db = Database('mysql', host="localhost", port=3306, user="root",
passwd="123456", db="learn_pony")
class TryUpdate(db.Entity):
_table_ = "try_update_record"
t = Required(int, default=0)
db.generate_mapping(create_tables=True)
@db_session
def insert_record():
new_t = TryUpdate()
@db_session
def update():
t = TryUpdate.get(id=1)
print t.t
t.t = 0
print t.t
if __name__ == "__main__":
insert_record()
update()
pony.orm报告异常:pony.orm.core.CommitException:对象TryUpdate[1]已在当前事务之外更新但根本没有其他事务在运行
我的实验表明,只要t.t被改变成与原始值不同的值,小马就可以正常工作,但是当t.t被设置成与原始值相等的值时,它总是报告异常。
我不确定这是否是一个设计决定我需要检查我的输入值是否每次在分配之前都有变化吗?或者我能做些什么来避免这个恼人的异常?
我的小马版本:0.4.8
太多了~~~
最佳答案
小马的作者来了。
此行为是MySQL特有的错误,已在发行版PONYORM0.4.9中修复,请升级。剩下的我的答案是什么引起了这个错误的解释。
出现此错误的原因如下。为了防止丢失更新,Pony ORM使用乐观检查pony跟踪在程序执行期间读取或更改了哪些属性,然后在相应的WHERE
查询的UPDATE
部分添加额外的条件。这样,Pony保证不会因为并发更新而丢失任何数据让我们考虑下一个例子:
@db_session
def some_function()
obj = MyObject[123]
print obj.x
obj.x = 100
在
some_function
退出时,@db_session
装饰器将提交正在进行的事务。在提交之前,对象的数据将通过以下UPDATE
命令保存:UPDATE MyTable
SET x = <new_value>
WHERE id = 123 and x = <old_value>
你可能想知道,为什么添加了这个附加条件
and x = <old_value>
?这是因为小马知道程序看到了属性x
的前一个值,并且可以使用该值来计算相同属性的新值。因此,Pony采取步骤来确保该属性在UPDATE
时刻保持不变。这种方法称为"optimistic concurrency check"(另见维基百科文章"optimistic concurrency control")由于大多数数据库中默认使用的隔离级别不是SERIALIZABLE
,如果不进行此附加检查,则有可能是其他一些事务在我们提交事务之前成功地更新了x
属性的值,然后并发事务写入的值将丢失。当python数据库驱动程序执行
UPDATE
查询时,它返回满足UPDATE
条件的行数。这样小马就能知道更新是否成功。如果结果为1,则表示成功找到并更新了一行,但如果结果为0,则表示该行已被另一个事务修改,现在它不满足WHERE
部分中的条件发生这种情况时,小马会终止当前事务,以防止丢失更新。该错误的原因是,虽然所有其他数据库驱动程序返回根据
WHERE
节条件找到的行数,但默认情况下,MySQLdb
驱动程序返回实际修改的行数!因此,如果属性的新值与同一属性的原始值相同,MySQLdb
会报告0行被修改,而pony(在0.4.9版本之前)错误地认为这意味着行被并发事务修改。从0.4.9版本开始,pony orm告诉MySQLdb
驱动程序以标准方式运行,并返回找到的行数,而不是实际更新的行数。希望这有帮助:)
另外,我发现你的问题只是偶然的,为了可靠地得到关于小马orm的答案,我建议你将问题发送到我们的邮件列表http://ponyorm-list.ponyorm.com。如果您认为您发现了一个bug,可以在这里打开问题:https://github.com/ponyorm/pony/issues。
谢谢你的提问!