Rollback Objects in Expired Session

我们的代码仓库大量地应用了装饰器rollback_on_failure, 已保证在出错时函数可以回滚事务。 以下是其实现:

def rollback_on_failure(db):
    def deco(f):
        @wraps(f)
        def _(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except:
                db.session.rollback()
                raise
        return _
    return deco

在一次报错中,我发现它无法应用在一种情况里。 假设我们有这样的函数:

@rollback_on_failure(db)
def reduce_stock(id, stock):
    obj = Model.query.get(id)
    obj.stock = Model.stock - stock
    db.session.add(obj)
    try:
        db.session.commit()
    except OperationalError:
        raise InsufficientStock(dict(id=obj.id, stock=obj.stock, other=obj.other))

当代码进入 OperationalError 分支时,这段代码其实是有问题的。 因为数据库的报错,SQLAlchemy 将此次 Session 标为过期,我们无法再从 obj 里获取数据

正确的写法是:

def reduce_stock(id, stock):
    obj = Model.query.get(id)
    obj.stock = Model.stock - stock
    db.session.add(obj)
    try:
        db.session.commit()
    except OperationalError:
        db.session.rollback()
        raise InsufficientStock(id)
  • 首先应该回滚事务,结束当前Session,此时获取数据才有意义
  • 如果要获取最新的stock, 应该在事务结束后,在控制层获取