正如上面@fbessho 所指出的,这确实是正确的模式:
try:
<use session>
session.commit()
except:
session.rollback()
但是,有些细微之处可能会破坏错误处理。
在这个例子中(一个虚构的唯一约束违反),回滚不会发生:
class Thing1(Base):
id = Column(BigInteger, primary_key=True)
class Thing2(Base):
id = Column(BigInteger, primary_key=True)
def do_something(s: session, thing_1: Thing1, duplicate_id):
# imagine this violates a unique constraint on Thing2
thing_2 = Thing2(id=duplicate_id)
s.add(thing_2)
try:
# the exception will occur when the commit statement is executed
s.commit()
except Exception as ex:
# this will log details of the exception
logger.error(f"{ex.__class__.__name__}: {ex}")
# referencing thing_1.id will raise a second exception
logger.error(f"Commit failed. Thing1 id was {thing_1.id}.")
s.rollback()
即使 thing_1 与失败的插入无关,也会发生第二个异常。仅引用 thing_1 会引发第二个异常,从而阻止执行回滚。
解决方案 1
这需要更多的开销,但总能奏效。
def do_something_1(s: session, thing_1: Thing1, duplicate_id):
# create a reference that does not rely on the data object
id_for_thing = thing_1.id
# imagine this violates a unique constraint on Thing2
thing_2 = Thing2(id=duplicate_id)
s.add(thing_2)
try:
# the exception will occur when the commit statement is executed
s.commit()
except Exception as ex:
logger.error(f"{ex.__class__.__name__}: {ex}")
# no direct reference to thing_1
logger.error(f"Commit failed. Thing1 id was {id_for_thing}.")
s.rollback()
解决方案 2
只要 thing_1 不受回滚影响,这将起作用。
def do_something_2(s: session, thing_1: Thing1, duplicate_id):
# imagine this violates a unique constraint on Thing2
thing_2 = Thing2(id=duplicate_id)
s.add(thing_2)
try:
# the exception will occur when the commit statement is executed
s.commit()
except Exception as ex:
logger.error(f"{ex.__class__.__name__}: {ex}")
s.rollback()
# thing_1.id can be referenced after rollback
logger.error(f"Commit failed. Thing1 id was {thing_1.id}.")