flask-sqlalchemy模块中mysql连接失效问题记录

flask-sqlalchemy模块中mysql连接失效问题记录

sqlalchemy是python中使用mysql的orm框架
flask中使用sqlalchemy的架构图
连接池

调用方式

  • db.engine.execute(sql): 从mysql的连接池获取一个连接,执行完自动commit,归还连接到连接池中
  • 使用orm的session的方式调用

    1
    2
    3
    4
    db.session.commit()
    db.session.rollback()
    db.session.close()
    db.session.remove(): 底层会调用db.session.close()

线程和session的关系

flask中使用sqlalchemy的时候,每个线程都可以直接用db.session获得session,使用orm的model.query方式,实际上也是调用了session

  • 每个线程有自己的threadlocal的session对象,并且随着线程销毁,会自动释放session,也就是会隐式调用session.remove,释放session的连接

多线程两种使用:

  1. t1=threading.Thread(…);
  2. 线程池: future= pool.submit(…).
    • 方法1的线程使用完以后自动销毁=>session自动销毁=>连接自动释放;
    • 方法2的线程使用完以后归还线程池=>session手动销毁=>连接释放。
  • 不使用线程池=>连接自动释放;
  • 使用线程池=>连接手动释放.
  • 手动释放的方法: db.session.remove()

空闲连接超时与连接释放bug

前面说到使用线程池时,连接没有自动释放,一直维护在线程的threadlocal存储中(tls)。那么这样似乎也没有什么关系,只要线程池大小<连接池大小,这样连接池有空闲连接,每个线程也有自己的连接可以用,一切似乎也相安无事。然而,这里有一个之前没有提到的机制:空闲连接超时回收。

mysql服务会定时清理掉连接超时的连接,这样客户端保存的该连接的引用都失效了

  • 查看mysql超时时间的命令

    1
    2
    show global variables like 'wait_timeout';
    set global wait_timeout=10*60; -- seconds

flask会定期检查连接池里的连接,删除超时的连接,重新向mysql申请连接,然后放入到连接池中,涉及到的配置参数 app.config['SQLALCHEMY_POOL_RECYCLE']

发生bug的可能原因

  1. mysql服务端清除了空闲时间过长的连接;
  2. 线程池中线程一直不销毁,因此持有了活了很久的session;
  3. 活了很久的session持有了空闲很久的连接, 这个连接其实已经被服务端销毁了,因此已经不可用了,但是由于其一直没有归还到连接池中,因此一直没有得到更新。
  4. 此时web服务收到数据请求,使用该线程中的该session中的连接,就会抛异常了,因为连接已经不可用了。

报错提示

1
2
MySQL server has gone away
Can't reconnect until invalid transaction is rolled back