这个问题好像和长时间连接数据库但不打开网页时,报错lost connection是一个原因

问题:

写了一个基于apscheduler的定时任务,里面的任务使用了sqlalchemy,大致如下:

scheduler.py
# ...
# 此处省略 import

# 示例任务1
def example_job1():
    exalple_query()
    
# 示例任务2
def example_job2():
    exalple_query()

# 2050-01-01前,每天定时运行

sched.add_job(example_job1, 'cron', hour=19, minute=10, second=00, end_date='2050-01-01')
sched.add_job(example_job2, 'cron', hour=20, minute=00, second=30, end_date='2050-01-01')
# sched.add_job(job_function, 'interval', seconds=10)

sched.start()

使用python scheduler.py跑起来之后,每次都是:
  • 当天可以正常运行两个任务;
  • 第二天跑的时候,第一个任务不能正常运行(在某条query语句上卡住,没有任何输出),但第二个任务可以正常运行。
  • 第三天仍然是,第一个任务不能正常运行,但第二个任务可以正常运行。
原因猜想:
  1. 数据库死锁,导致一直等待?每天到点就锁表,不是很能说得通,也手工排除了一下这种可能:在第二天自动跑的时候,用show processlist一直监视数据库进程,没有看到锁表情况。
  2. 上一次运行过程中的某一个 session 没 commit ?检查代码,没有发现遗漏。
  3. 当进行过一轮增/删/改/查操作后,不使用 sqlalchemy 的时候,session 需要 close?(未测试,代码中没有使用 close,只是用了 commit,所以有可能是这个原因)
  4. SQLAlchemy长时间未请求数据库连接断开?(可能性较大)
解决方案:

分析了一下,觉得SQLAlchemy长时间未请求数据库连接断开的可能性较大。

临时解决方案:在正式运行定时任务之前,先跑一个session.query(),相当于唤醒连接。

修改后如下:

# ...
# 此处省略 import

# 保持连接
def keep_sqlalchemy_connected_job():
    result_no_use = session.query(Example_table).first()

# 示例任务1
def example_job1():
    exalple_query()
    
# 示例任务2
def example_job2():
    exalple_query()

# 2050-01-01前,每天定时运行
sched.add_job(keep_sqlalchemy_connected_job, 'cron', hour=18, minute=55, second=00, end_date='2050-01-01')  # 保持连接
sched.add_job(example_job1, 'cron', hour=19, minute=10, second=00, end_date='2050-01-01')
sched.add_job(example_job2, 'cron', hour=20, minute=00, second=30, end_date='2050-01-01')
# sched.add_job(job_function, 'interval', seconds=10)

sched.start()

拓展阅读:SQLAlchemy长时间未请求数据库连接断开

当较长时间没有去访问网站,再次打开时就会报一个数据库连接失败的错误。这也解释了之前遇到过的lost connection问题。

连接池连接mysql数据库失败,应该是mysql数据库连接超时,mysql数据库配置文件存在以下两个参数,是负责管理连接超时的。

  1. interactive_timeout:针对交互式连接
  2. wait_timeout:针对非交互式连接。

所谓的交互式连接,即在mysql_real_connect()函数中使用了CLIENT_INTERACTIVE选项。说得直白一点,通过mysql客户端连接数据库是交互式连接,通过jdbc连接数据库是非交互式连接。

这两个参数默认都是28800秒,即8小时,也就是超过8小时的连接就会自动失效。这本身并没什么问题,真正的问题是:我们做项目一般使用数据库连接池来获取连接,连接池里的连接可能会较长时间不关闭,等待被使用,这就与mysql连接超时机制起冲突了,当连接池配置永不关闭或者关闭时间超过8小时就会出现我所遇到的问题。

当超过8个小时没有新的数据库请求的时候,数据库连接就会断开,如果我们连接池的配置是用不关闭或者关闭时间超过8小时,这个时候连接池没有回收并且还认为连接池与数据库之间的连接还存在,就会继续连接,但是数据库连接断开了,就会报错数据库连接失败!

解决办法:

  1. 修改mysql配置文件里wait_timeout参数,让这个时间大于连接池的回收时间(修改配置文件需要重启数据库,不推荐!)
  2. 修改数据库连接池的配置,数据库连接池都会带有一个参数:回收时间(就是一定时间内不使用就会回收),修改这个参数的值,不要大于wait_timeout的值即可。在flask-SQLAlchemy中有个配置是SQLALCHEMY_POOL_RECYCLE(多之后对线程池中的线程进行一次连接的回收),如果这个值是-1代表永不回收,Flask-SQLALchemy 自动设定这个值为2小时,我们可以将这个值设置的小于wait_timeout参数的值也就是8小时即可。
  3. 禁用SQLAlchemy提供的数据库连接池
    只需要在调用 create_engine 是指定连接池为 NullPool,SQLAlchemy就会在执行 session.close() 后立刻断开数据库连接。当然,如果 session 对象被析构但是没有被调用 session.close(),则数据库连接不会被断开,直到程序终止。

引用一下别人的总结 (Java版)

原文:一个由session.close()引发的血案

问题很明显了。如果不调用close 方法,
当前连接不会释放回空闲连接池中,一直存在于 活动连接池中,当空闲连接池耗光之后 pop 会从 活动连接池中选取第一个连接,
返回给应用.而这个时候 这个连接从最后次使用到拿出来可能过了好几分钟了。就真的是个处于关闭状态的连接了。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐