在 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";
这个丢失连接的现象只发生在后台脚本中,而在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)