Django 数据库参数之CONN_MAX_AGE

官方文档 https://docs.djangoproject.com/en/2.2/ref/databases/#mysql-notes

持久连接

持久连接避免了在每个请求中重新建立到数据库的连接的开销。它们由 CONN_MAX_AGE 定义连接最长生命周期的参数控制 。可以为每个数据库独立设置。

  • 默认值为0,每次请求结束时关闭数据库连接。
  • CONN_MAX_AGE 秒数为正数,启用持久连接。
  • None:无限制的持久连接.

连接管理

Django 在第一次进行数据库查询时会打开一个到数据库的连接。它保持此连接打开并在后续请求中重用它。一旦连接超过定义的最大值 CONN_MAX_AGE 或不再可用时,Django 将关闭连接 。

详细地说,Django 会在需要连接但还没有连接时自动打开一个到数据库的连接——要么因为这是第一个连接,要么因为前一个连接已关闭。

在每个请求开始时,如果连接已达到最大值,Django 会关闭连接。

如果您的数据库在一段时间后终止空闲连接,您应该设置 CONN_MAX_AGE 一个较低的值,以便 Django 不会尝试使用已被数据库服务器终止的连接。(这个问题可能只会影响非常低流量的网站。)

在每个请求结束时,如果连接已达到最大值或处于不可恢复的错误状态,Django 将关闭连接。如果在处理请求时发生任何数据库错误,Django 会检查连接是否仍然有效,如果无效则关闭它。因此,数据库错误最多影响一个请求;如果连接变得不可用,下一个请求将获得一个新的连接。

注意事项

由于每个线程都维护自己的连接,因此您的数据库必须至少支持与工作线程一样多的同时连接。

有时数据库不会被大多数视图访问,例如因为它是外部系统的数据库,或者由于缓存。在这种情况下,您应该设置CONN_MAX_AGE一个较低的值甚至 0,因为维护一个不太可能被重用的连接是没有意义的。这将有助于保持与该数据库的同时连接数较少。

开发服务器为它处理的每个请求创建一个新线程,消除持久连接的影响。不要在开发过程中启用它们。

当 Django 与数据库建立连接时,它会根据所使用的后端设置适当的参数。如果启用持久连接,则不再在每个请求中重复此设置。如果修改连接的隔离级别或时区等参数,则应该在每个请求结束时恢复 Django 的默认值,在每个请求开始时强制使用适当的值,或者禁用持久连接。

Django的数据库连接

Django对数据库的链接处理是这样的,Django程序接受到请求之后,在第一访问数据库的时候会创建一个数据库连接,直到请求结束,关闭连接。下次请求也是如此。因此,这种情况下,随着访问的并发数越来越高,就会产生大量的数据库连接。

这对于高访问量的应用来说完全是不可接受的。因此在Django1.6时,提供了持久的数据库连接,通过DATABASE配置上添加CONN_MAX_AGE来控制每个连接的最大存活时间。具体使用可以参考最后的链接。

这个参数的原理就是在每次创建完数据库连接之后,把连接放到一个Theard.local的实例中。在request请求开始结束的时候,打算关闭连接时会判断是否超过CONN_MAX_AGE设置这个有效期。这是关闭。每次进行数据库请求的时候其实只是判断local中有没有已存在的连接,有则复用。

基于上述原因,Django中对于CONN_MAX_AGE的使用是有些限制的,使用不当,会事得其反。因为保存的连接是基于线程局部变量的,因此如果你部署方式采用多线程,必须要注意保证你的最大线程数不会多余数据库能支持的最大连接数。另外,如果使用开发模式运行程序(直接runserver的方式),建议不要设置CONN_MAX_AGE,因为这种情况下,每次请求都会创建一个Thread。同时如果你设置了CONN_MAX_AGE,将会导致你创建大量的不可复用的持久的连接。

CONN_MAX_AGE设置多久

CONN_MAX_AGE的时间怎么设置主要取决于数据库对空闲连接的管理,比如你的MySQL设置了空闲1分钟就关闭连接,那你的CONN_MAX_AGE就不能大于一分钟,不过DBA已经习惯了程序中的线程池的概念,会在数据库中设置一个较大的值。

最好的文档是代码

1. 首先是一次请求开始和结束时对连接的处理

请求开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# django.core.handlers.wsgi.py
class WSGIHandler(base.BaseHandler):
initLock = Lock()
request_class = WSGIRequest
def __call__(self, environ, start_response):
# ..... 省略若干代码
# 触发request_started这个Signal
signals.request_started.send(sender=self.__class__, environ=environ)
try:
request = self.request_class(environ)
except UnicodeDecodeError:
logger.warning('Bad Request (UnicodeDecodeError)',
exc_info=sys.exc_info(),
extra={
'status_code': 400,
}
)
# 请求结束
# 代码位置:django/http/response.py
class HttpResponseBase:
"""
An HTTP response base class with dictionary-accessed headers.
This class doesn't handle content. It should not be used directly.
Use the HttpResponse and StreamingHttpResponse subclasses instead.
"""
def close(self):
for closable in self._closable_objects:
try:
closable.close()
except Exception:
pass
self.closed = True
signals.request_finished.send(sender=self._handler_class)

这里只是触发,那么在哪对这些signal进行处理呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 文件:django.db.__init__.py
from django.db.utils import ConnectionHandler
connections = ConnectionHandler()
# Register an event to reset saved queries when a Django request is started.
def reset_queries(**kwargs):
for conn in connections.all():
conn.queries_log.clear()
signals.request_started.connect(reset_queries)
# Register an event to reset transaction state and close connections past
# their lifetime.
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)

在这里对触发的signal进行了处理,从代码上看,逻辑就是,遍历所有已存在的链接,关闭不可用的连接。

再来看ConnectionHandler代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 文件:django/db/utils.py
class ConnectionHandler(object):
def __init__(self, databases=None):
"""
databases is an optional dictionary of database definitions (structured
like settings.DATABASES).
"""
# databases来自settings对数据库的配置
self._databases = databases
self._connections = local()

@cached_property
def databases(self):
if self._databases is None:
self._databases = settings.DATABASES
if self._databases == {}:
self._databases = {
DEFAULT_DB_ALIAS: {
'ENGINE': 'django.db.backends.dummy',
},
}
if self._databases[DEFAULT_DB_ALIAS] == {}:
self._databases[DEFAULT_DB_ALIAS]['ENGINE'] = 'django.db.backends.dummy'
if DEFAULT_DB_ALIAS not in self._databases:
raise ImproperlyConfigured("You must define a '%s' database" % DEFAULT_DB_ALIAS)
return self._databases

def __iter__(self):
return iter(self.databases)

def all(self):
# 调用__iter__和__getitem__
return [self[alias] for alias in self]

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'])
# 关键在这了,这个就是conn
conn = backend.DatabaseWrapper(db, alias)
# 放到 local里
setattr(self._connections, alias, conn)
return conn

这个代码的关键就是生成对于backend的conn,并且放到local中。backend.DatabaseWrapper继承了db.backends.**base.base.**BaseDatabaseWrapper类的 close_if_unusable_or_obsolete() 的方法,来直接看下这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 文件:django/db/backends/base/base.py
class BaseDatabaseWrapper:
"""
Represents a database connection.
"""
def connect(self):
"""Connects to the database. Assumes that the connection is closed."""
# ....省略其他代码
# 连接数据库时读取配置中的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):
"""
Closes the current connection if unrecoverable errors have occurred,
or if it outlived its maximum age.
"""
if self.connection is not None:
# If the application didn't restore the original autocommit setting,
# don't take chances, drop the connection.
if self.get_autocommit() != self.settings_dict['AUTOCOMMIT']:
self.close()
return
# If an exception other than DataError or IntegrityError occurred
# since the last commit / rollback, check if the connection works.
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

Django 数据库参数之CONN_MAX_AGE
https://flepeng.github.io/021-Python-32-框架-Django-Django-数据库参数之CONN-MAX-AGE/
作者
Lepeng
发布于
2021年8月8日
许可协议