为什么SQLAlchemy/mysql总是超时?
我有两个函数需要执行,第一个函数大约需要4个小时才能完成。它们都使用了SQLAlchemy:
def first():
session = DBSession
rows = session.query(Mytable).order_by(Mytable.col1.desc())[:150]
for i,row in enumerate(rows):
time.sleep(100)
print i, row.accession
def second():
print "going onto second function"
session = DBSession
new_row = session.query(Anothertable).order_by(Anothertable.col1.desc()).first()
print 'New Row: ', new_row.accession
first()
second()
这是我定义DBSession的方式:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy import create_engine
engine = create_engine('mysql://blah:blah@blah/blahblah',echo=False,pool_recycle=3600*12)
DBSession = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
Base = declarative_base()
Base.metadata.bind = engine
第一个函数(first())顺利完成了(大约花了4个小时),然后我看到打印了“开始执行第二个函数”,但紧接着就出现了错误:
sqlalchemy.exc.OperationalError: (OperationalError) (2006, 'MySQL server has gone away')
根据我阅读的文档,我以为把session=DBSession赋值会得到两个不同的会话实例,这样第二个函数(second())就不会超时。我还尝试调整pool_recycle,但似乎没有什么效果。在实际情况中,我不能把first()和second()分成两个脚本:second()必须在first()之后立即执行。
3 个回答
看起来你并没有得到不同的会话实例。如果第一次查询成功提交了,那么你的会话可能在提交后就过期了。
试着把你的会话的自动过期设置为假,也就是不让它自动过期:
DBSession = scoped_session(sessionmaker(expire_on_commit=False, autocommit=False, autoflush=False, bind=engine))
然后再进行提交。
你的引擎(不是会话)会保持一个连接池。当一个MySQL连接长时间没有被使用,比如几个小时后,MySQL服务器会关闭这个连接,这时如果你再想用这个连接,就会出现“Mysql服务器已经不在了”的错误。如果你只是写了一个简单的单线程脚本,调用create_engine
并设置pool_size=1
可能就能解决问题。如果不行,你可以使用事件来在从连接池取出连接时进行“心跳检测”。这个很棒的回答里有所有的细节:
将 session 赋值为 DBSession 会得到两个不同的会话实例。
这其实是不对的。session = DBSession
只是一个局部变量的赋值,在 Python 中你不能覆盖局部变量的赋值(你可以覆盖实例成员的赋值,但这和这个无关)。
还有一点需要注意的是,scoped_session 默认会生成一个线程本地的会话(也就是说,同一个线程中的所有代码都使用同一个会话)。因为你在同一个线程中调用了 first() 和 second(),所以它们实际上是同一个会话。
你可以选择使用普通的(无作用域的)会话,手动管理你的会话范围,并在两个函数中创建一个新的会话。或者,你可以查看文档,了解如何定义自定义会话范围。