Django 数据库连接丢失
2024-04-09 19:20:38  阅读数 774

在 Django 工程中,有时候我们需要在服务器上执行一些脚本。这些脚本需要 Django 的运行环境,还需要做一些 ORM 操作。一般大概流程如下:

import os    
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')  
import django
django.setup()

from app.tasks import sometask
 

if __name__ == '__main__':
    # 做一些任务,一般是消费者模型下的consume
    sometask.consume()

但这里非常容易出现一个错误,就是隔一阵子,脚本就会崩溃,并报如下错误:

django.db.utils.OperationalError: (2006, 'MySQL server has gone away')

很明显,这个脚本与数据库的连接由于长时间闲置,被数据库强制关闭了。数据库能够同时支持的连接数是有限的, 例如 MySQL 的默认值是 200.

show variables where variable_name="max_connections";

可以看出,连接是个稀缺资源,所以需要省着用,长时间空占着的连接会被关闭释放。这个时长,可以通过下面的指令获取,MySQL默认是8小时。

show variables where variable_name = "wait_timeout";

为什么 Django Web 服务不会出现连接超时呢?

这个丢失连接的现象只发生在后台脚本中,而在web服务中,好像从来没出现过这个错误。
原来Django已经帮我们处理了。Django默认的做法是,每个请求开始的时候,如果这个请求涉及到数据库,那么就会新开一个连接,请求结束的时候,再主动关闭这个连接。
这样,虽然增加了响应时间,但确保了数据库连接不会超时。
当然,我们也可以修改默认行为,通过修改参数 CONN_MAX_AGE , 可以让数据库连接保持不关闭,直到超时,以提升响应速度。但这个时候要注意,数据库的最大连接数可别超了。

如何解决脚本的数据库连接丢失问题

可以模仿Django的默认行为,在需要访问数据库前,先关闭老连接,再访问数据库即可(ORM会自动新建连接)。

from django.db import close_old_connections
close_old_connections()
do_something_with_db()

也可以在任务完成之后,主动关闭数据库。

from django.db import connection 
sometask.consume()
connection.close()

如果脚本要操作数据库的地方很多,总不能每个地方都去添加一行关闭数据库的代码吧(当然此时可以用装饰器,不过也得每个函数都补一行代码)。

这里也可以用心跳来维持连接,定时用一个不消耗性能的查询来访问数据库,从而达到保持连接的目的。

import os    
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')  
import django
django.setup()

from app.tasks import sometask, heartbeat
from funboost import fsdf_background_scheduler, timing_publish_deco  
  
fsdf_background_scheduler.add_job(  
    timing_publish_deco(heartbeat),  
    'interval',  
    id='heartbeat',  
    seconds=5  
)  
fsdf_background_scheduler.start()

if __name__ == '__main__':
    # 任务
    sometask.consume()
    
    # 心跳
    heartbeat.consume()  
    # 主线程永不结束,否则定时任务在python3.9会报错  
    while 1:  
        time.sleep(10)