django数据库连接


最近总会遇到MySQL server has gone away的报错,然后就看了一下django的数据库连接这一块。

django数据库连接

ORM中数据库连接用到的connections,从django.db模块引入,属于ConnectionHandler对象。

# django.db.__init__.py

# django ORM中用到的数据库连接来源
connections = ConnectionHandler()

# 请求开始之前重置所有连接
def reset_queries(**kwargs):
    for conn in connections.all():
        conn.queries_log.clear()
signals.request_started.connect(reset_queries)

# 请求开始结束之前遍历所有已存在连接,关闭不可用的连接
def close_old_connections(**kwargs):
    for conn in connections.all():
        conn.close_if_unusable_or_obsolete()
signals.request_started.connect(close_old_connections)
signals.request_finished.connect(close_old_connections)

我理解的ConnectionHandler类是一个数据库连接管理器,负责根据不同数据库后端创建数据库连接,保存连接,给应用方提供连接,以及关闭所有连接。 这里通过django信号的方式,在请求开始之前以及请求结束之后关闭失效数据库连接。

# django.db.utils.py

class ConnectionHandler(object):
    def __init__(self, databases=None):
        # 获取数据库配置
        self._databases = databases
        # 从当前线程变量获取所有数据库连接
        self._connections = local()

    # 获取数据库连接关键逻辑
    def __getitem__(self, alias):
        # 首先直接从当前线程变量获取
        if hasattr(self._connections, alias):
            return getattr(self._connections, alias)
        
        # 重新建立数据库连接并写入当前线程变量
        self.ensure_defaults(alias)
        self.prepare_test_settings(alias)
        db = self.databases[alias]
        backend = load_backend(db['ENGINE'])
        # django.db.backends.mysql.base.DatabaseWrapper
        conn = backend.DatabaseWrapper(db, alias)
        setattr(self._connections, alias, conn)
        return conn

ConnectionHandler_connections表示当前数据库连接集合,是一个ThreadLocal对象,是和线程绑定在一起的。在整个线程生命周期内,_connections属于全局变量,但是当线程一旦关闭,_connections也消失了。

关键逻辑在于__getitem__方法,当通过别名获取数据库连接时,首先从当前线程变量中获取连接,获取不到就根据别名创建新的数据库连接,并将连接写入ThreadLocal

通过CONN_MAX_AGE设置连接存活时间

django 1.6开始支持持久数据库连接,通过参数CONN_MAX_AGE设置每个连接的最大存活时间。默认值是0,设置为None表示无限制的持久连接。

# django.db.backends.base.base.py

class BaseDatabaseWrapper(object):
    def connect(self):
        self.in_atomic_block = False
        self.savepoint_ids = []
        self.needs_rollback = False
        # 根据CONN_MAX_AGE参数设置连接的关闭时间
        max_age = self.settings_dict['CONN_MAX_AGE']
        self.close_at = None if max_age is None else time.time() + max_age
        ... ...
        
    def close_if_unusable_or_obsolete(self):
        if self.connection is not None:
            if self.get_autocommit() != self.settings_dict['AUTOCOMMIT']:
                self.close()
                return

            # 发生异常,检查连接是否可用,不可用关闭连接
            if self.errors_occurred:
                if self.is_usable():
                    self.errors_occurred = False
                else:
                    self.close()
                    return
            # 设置了超时时间,并且连接超时,关闭连接
            if self.close_at is not None and time.time() >= self.close_at:
                self.close()
                return

数据库连接在建立的时候会根据CONN_MAX_AGE参数设置连接的close_at属性,表示连接失效时间。再看上面👆django.db.__init__.py的代码,通过信号方式,每次请求开始以及结束的时候,会调用close_if_unusable_or_obsolete方法,判断当连接超时或者处在不可恢复状态时则关闭连接。

总结

1. django的数据库连接是保存到线程变量的 数据库连接是全局的,但只存在于当前线程中,如果线程关闭,数据库连接也不存在了。

2. 可以通过CONN_MAX_AGE参数配置数据库连接的存活时间 即使设置了CONN_MAX_AGE参数,也是在线程依然存活的情况下,数据库连接能够存活的时间。

需要注意的两点是:


参考阅读:
通过CONN_MAX_AGE优化Django的数据库连接
Django Doc