我在磁盘上使用Pony ORM 0.7版和Sqlite3数据库,并遇到了这个问题:我正在执行select,然后是更新,然后是select,然后是另一个更新,并收到错误消息
pony.orm.core.UnrepeatableReadError: Value of Task.order_id for
Task[23654] was updated outside of current transaction (was: 1, now: 2)
我已将问题减少到导致问题的最小命令集(即删除任何内容导致问题不会发生):
@db_session
def test_method():
tasks = list(map(Task.to_dict, Task.select()))
db.execute("UPDATE Task SET order_id=order_id*2")
task_to_move = select(task for task in Task if task.order_id == 2).first()
task_to_move.order_id = 1
test_method()
为了完整起见,以下是Task
:
class Task(db.Entity):
text = Required(unicode)
heading = Required(int)
create_timestamp = Required(datetime)
done_timestamp = Optional(datetime)
order_id = Required(int)
另外,如果我从select中删除task.order_id == 2
的约束,则问题不再出现,所以我认为问题与基于自事务开始以来已更改的字段的查询有关,但我不知道为什么错误消息告诉我它被另一个事务更改(除非db.execute
可能在单独的事务中执行,因为它是原始SQL?)
我已经查看了这个类似的问题,但问题不同(Pony ORM reports record "was updated outside of current transaction" while there is not other transaction)和此文档(https://docs.ponyorm.com/transactions.html),但都没有解决我的问题。
任何想法可能会在这里发生什么?
答案 0 :(得分:2)
Pony默认使用乐观并发控制。对于每个属性,Pony会记住它的当前值(可能由应用程序代码修改)以及从数据库中读取的原始值。在UPDATE期间,Pony会检查数据库中列的值是否仍然相同。如果值被更改,Pony会假定某些并发事务执行此操作,并抛出异常以避免“丢失更新”情况。
如果执行一些原始SQL查询,Pony不知道数据库中究竟修改了什么。因此,当Pony遇到计数器值被更改时,它错误地认为该值已被另一个事务更改。
为了避免此问题,您可以将order_id
属性标记为volatile
。然后Pony会假设属性的值可以随时改变(通过触发器或原始SQL更新),并将从乐观检查中排除该属性:
class Task(db.Entity):
text = Required(unicode)
heading = Required(int)
create_timestamp = Required(datetime)
done_timestamp = Optional(datetime)
order_id = Required(int, volatile=True)
请注意,Pony将缓存volatile
属性的值,并且在保存对象之前不会重新读取数据库中的值,因此在某些情况下,您可以在Python中获得过时的值。
<强>更新强>
从版本0.7.4开始,您还可以为optimistic=False
指定db_session
选项,以关闭对使用原始SQL查询的特定事务的乐观检查:
with db_session(optimistic=False):
...
或
@db_session(optimistic=False)
def some_function():
...
此外,现在可以为属性指定optimistic=False
选项,而不是指定volatile=True
。然后Pony不会对该属性进行乐观检查,但仍会考虑将其视为非易失性