为什么SQLAlchemy/mysql总是超时?

6 投票
3 回答
2404 浏览
提问于 2025-04-18 01:37

我有两个函数需要执行,第一个函数大约需要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 个回答

1

看起来你并没有得到不同的会话实例。如果第一次查询成功提交了,那么你的会话可能在提交后就过期了。

试着把你的会话的自动过期设置为假,也就是不让它自动过期:

DBSession = scoped_session(sessionmaker(expire_on_commit=False, autocommit=False, autoflush=False,  bind=engine))

然后再进行提交。

2

你的引擎(不是会话)会保持一个连接池。当一个MySQL连接长时间没有被使用,比如几个小时后,MySQL服务器会关闭这个连接,这时如果你再想用这个连接,就会出现“Mysql服务器已经不在了”的错误。如果你只是写了一个简单的单线程脚本,调用create_engine并设置pool_size=1可能就能解决问题。如果不行,你可以使用事件来在从连接池取出连接时进行“心跳检测”。这个很棒的回答里有所有的细节:

SQLAlchemy错误:MySQL服务器已经不在了

2

将 session 赋值为 DBSession 会得到两个不同的会话实例。

这其实是不对的。session = DBSession 只是一个局部变量的赋值,在 Python 中你不能覆盖局部变量的赋值(你可以覆盖实例成员的赋值,但这和这个无关)。

还有一点需要注意的是,scoped_session 默认会生成一个线程本地的会话(也就是说,同一个线程中的所有代码都使用同一个会话)。因为你在同一个线程中调用了 first() 和 second(),所以它们实际上是同一个会话。

你可以选择使用普通的(无作用域的)会话,手动管理你的会话范围,并在两个函数中创建一个新的会话。或者,你可以查看文档,了解如何定义自定义会话范围

撰写回答