我们生产中的一个线程遇到了一个错误,现在正在产生InvalidRequestError: This session is in 'prepared' state; no further SQL can be emitted within this transaction.错误,每次请求都有一个查询它服务的余生!现在已经好几天了!这怎么可能,我们如何防止它向前发展?
我们在uWSGI上使用Flask应用程序(4个进程,2个线程),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
Run Code Online (Sandbox Code Playgroud)
...... self.session.commit()当事务无效时,以某种方式设法调用.这导致sqlalchemy.exc.InvalidRequestError: Can't reconnect until invalid transaction is rolled back输出到stdout,无视我们的日志记录配置,这是有道理的,因为它发生在应用程序上下文拆除期间,这从来不应该引发异常.我不确定如果没有response_or_exec设置,交易如何变得无效,但这实际上是AFAIK的较小问题.
更大的问题是,当"准备好的'状态"错误开始时,并且从那时起就没有停止过.每次这个线程提供命中数据库的请求时,它就是500s.每个其他线程似乎都没问题:据我所知,即使是在同一进程中的线程也没问题.
SQLAlchemy邮件列表中有一个关于"'准备好的'状态"错误的条目,说明如果会话开始提交但尚未完成,而其他东西试图使用它.我的猜测是,这个帖子中的会话永远不会self.session.remove()迈出这一步,现在它永远不会.
我仍然觉得这并没有解释这个会话如何在请求中持续存在.我们还没有修改Flask-SQLAlchemy对请求范围会话的使用,因此会话应该返回到SQLAlchemy的池并在请求结束时回滚,即使是错误的(尽管可能不是第一个,因为在应用程序上下文中引发的内容被拆除了).为什么回滚没有发生?如果我们每次都看到stdout上的"无效事务"错误(在uwsgi的日志中),我就能理解它,但我们不是:我第一次只看到它一次.但每次500秒发生时,我都会看到"准备好的状态"错误(在我们的应用程序日志中).
我们已经关闭expire_on_commit了session_options,我们已经开启了SQLALCHEMY_COMMIT_ON_TEARDOWN.我们只是从数据库中读取,而不是写作.我们还使用Dogpile-Cache进行所有查询(使用memcached锁,因为我们有多个进程,实际上是2个负载均衡的服务器).缓存会在每分钟到期,因为我们的主要查询.
重启服务器似乎已经解决了问题,这并不奇怪.也就是说,我希望能再次看到它,直到我们弄清楚如何阻止它.benselme(下面)建议编写我们自己的拆解回调,并在提交时进行异常处理,但我觉得更大的问题是该线程在其余生中被搞砸了.事实上,在一两个请求之后这并没有消失,这让我很紧张!
我们正在尝试调整一些Oracle JVM垃圾收集选项,并且一位开发人员试图使用它-XX:PretenureSizeThreshold来确保立即将大量对象放入Tenured中.我很确定假设数组大小等于或超过其中所有对象的总大小.
但是在Java中,不是对象数组只是引用数组?即数组中的每个对象,以及数组对象本身,在内存中是独立的,并被垃圾收集器分开处理?我认为如果有数百万个条目,数组对象仍然可以变得相当大,但如果每个对象比引用大得多,它不应该接近它"包含"的对象的总大小.
我认为有混乱,因为AFAIK,在C:
struct真正存储structs 的s 数组.struct秒.我非常确定Java总是使用1.用于基本类型的数组,并且总是使用2.用于对象数组,而C可以用于任何类型......?
如果我ArrayList经常使用append()s(如我们手头的情况那样)怎么办?是仅复制了数组,而不是数组中的对象?此外,当复制数组时,即使旧数组在Tenured中,新数组也会在Eden中启动,对吧?
StringIO其代码中有以下注释:
Notes:
- Using a real file is often faster (but less convenient).
- There's also a much faster implementation in C, called cStringIO, but
it's not subclassable.
Run Code Online (Sandbox Code Playgroud)
“真实文件通常更快”这句话对我来说真的很奇怪:写入磁盘怎么能胜过写入内存呢?我尝试分析这些不同的案例,得到的结果与这些文档以及这个问题的答案相矛盾。这个另一个问题确实解释了为什么 cStringIO 在某些情况下速度较慢,尽管我在这里没有进行任何连接。该测试将给定量的数据写入文件,然后查找开头并将其读回。在“新”测试中,我每次都创建一个新对象,而在“相同”测试中,我会截断并为每次重复重用相同的对象,以排除开销来源。对于使用数据量较小但数据量较大的临时文件来说,这种开销很重要。
代码在这里。
Using 1000 passes with size 1.0KiB
New StringIO: 0.0026 0.0025 0.0034
Same StringIO: 0.0026 0.0023 0.0030
New cStringIO: 0.0009 0.0010 0.0008
Same cStringIO: 0.0009 0.0009 0.0009
New tempfile: 0.0679 0.0554 0.0542
Same tempfile: 0.0069 0.0064 0.0070
==============================================================
Using 1000 passes with …Run Code Online (Sandbox Code Playgroud)