【问题标题】:how to ensure consistent database update under race condition using flask-sqlalchemy如何使用 flask-sqlalchemy 在竞争条件下确保一致的数据库更新
【发布时间】:2016-01-23 19:32:53
【问题描述】:

这可能是一个老问题。但我是数据库和flask-sqlalchemy 的新手,无法从以前的帖子中找到任何答案。也许答案很简单。

我正在尝试使用 postgresql 将 Flask 应用程序部署到 Heroku。它具有以下模型,即库存跟踪剩余库存的数量。

class Inventory(db.Model):
    __tablename__ = 'inventories'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    count = db.Column(db.Integer)

有一个网络表单,用户可以在其中下订单,该表单将更新由字段 count 跟踪的数据库中剩余的商品数量。下面的代码处理订单并根据是否有足够的物品设置不同的状态。

item = Inventory.query.filter_by(name=form.name.data).first()
order = int(form.count.data)
if item.count > order:
    item.count -= order
    db.session.add(item)
    db.session.commit()
    status = ('%r: %d left' % (item.name, item.count))
else:
    status = '%r not enough left' % item.name

如果用户 1 和用户 2 同时对同一个商品下单,则剩余 5 件商品。也许 user1 的请求首先到达 3 个订单。那么当user2的请求到达时,user1的订单可能还没有提交。所以 user2 也看到剩下 5 个项目。所以最后,数据库中剩下的项目是2; user1 和 user2 都为每个用户成功下了 3 件商品的订单,尽管一开始只剩下 5 件商品,这已经足够了。如何避免这个问题?

This image explains the above situation.

【问题讨论】:

    标签: python database postgresql flask-sqlalchemy


    【解决方案1】:

    为防止过时数据泄漏到数据库中,您需要执行原子更新。

    item.count = Item.count - order
    

    在提交会话后,您仍然需要检查并处理负数。

    这可以很简单

    db.session.commit()
    
    if item.count < 0:
      # roll back the order 
    

    当您提交会话时,SQLAlchemy 会清除其内部缓存并从数据库中重新加载记录。

    【讨论】:

    • 如果还剩5件,user1订购3件,user2也订购3件,最后剩下的件数可能会变成2件。在提交会话后如何防止或检查这一点?谢谢!
    • 您能再解释一下吗?当您对该项目进行初始查询时,它可能会为 user1 和 user2 返回 5,因为 user1 的请求尚未提交。
    • 我的代码检查更新数据库后 的数量。原始值无关紧要。
    • 您的代码只检查负数。如果数量是正数,它会中断,对吗? item = Inventory.query.filter_by(name=form.name.data).first() 可能为 user1 和 user2 提供 item.count = 5。那么在提交之后,item.count 将是 2。所以用户 1 和用户 2 都可以订购 3 件商品,这是不正确的。
    • 如果数量是正数,它什么也不做,因为您能够满足订单。
    【解决方案2】:

    这是@dirn 回答的完整代码示例

    item = Inventory.query.filter_by(name=form.name.data).first()
    order = int(form.count.data)
    item.count = Item.count - order
    db.session.commit()
    
    if item.count < 0:
        status = '%r not enough left' % item.name
    
        # Should rollback/cancel the order
        ...
    
    else:
        status = ('%r: %d left' % (item.name, item.count))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-06-17
      • 2012-04-08
      • 1970-01-01
      • 1970-01-01
      • 2019-07-28
      • 2017-04-18
      • 1970-01-01
      • 2013-08-12
      相关资源
      最近更新 更多