事务未按预期工作 SQLAlchemy

时间:2021-02-07 20:47:45

标签: python postgresql flask sqlalchemy

我正在构建一个需要 ACID 的 python 烧瓶后端。用法就像银行余额,一个必须准确增加,而另一个却减少了多少。

我正在将 heroku 与 postgres 以及 SQLAlchemy 用于我的 ORM。我像这样初始化数据库:

app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy()
db.init_app(app)

JSON 正文采用付款接收方的电话号码以及付款发送方的访问令牌。整个路线很大,所以我不会只附加实际的数据库操作的数据验证。所以:

# getting the sender
user = User.verify_auth_token(data.get('access_token'))
# getting recipient (phone is unique)
recipient = db.session.query(User).filter_by(phone=data.get('phone')).first()

if recipient is None:
    return make_error("Recipient does not exist")    
else:
    try:
        good1 = user.update_balance(amount, True, ticker)
        good2 = recipient.update_balance(amount, False, ticker)
        transaction = Transaction(sender_id=user.id, recipient_id=recipient.id, amount=amount, operation="regular")
            
        if good1 is None and good2 is None and transaction is not None:
            db.session.add(transaction)
            db.session.commit()
        else:
            db.session.rollback()
            return make_error(good1)
            return make_error(good2)
            
        response["success"] = True
        response["transaction"] = TransactionNested(many=False).dump(transaction)
        return response

    except:
        db.session.rollback()
        return make_error("Something went wrong")

为了操作余额,我使用了 user 的 update_balance 函数。该函数如下:

def update_balance(self, amount, sender, ticker):
    balance = self.balances.filter_by(curr=ticker).first()
    
    if balance is None:
        return "Balance not supported by user"
    
    if sender:
        if balance.amount < amount:
            return "Not enough funds to send"
        else:
            balance.amount = balance.amount - amount
    else:
        balance.amount = balance.amount + amount

因此,该函数要么返回字符串错误,要么在成功时不返回任何内容。 问题如下:当我运行说 3 个不同的 shell 窗口并运行一个机器人一遍又一遍地执行相同的交易时,两个用户的余额总和不正确。这意味着这里有一些东西使交易变得不酸。例如,如果我在循环中从用户 1 向用户 2 发送 10 次 1000 次,比如 time.sleep(0.5) 和 3 个不同的外壳,当不应该有一个单一的差异单位时,我最终会得到数百个平衡的差异。

任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:0)

update_balance 似乎不见了

db.session.add(balance)

答案 1 :(得分:0)

您似乎正在使用默认隔离级别(为 postgresql 提交读取),但您的代码假定值在执行过程中不会更改。例如,您读取余额 100,替换 10 并准备写入 90,但在此期间余额可能已经是 70(它已被其他工作人员更改),因此您将用不正确的 1 (90) 覆盖此值。< /p>

要改变这种行为,您需要串行运行代码,而不是并发运行:在数据库端(通过将隔离级别更改为可序列化)或在架构上(将所有请求放入队列)。

将事务隔离级别更改为 Serializable 似乎是最直接的解决方案。但它也有缺点:当序列化失败时,您需要调整代码以适应重试事务的逻辑:

SQLALCHEMY_ENGINE_OPTIONS = {'isolation_level': 'SERIALIZABLE'}

运行任务队列可以通过 celery 完成,它也有权衡(设置复杂性等)。