>我最近从 Django 1.4 升级到 Django 1.7,由于我不断收到某些脚本的以下错误消息,有时:
OperationalError: (2006, 'MySQL server has gone away')
脚本是非常长或连续运行的任务,可能涉及几分钟不与数据库通信的阶段,因此连接超时。然而,在我升级之前,这没有问题,因为 Django 似乎会自动重新建立连接。现在没有了,这意味着任务经常在中间停止并失败。
有谁知道发生了什么变化以及我如何解决它?
是否可能与该票证/修复有关:https://code.djangoproject.com/ticket/21463
多谢!
这种行为的原因是持久连接到数据库,这是在 Django 1.6 中引入的。
为防止连接超时错误,您应该在 settings.py
中将 CONN_MAX_AGE
设置为 MySQL 配置(my.cnf
)中小于 wait_timeout
的值。在这种情况下,Django 检测到连接需要在 MySQL 抛出它之前重新打开。MySQL 5.7 的默认值为 28800 秒。
settings.py
:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'CONN_MAX_AGE': 3600,
<other params here>
}
}
文档:https://docs.djangoproject.com/en/1.7/ref/settings/#conn-max-age
my.cnf
:
wait_timeout = 28800
文档:https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_wait_timeout
我有一个正在运行的后台进程rqworker
它执行单独的作业以在某些用户操作后刷新一些数据。
我总是得到OperationalError: (2006, 'MySQL server has gone away')
如果在超过 wait_timeout
秒内没有用户操作。即使我设置CONN_MAX_AGE小于MySQL wait_timeout
.
据我了解,如果 Django 在此超时之前自动检查并关闭其连接,更改CONN_MAX_AGE可能会有所帮助。但是 Django 1.7.x 只在每个请求之前和之后检查它(参见 django/db/init.py#L101-L112 )。
如 Django 票证 15119 中所述,我们可以看到 Django 在执行每个查询之前正在执行 ping 以验证连接是否处于活动状态。此行为已在提交 282b2f4 中修复。
Django 开发人员在 https://code.djangoproject.com/ticket/21597#comment:29 中对所有此类问题给出了一个简短的答案
因此,我的rqworker
过程必须为每个新作业验证连接本身。(注意:如果我们关闭一个连接,那么 Django 将创建一个新的连接)。
我将对作业使用 Django 每个请求方法,并在每个作业之前和之后调用django.db.close_old_connections()
。是的,CONN_MAX_AGE
应该小于MySQL wait_timeout
,因为Django不会检查MySQL server has gone away
django.db.close_old_connections()
方法中。
在 Django 1.9 上:我有一个正在进行的Django Shell在Unix屏幕中运行,无人看管超过48小时。当我回到它并运行一个<some_model>.objects.filter
它扔掉时 OperationalError: (2006, 'MySQL server has gone away')
一个快速的import django.db; django.db.close_old_connections()
为我做了这个技巧。
我找不到 1.9 版 Django Docs 上的close_old_connections()
文档,但这里是它在 Github 上的 Django 代码库中实现的直接链接
在 django 1.6 中,当wait_timeout通过(mysql)时,数据库访问会导致(2006,'MySQL 服务器已经消失')错误。在 django 1.5.1 中
并非如此。在使用运行 django 代码(使用 gearman)的工人时,我注意到了这个错误。
要重现:
通过编辑/etc/mysql/my.cnf 将超时设置为低值在 [mysqld] 下添加以下内容
wait_timeout = 10
interactive_timeout = 10
然后
% Python manage.py shell
>>> # access DB
>>> import django.contrib.auth.models
>>> print list(django.contrib.auth.models.User.objects.all())
>>> import time
>>> time.sleep(15)
>>> print list(django.contrib.auth.models.User.objects.all())
现在你得到错误。
我在网上找到的简单解决方案是在访问之前调用 django.db.close_connection()
>>> import django.db
>>> django.db.close_connection()
>>> print list(django.contrib.auth.models.User.objects.all())
工作正常。
我们也注意到了这一点。上面的答案是将CONN_MAX_AGE设置为小于MySQL/MariaDB的wait_timeout工作 - 对于网络。
但是,对于长时间运行的任务,这似乎不起作用。相反,我们包装它并在每次执行长时间运行的任务之一时关闭连接。
我们将此与我们自己的自定义池相结合。接受它或离开它 - 默认的 Django 控制为零 - 不是我们在生产中喜欢的东西。我们设置了一个最大池,以便在服务器终止连接过多的数据库之前杀死服务器。将其用作任务的装饰器:
@close_db_connection()
def task_do_something():
print 'Hello'
'''
Created on Dec 23, 2017
@author: Kevin
'''
from functools import wraps
def close_db_connection(ExceptionToCheck=Exception, raise_exception=False, notify=False):
"""Close the database connection when we're finished, django will have to get a new one..."""
def deco_wrap(f):
@wraps(f)
def f_wrap(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as e:
raise e
finally:
from django.db import connection;
connection.close();
return f_wrap
return deco_wrap
简短:
-
pip install mysql_server_has_gone_away
-
settings.py:
DATABASES = {
'default': {
'ENGINE': 'mysql_server_has_gone_away'
}
}
博士
就我而言,我正在使用具有长寿命请求(webocket)的django ORM。 CONN_MAX_AGE
在我的情况下不起作用。
一开始我创建了一个包装器,它会尝试捕获错误,但由于 django 延迟加载,不清楚你应该包装它是为了哪些事情。所以我最终在整个项目中复制了这段代码,这是一种痛苦。而不是写例如 User.objects.get(id=3)
我会做do_db(User.objects.get, id=3)
,数据库try: return callback(*args, **kwargs); catch e: conn.close(); callback(*args, **kwargs)
.
在深入研究 django 后端时,我们可以在连接级别迁移此解决方案。因此,每个转到 db 的查询都将用它包装:
settings.py:
DATABASES = {
'default': {
'ENGINE': 'lol'
}
}
lol/base.py:
"""
https://stackoverflow.com/a/60894948/3872976
"""
from django.db.backends.mysql import base
def check_mysql_gone_away(db_wrapper):
def decorate(f):
def wrapper(self, query, args=None):
try:
return f(self, query, args)
except (base.Database.OperationalError, base.Database.InterfaceError) as e:
if 'MySQL server has gone away' in str(e):
db_wrapper.connection.close()
db_wrapper.connect()
self.cursor = db_wrapper.connection.cursor()
return f(self, query, args)
# Map some error codes to IntegrityError, since they seem to be
# misclassified and Django would prefer the more logical place.
if e.args[0] in self.codes_for_integrityerror:
raise base.utils.IntegrityError(*tuple(e.args))
raise
return wrapper
return decorate
class DatabaseWrapper(base.DatabaseWrapper):
def create_cursor(self, name=None):
class CursorWrapper(base.CursorWrapper):
@check_mysql_gone_away(self)
def execute(self, query, args=None):
return self.cursor.execute(query, args)
@check_mysql_gone_away(self)
def executemany(self, query, args):
return self.cursor.executemany(query, args)
cursor = self.connection.cursor()
return CursorWrapper(cursor)
您应该注意,如果您在原子操作期间mysql断开连接,则会遇到事务问题。但不幸的是,没有其他办法可以解决这个问题。
也许超时对某些人来说是一个问题,但是在尝试编写非常大的BLOB字段时,我遇到了这个问题。 我通过增加 mysql 配置文件中允许的最大数据包大小来解决这个问题......
max_allowed_packet=4M
不要忘记在更改为/etc/my.cnf 后重新启动 mysql。 此页面帮助...
http://dev.mysql.com/doc/refman/5.5/en/packet-too-large.html
我面临着同样的问题,我的解决方案在哪里。我是 Django 的新手,我已经迟到了,但我正在发布我的解决方案。也许它会帮助某人。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'your_dabase',
'USER': 'your_user',
'PASSWORD': 'your_password',
'HOST': 'your_host',
'PORT': 'your_port',
'CONN_MAX_AGE': 290,
},
'OPTIONS': {
'timeout':20,
}
}
我添加了CONN_MAX_AGE和选项,它现在运行良好。
这是一个老问题,但我的解决方案不在当前的任何答案中。
我问题的根源在服务器端。通过检查服务器超时设置(使用 show global variables like '%timeout'
),我发现 wait_timeout
变量设置为 120 秒,因此当我尝试检索或保存数据时,任何需要这么长时间的任务都会引发"服务器已消失"异常。添加CONN_MAX_AGE
没有任何区别。
解决方案#1 - 更改服务器的wait_timeout设置(如果您有权这样做)
在服务器中使用set global wait_timeout=1*60*60
(时间以秒为单位)。
解决方案#2 - 在数据库空闲时间后强制建立新连接
在你的 Django 视图/任务中:
from django.db import connection
from django.db.utils import OperationalError
cursor = connection.cursor()
cursor.execute('SELECT 1') # <-- no issues here
# time consuming code...
try:
cursor.execute('SELECT 1')
except OperationalError:
connection.connect()
cursor = connection.cursor()
cursor.execute('SELECT 1')
观察:
这也应该适用于ORM请求
姜戈版本 3.1.6