坑—— Django ORM 连接超时的坑

Posted linagcheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了坑—— Django ORM 连接超时的坑相关的知识,希望对你有一定的参考价值。

问题

数据库链接丢失异常

django.db.utils.OperationalError: (2013, ‘Lost connection to mysql server during query‘)

查询mysql全局变量SHOW GLOBAL VARIABLES;可以看到wait_timeout,此变量表示连接空闲时间,MySQL默认的时间是8小时。如果客户端使用一个连接查询多次数据库,如果连续查询则没有问题,如果查询几次后停顿超过wait_timeout后再次查询就会出现数据库连接丢失

Django中的数据库连接

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

解决方案:

方案一:(不一定能解决)

使用CONN_MAX_AGE减少数据库请求

每次请求都会创建新的数据库连接,这对于高访问量的应用来说完全是不可接受的。因此在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就不能大于一分钟。

具体解决

# django项目中 settings.py

DATABASES = {
    "default": {
            ‘ENGINE‘: ‘django.db.backends.mysql‘,
            ‘NAME‘: ‘‘,
            ‘USER‘: ‘‘,
            ‘PASSWORD‘: ‘‘,
            ‘HOST‘: ‘‘,
            ‘CONN_MAX_AGE‘: 9*60  # 比wait_timeout小一些
    }
}

方案二:

django关闭连接流程

对django源码中CONN_MAX_AGE对django关闭失效连接的方法django.db.close_old_connections()

# 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实现特定事件时执行此方法(类似于 触发器),两个特定事件是请求开始和请求结束。

如果报错的是在一次请求中,所以此法通常无效,仅仅是实现每个请求关闭并重新建立连接。

解决原理:

在每一次查询操作前,通过调用close_old_connections()主动关闭闲置连接,主动关闭闲置连接,当再次操作数据库,会自动建立新的连接。

  • 一般情况不会出现此类问题,因为一个请求中不间断进行数据库查询,无需每个请求调用此方法。

  • 有时候一个请求中数据量较大,会查询数据库后进行一段时间其他(不涉及数据库)处理,比如先查询一些数据,然后进行超过MySQL的wait_timeout的长时间的操作。经过非常长时间,那么请求时建立的连接conn_1已经被MySQL断开,而在项目中在经过长时间的操作后却仍然使用conn_1,就会抛出异常,应该调用django.db.close_old_connections关闭无效连接,重新连接MySQL,在操作数据库,防止连接丢失

具体方法

from django.db import close_old_connections

def func(request):
    models.User.objects.filter(name=‘xxx‘)
    
    time.sleep(9*60*60)		# 模拟执行长时间耗时操作,时间超过MySQL的 wait_timeout
    
    close_old_connections()	# 先手动关闭无效时间
    models.User.objects.filter(name=‘xxx‘)	# 再进行查询

以上是关于坑—— Django ORM 连接超时的坑的主要内容,如果未能解决你的问题,请参考以下文章

rpyc遇到的坑

Mac 修改 PermitRootLogin 权限遇到的坑

几个django 2.2和mysql使用的坑

使用ABP框架踩过的坑系列5

(Django)气流中的 ORM - 有可能吗?

关于django操作orm的一些事--反向生成orm连接多个数据库