我正在尝试在金字塔提供的事务管理器中进行一些架构更改。我在回滚后尝试运行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)
)
答案 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