【问题标题】:Issue with Stale data Flask/SqlAlchemy陈旧数据 Flask/SqlAlchemy 的问题
【发布时间】:2019-01-23 07:30:06
【问题描述】:

我有以下设置,在 session.query() SqlAlchemy 返回过时数据:

使用 Gunicorn + supervisor 在 Flask 上运行的 Web 应用程序。 其中一项服务是这样组成的:

  • app.py:

    @app.route('/api/generatepoinvoice', methods=["POST"])
    @auth.login_required
    def generate_po_invoice():
    try:
       po_id = request.json['po_id']
       email = request.json['email']
       return jsonify(response=POInvoiceGenerator.get_invoice(po_id, email))
    except Exception as ex:
        app.logger.error("generate_po_invoice(): " + ex.message)
    

在另一个文件夹中,我有数据库相关的东西:

DatabaseModels(文件夹)

|-->模型.py

|-->Connection.py

这就是 connection.py 文件中包含的内容:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine(DB_BASE_URI, isolation_level="READ COMMITTED")
Session = scoped_session(sessionmaker(bind=engine))
session = Session()
Base = declarative_base()

这是 model.py 文件的摘录:

from DatabaseModels.Connection import Base
from sqlalchemy import Column, String, etc...

class Po(Base):
    __tablename__ = 'PLC_PO'

    id = Column("POId", Integer, primary_key=True)
    code = Column("POCode", String(50))
    etc...

然后我有另一个文件 POInvoiceGenerator.py 包含对数据库的调用以获取一些数据:

import DatabaseModels.Connection as connection
import DatabaseModels.model as model
def get_invoice(po_code, email):
    try:
        po_code = po_code.strip()
        PLCConnection.session.expire_all()
        po = connection.session.query(model.Po).filter(model.Po.code == po_code).first()

    except Exception as ex:
        logger.error("get_invoice(): " + ex.message)

在后续用户调用此服务时,有时我会开始收到错误消息,例如:无法在数据库中找到该特定代码的数据等等。比如数据是否过时等等。

我的第一种方法是在引擎声明中添加isolation_level="READ COMMITTED",然后创建一个作用域会话,但过时的数据读取不断出现。

是否有人知道我的设置是否错误(会话和模型在多个方法和文件中重复使用)

提前致谢。

【问题讨论】:

  • 您能否清楚地添加您拥有的错误消息,显示错误行号和 SQLAlchemy 日志(如果有)。另外,如果您真的想捕获异常,请尝试显示它的类。最好先捕获 sqlachemy NoResultFound 和 StaleDataError,它们会显示具体的错误。
  • SqlAlchemy 中没有实际错误,只是我从数据库中获取的数据已经过时,在某种意义上,已经在外部修改并且已经在数据库中提交,但是当从 python 调用时,数据有点从 SqlAlchemy 的内存中读取
  • “数据有点从 SqlAlchemy 的内存中读取”:如果您首先“从数据库中读取”,然后不从实例中更改任何内容,则后续读取将从“内存”中读取:这个是 SqlAlchemy 的运行方式。请向我们展示一些错误日志,以便更好地了解您的问题。在您显示的代码中,根本没有任何修改,因此数据不会过时......除非您有另一个计算进程在您的 Flask 应用程序运行时更改数据库。
  • "除非您有另一个计算进程在您的 Flask 应用程序运行时更改数据库。" - 不幸的是,这就是正在发生的事情,烧瓶应用程序正在从一个可以从烧瓶应用程序本身外部修改的数据库读取,是一个由各种进程共享的数据库。
  • Web 是无状态/无事务的。如果你想处理你在之前的状态/交易中使用的相同数据,你必须确保它。例如这里首先捕获 NoResultFound (来自 sqlalchemy,使用 query.one() 而不是 query.first() )。然后,例如使用乐观锁定来了解数据是否已在其他地方更改(查看docs.sqlalchemy.org/en/latest/orm/…)。

标签: python flask sqlalchemy gunicorn supervisord


【解决方案1】:

即使@TonyMountax 指出的solution 似乎有效,并让我发现了一些我不知道的关于 SqlAlchemy 的东西,但最后我选择了不同的东西。

我发现 SqlAlchemy 建立的连接是持久的,因为它每次都是从连接池创建的,这不知何故导致数据过时。

我在我的代码中添加了一个 NullPool:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.pool import NullPool

engine = create_engine(DB_URI, isolation_level="READ COMMITTED", poolclass=NullPool)
Session = scoped_session(sessionmaker(bind=engine))
session = Session()

然后我为我所做的每个查询调用一个会话关闭:

session.query("some query..")
session.close()

这将导致 SqlAlchemy 每次都创建一个新连接并从数据库中获取新数据。

希望这是使用它的正确方法,并且可能对其他人有用。

【讨论】:

  • 您应该查看 Flask after_request / before_request,您可以在其中放置代码来打开/关闭会话。然后你只需要在你的代码中放置查询,而忘记每次在 after_request 中调用的 close()。
  • 感谢您的提示!我也在探索这种方式,不得不重写相同的代码总是不是最好的事情。
【解决方案2】:

您实例化数据库连接的方式意味着它们会被重用于下一个请求,并且它们会保留上一个请求的一些状态。 SQLAlchemy 使用会话的概念与数据库进行交互,因此即使您碰巧执行了两次相同的查询,您的数据也不会在单个请求中突然更改。当您使用 ORM 查询功能时,这很有意义。例如,如果您在同一会话期间查询 len(User.friendlist) 两次,但在请求期间接受了好友请求,则在两个位置仍将显示相同的号码。

要解决此问题,您必须在第一次请求时设置会话,然后在请求完成时将其拆除。这样做并非易事,但有一个成熟的项目已经做到了:Flask-SQLAlchemy。它来自 Pallets,Flask 本身和 Jinja2 背后的人。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-22
    • 1970-01-01
    • 1970-01-01
    • 2016-11-01
    相关资源
    最近更新 更多