1

我正在使用 Pony ORM 0.7 版和磁盘上的 Sqlite3 数据库,并遇到了这个问题:我正在执行选择,然后是更新,然后是选择,然后是另一个更新,并收到错误消息

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)

另外,如果我从我的选择中删除约束task.order_id == 2,则问题不再发生,所以我认为问题与基于事务开始后已更改的字段的查询有关,但我不知道为什么错误消息告诉我它已被不同的事务更改(除非可能db.execute是在单独的事务中执行,因为它是原始 SQL?)

我已经看过这个类似的问题,但问题不同(Pony ORM 报告记录“在当前事务之外更新”而没有其他事务)和本文档(https://docs.ponyorm.com/ transactions.html ) 但都没有解决我的问题。

有什么想法可能会在这里发生吗?

4

1 回答 1

3

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 不会对该属性进行乐观检查,但仍会考虑将其视为非易失性

于 2016-11-29T19:32:26.527 回答