当前位置: 首页 > 面试题库 >

无效交易在请求之间持续存在

赵俊侠
2023-03-14
问题内容

我们生产中的一个线程在InvalidRequestError: This session is in 'prepared' state; no further SQL can be emitted within this transaction.整个生命周期中都在遇到每个错误并正在为其服务的每个请求上产生错误!现在已经有好 几天
了!这怎么可能?我们如何防止这种情况继续下去?

背景

我们在uWSGI(4个进程,2个线程)上使用了Flask应用程序,并且Flask-SQLAlchemy为我们提供了到SQL Server的数据库连接。

当我们的生产中的一个线程在Flask-SQLAlchemy方法内部拆除请求时,问题似乎开始了:

@teardown
def shutdown_session(response_or_exc):
    if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:
        if response_or_exc is None:
            self.session.commit()
    self.session.remove()
    return response_or_exc

…并self.session.commit()在交易无效时设法以某种方式致电。这导致sqlalchemy.exc.InvalidRequestError: Can't reconnect until invalid transaction is rolled back无视我们的日志记录配置而将输出输出到stdout,这是有道理的,因为它发生在应用程序上下文崩溃期间,这绝不应该引发异常。我不确定交易 如何
在不进行response_or_exec设置的情况下变为无效,但这实际上是AFAIK较小的问题。

更大的问题是,这是“准备好的状态”错误开始的那一刻,而此后一直没有停止过。每次该线程处理一个命中DB的请求时,即为500s。每个其他线程似乎都不错:据我所知,即使在同一进程中的线程也能正常工作。

胡乱猜测

SQLAlchemy邮件列表中有一个有关“’已准备好’状态”错误的条目,该错误表示如果会话开始提交但尚未完成,并且其他尝试使用它,则会发生该错误。我的猜测是,该线程中的会话从未达到self.session.remove()目标,现在它永远也不会执行。

我仍然觉得这并不能说明此会话如何 在请求中 持续存在。我们尚未修改Flask-
SQLAlchemy对请求范围的会话的使用,因此该会话应返回到SQLAlchemy的池中,并在请求结束时回滚,即使是发生错误的会话也可以回滚(尽管坦白地说,可能不是第一个,
(因为在应用上下文中发生的崩溃)。为什么不进行回滚?如果我们每次都在stdout上(在uwsgi的日志中)看到“无效事务”错误,我就可以理解,但事实并非如此:我第一次只看到一次。但每次出现500秒钟时,我都会在应用日志中看到“’prepared’state”错误。

配置细节

我们已经在中关闭expire_on_commitsession_options,然后又打开了SQLALCHEMY_COMMIT_ON_TEARDOWN。我们仅从数据库中读取,尚未写入。我们还对所有查询都使用Dogpile-
Cache(使用memcached锁,因为我们有多个进程,实际上是2个负载平衡的服务器)。对于我们的主要查询,缓存每分钟都会过期。

2014年4月28日更新:解决步骤

重新启动服务器似乎已解决了该问题,这并不完全令人惊讶。就是说,我希望再次看到它,直到我们弄清楚如何停止它为止。benselme(下)建议编写自己的拆卸回调,并在提交周围进行异常处理,但我觉得更大的问题是线程在整个余生中都被弄乱了。一两个请求后这
一切都没有 消失的事实让我很紧张!


问题答案:

编辑2016-06-05:

解决此问题的PR已于2016年5月26日合并。

烧瓶PR 1822

编辑2015-04-13:

谜团已揭开!

TL; DR:请 务必 使用2014-12-11编辑中的拆解包装配方, 确保 拆解功能成功!

我还使用Flask开始了一项新工作,在我准备好拆包食谱之前,这个问题再次弹出。因此,我重新审视了这个问题,并最终弄清了发生了什么。

正如我认为的那样,每当新请求下线时,Flask都会将新的请求上下文推送到请求上下文堆栈中。这用于支持请求本地全局变量,例如会话。

Flask还具有与请求上下文分离的“应用程序”上下文概念。它旨在支持未发生HTTP的测试和CLI访问等功能。我知道这一点,而且我也知道那是Flask-
SQLA放置其数据库会话的地方。

在正常操作期间,请求和应用程序上下文都在请求开始时被推送,并在结束时弹出。

但是,事实证明,当推送请求上下文时,请求上下文会检查是否存在现有的应用程序上下文,如果存在,则 不会 推送新的应用程序上下文!

因此,如果由于拆解功能的提高而在请求结束时 弹出应用程序上下文,则它不仅会永远存在,甚至不会在其之上推送新的应用程序上下文。

这也解释了我在集成测试中没有理解的一些魔术。您可以插入一些测试数据,然后运行一些请求,即使您不提交,这些请求也将能够访问该数据。这是唯一可行的,因为该请求具有新的请求上下文,但正在重用测试应用程序上下文,因此,它正在重用现有的数据库连接。因此,这确实是一个功能,而不是错误。

就是说,这确实意味着您必须绝对确保您的拆卸功能成功,使用下面的拆卸功能包装器之类的东西。即使没有该功能,这也是一个好主意,以避免泄漏内存和数据库连接,但鉴于这些发现,这一点尤其重要。因此,我将向Flask的文档提交PR。(在这里)

编辑2014-12-11:

我们最后放置的一件事是下面的代码(在我们的应用程序工厂中),该代码包装了所有拆卸函数以确保其记录异常并且不会进一步引发。这样可以确保始终成功弹出应用上下文。显然,这必须
您确定所有拆卸功能都已注册之后进行。

# Flask specifies that teardown functions should not raise.
# However, they might not have their own error handling,
# so we wrap them here to log any errors and prevent errors from
# propagating.
def wrap_teardown_func(teardown_func):
    @wraps(teardown_func)
    def log_teardown_error(*args, **kwargs):
        try:
            teardown_func(*args, **kwargs)
        except Exception as exc:
            app.logger.exception(exc)
    return log_teardown_error

if app.teardown_request_funcs:
    for bp, func_list in app.teardown_request_funcs.items():
        for i, func in enumerate(func_list):
            app.teardown_request_funcs[bp][i] = wrap_teardown_func(func)
if app.teardown_appcontext_funcs:
    for i, func in enumerate(app.teardown_appcontext_funcs):
        app.teardown_appcontext_funcs[i] = wrap_teardown_func(func)

编辑2014-09-19:

好的,结果--reload-on- exception不是一个好主意,如果1.)您正在使用多个线程,并且2.)在请求中终止线程可能导致麻烦。我以为uWSGI会等待该工作程序的所有请求完成,就像uWSGI的“优美的重载”功能一样,但事实并非如此。我们开始遇到这样的问题:线程将在Memcached中获取狗锁,然后由于不同线程中的异常而在uWSGI重载worker时终止该线程,这意味着该锁永远不会释放。

移除已SQLALCHEMY_COMMIT_ON_TEARDOWN解决问题的一部分,尽管在期间的应用拆卸 期间
我们仍然会偶尔出现错误session.remove()。看来这些是由SQLAlchemy问题3043引起的,该问题已在0.9.5版中修复,因此希望升级到0.9.5将使我们能够依赖始终可用的应用程序上下文拆除。

原版的:

首先如何实现仍然是一个悬而未决的问题,但是我确实找到了一种防止这种情况的方式:uWSGI的--reload-on-exception选择。

我们的Flask应用程序的错误处理应该捕获几乎所有内容,因此它可以提供自定义错误响应,这意味着只有最意外的异常才应将其处理到uWSGI。因此,无论何时重新加载整个应用程序都是有意义的。

我们还将关闭SQLALCHEMY_COMMIT_ON_TEARDOWN,尽管我们可能会显式提交而不是编写自己的回调来删除应用程序,因为我们很少写入数据库。



 类似资料:
  • 我正在ASP.NET项目中实现Paypal Express结账功能,该功能需要授权,然后取消或捕获授权金额。我使用的是他们版本=104.0的API。 就我对整个过程的理解而言,我做的每件事都是正确的: > 我在付款详细信息中调用ActionType设置为“authorize”的SetExpressCheckout方法 然后我打电话给DoExprescheckout。这是请求的代码 此请求也返回“成

  • 交付管道的建立和自动化是持续交付的基础 持续集成 更关注代码质量。持续集成是为了确保随着需求变化而变化的代码,在实现功能的同时,质量不受影响。因此,在每一次构建后会运行单元测试,保证代码级的质量。单元测试会针对每一个特定的输入去判断和观察输出的结果,而单元测试的粒度则用来平衡持续集成的质量和速度。 持续集成的核心价值在于1: 持续集成中的任何一个环节都是自动完成的,无需太多的人工干预,有利于减少重

  • 第一。第二步,获取要验证的url,验证程序就可以了。 使用与在请求令牌中签名请求相同的方法,我得到“unauthorized”,“oauth_problem=signature invalid”。 我一个字母一个字母地验证代码,却找不到问题。 步骤,请求令牌- 我有几个函数,但我把代码放在一个函数中,以找出问题: 响应变量得到“unauthorized”,因为“签名无效”。 欢迎任何帮助,提前谢谢

  • 问题内容: 我正在尝试使用Spring @Transactional批注,但是在调用 findAll 方法时出现问题,并且出现以下错误: 人DAO: 这是我的: 我尝试删除此行,但是又出现了一个错误,为什么这行不通? 问题答案: 您必须显式声明对事务管理器注释的支持 添加到您的配置中: tx是xmlns:tx =“ http://www.springframework.org/schema/tx”

  • 它们还提供了一个警告:如果您能够持续部署到测试系统,有时也会使用术语“持续部署”。 这一切让我很困惑。任何更详细的解释(或附带一个例子)都是赞赏的!

  • 问题内容: 我正在经历apache cassandra并从事示例数据插入,检索等工作。 该文档非常有限。 我有兴趣知道 我们可以用cassandra完全取代mysql / oracle之类的关系数据库吗? cassandra支持回滚/提交吗? cassandra客户端(节俭/ hector)是否支持获取关联的对象(将一个超级列的键保存在另一个超级列族中的对象)? 这将帮助我进一步前进。 先感谢您。