回滚后“此交易已关闭”

时间:2019-08-18 22:56:34

标签: python sqlalchemy pyramid

我正在尝试在金字塔提供的事务管理器中进行一些架构更改。我在回滚后尝试运行commit时遇到各种问题:

简化版是:

def get_version(conn):
    try:
        result = conn.execute('SELECT version FROM versions LIMIT 1')
        return result.scalar()
    except:
        conn.rollback()
        return 0

def m_version_table(conn):
    conn.execute('CREATE TABLE versions (version INT)')
    conn.execute('INSERT INTO versions VALUES (1)')

def handle(conn):
    ver = get_version(conn)
    m_version_table(conn)

# task started with pyramid's transaction manager
    with env['request'].tm as tm:
        handle(env['request'].dbsession)

交易是隐式启动的,我可以在日志中看到

 BEGIN (implicit)
 SELECT version FROM versions LIMIT 1
 ()
 ROLLBACK

 BEGIN (implicit)
 CREATE TABLE versions (version INT)
 ()
 INSERT INTO versions VALUES (1)
 ()
 UPDATE versions SET version = %s
 (1,)
 ROLLBACK

如果versions存在(此后我运行其他ALTER),则一切正常。但回滚后,我得到:

Traceback (most recent call last):
  File ".venv/bin/schema_refresh", line 11, in <module>
    load_entry_point('project', 'console_scripts', 'schema_refresh')()
  File ".../schema_refresh.py", line 270, in run
    handle(env['request'].dbsession, tm)
  File ".../transaction-2.4.0-py3.7.egg/transaction/_manager.py", line 140, in __exit__
    self.commit()
  File ".../transaction-2.4.0-py3.7.egg/transaction/_manager.py", line 131, in commit
    return self.get().commit()
...
sqlalchemy.exc.ResourceClosedError: This transaction is closed

即使回滚后已经正确启动了新事务,为什么不能提交下一个事务? ({ROLLBACK后跟BEGIN (implicit)

2 个答案:

答案 0 :(得分:0)

tl;博士
在您的示例中,__exit__尝试提交的似乎不是新事务。
在数据库会话上调用rollback确实会创建一个新的会话事务,但是该事务不会与管理器在上下文中跟踪的事务一起加入。您对execute的后续调用是在新的会话事务中完成的,但是commit在进入上下文时创建的原始第一个事务上被调用。


假设您使用cookiecutter来设置项目,那么您的models.__init__.py可能就是default from the repo

这意味着env['request'].tm返回一个Zope TransactionManager,并且在进入其上下文时,begin()方法将实例化一个Transaction对象和stores it in the _txn attribute

env['request'].dbsession在事务管理器Session之后返回SQLAlchemy registering
TransactionManager的{​​{1}}现在与Transaction的{​​{3}}是joined,应该控制其结尾和结果。

在处理Session调用中引发的异常的同时回滚SessionTransaction会绕过事务管理器。调用其execute()commit()方法(如稍后由rollback()进行的操作一样)仍将使其尝试终止您回滚的__exit__。 此外,没有机制可以与经理加入新交易。

您可以使用事务管理器,也可以选择手动进行事务控制。只要坚持您的选择,不要将两者混在一起。

答案 1 :(得分:0)

您使用的conn.execute不受事务管理器跟踪(默认情况下,它仅跟踪通过ORM完成的更改)。您可以1)修改将zope.sqlalchemy.registry(session)设置为initial_state='changed'的代码,以使默认设置为COMMIT而不是ROLLBACK(如果默认设置不知道有所更改,则避免额外的提交-为了提高性能) )或2)用zope.sqlalchemy.mark_changed(session)标记执行此操作的特定会话。

最后,get_version应该通过与事务管理器进行协调来完成,以使整个事务不会进入不良状态(尽管您回滚了,管理器现在仍被标记为已中止)。为此,请使用tm.savepoint()

def get_version(conn, tm):
    sp = tm.savepoint()
    try:
        result = conn.execute('SELECT version FROM versions LIMIT 1')
        return result.scalar()
    except:
        sp.rollback()
        return 0