最近总会遇到 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 属性,表示连接失效时间。再看上面:point_up_2: django.db.__init__.py 的代码,通过信号方式,每次请求开始以及结束的时候,会调用 close_if_unusable_or_obsolete 方法,判断当连接超时或者处在不可恢复状态时则关闭连接。
总结
1. django的数据库连接是保存到线程变量的数据库连接是全局的,但只存在于当前线程中,如果线程关闭,数据库连接也不存在了。
2. 可以通过CONN_MAX_AGE参数配置数据库连接的存活时间即使设置了CONN_MAX_AGE参数,也是在线程依然存活的情况下,数据库连接能够存活的时间。
需要注意的两点是:
·CONN_MAX_AGE 应该小于数据库本身的最大连接时间 wait_timeout ,否则应用程序可能会获取到连接超时的数据库连接,这时会出现 MySQL server has gone away 的报错。
·如果部署方式采用多线程,最大线程数不能大于最大数据库连接数。另外,开发模式下(runserver),由于每条请求都是创建一个新的 Thread ,就不要使用 CONN_MAX_AGE 参数了,这样在老的请求线程中保存的数据库连接根本不能复用。
来源:rainybowe