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

参考资料#

https://stackoverflow.com/questions/57211476/django-orm-leaks-connections-when-using-threadpoolexecutor

comments powered by Disqus