Django DB Connection Leaking
在使用Django时如果创建了新的线程,在线程中访问了ORM,Django会在每个线程中创建独立的数据库连接。
但是 在线程结束后,Django并不会立即关闭数据库连接,如果在程序中频繁使用线程,可能会因此导致数据库连接数过高。
def connection_test():
def test_run():
task = Task.objects.get(pk=693)
logger.warning(f"get task: {task}")
threading.Thread(target=test_run).start()
上面的代码就会产生妥妥的“连接泄漏”,不过实测连接并不会一直增加,过一段时间连接数会减少,猜测是数据库连接对象被垃圾回收时关闭的连接。
使用下面命令可以查看Mysql的连接数:
mysql> show processlist;
+------+------+-----------+--------+---------+------+----------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+------+------+-----------+--------+---------+------+----------+------------------+
| 1229 | root | localhost | abcdef | Query | 0 | starting | show processlist |
+------+------+-----------+--------+---------+------+----------+------------------+
1 row in set (0.00 sec)
通过代码可以主动关闭数据库连接,使连接数量保持稳定。
def connection_test(self):
def test_run():
task = Task.objects.get(pk=693)
logger.warning(f"get task: {task}")
django.db.connections.close_all() # close db connections
threading.Thread(target=test_run).start()
在代码中显示关闭数据库连接违背了编程时资源管理的基本原则:谁分配谁释放,谁打开谁关闭。
理想的设计应该是上层代码不需要关心连接管理问题,希望Django后面可以增加连接池的能力。
为了方便程序使用,我编写了两个帮助类:
import threading
import concurrent.futures
import django.db
class DbSafeThread(threading.Thread):
def run(self) -> None:
try:
super().run()
finally:
django.db.connections.close_all()
class DbSafeThreadPoolExecutor(concurrent.futures.ThreadPoolExecutor):
@staticmethod
def close_db_connection(future):
django.db.connections.close_all()
def submit(*args, **kwargs):
future = concurrent.futures.ThreadPoolExecutor.submit(*args, **kwargs)
future.add_done_callback(DbSafeThreadPoolExecutor.close_db_connection)
多进程时的连接问题#
Django在使用多进程时会产生另外的连接问题,在一个进程内开启的连接,在fork出新的进程后会无法使用。
所以看到网上的建议都是在启动多进程之前,关闭全部数据库连接。
在使用uwsgi的多进程模式时,也会遇到上述的连接问题,具体可以参考这里:UWSGI Django Init