PonyORM (Python) "Value was updated outside of current transaction" 但它不是

PonyORM (Python) "Value was updated outside of current transaction" but it wasn't

我在磁盘上使用带有 Sqlite3 数据库的 Pony ORM 版本 0.7,运行 遇到这个问题:我正在执行 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) and at this documentation (https://docs.ponyorm.com/transactions.html) 但都没有解决我的问题。

知道这里会发生什么吗?

Pony默认使用乐观并发控制。对于每个属性,Pony 都会记住它的当前值(可能被应用程序代码修改)以及从数据库中读取的原始值。在 UPDATE 期间,Pony 检查数据库中列的值是否仍然相同。如果这个值被改变了,Pony 会假设一些并发事务做了它,并抛出异常以避免 "lost update" 的情况。

如果你执行一些原始的 SQL 查询,Pony 不知道数据库中到底修改了什么。所以当Pony遇到counter值被改变的时候,会误认为这个值被另一个事务改变了。

为了避免该问题,您可以将 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 开始,您还可以为 db_session 指定 optimistic=False 选项,以关闭对使用原始 SQL 查询的特定事务的乐观检查:

with db_session(optimistic=False):
    ...

@db_session(optimistic=False)
def some_function():
    ...

现在还可以为属性指定 optimistic=False 选项,而不是指定 volatile=True。那么 Pony 不会对该属性进行乐观检查,但仍会考虑将其视为非易失性