Python和Django OperationalError(2006,'MySQL服务器已经消失')

时间:2013-01-04 19:16:03

标签: python mysql django nginx django-middleware

原文:我最近开始从我的一些旧代码中获取MySQL OperationalErrors,似乎无法追溯问题。由于它之前的工作,我认为它可能是一个破坏了一些东西的软件更新。我正在使用带有nginx的django runfcgi的python 2.7。这是我的原始代码:

views.py

DBNAME = "test"
DBIP = "localhost"
DBUSER = "django"
DBPASS = "password"
db = MySQLdb.connect(DBIP,DBUSER,DBPASS,DBNAME)
cursor = db.cursor()

def list(request):
    statement = "SELECT item from table where selected = 1"
    cursor.execute(statement)
    results = cursor.fetchall()

我尝试过以下操作,但它仍无效:

views.py

class DB:
    conn = None
    DBNAME = "test"
    DBIP = "localhost"
    DBUSER = "django"
    DBPASS = "password"
def connect(self):
    self.conn = MySQLdb.connect(DBIP,DBUSER,DBPASS,DBNAME)
def cursor(self):
    try:
        return self.conn.cursor()
    except (AttributeError, MySQLdb.OperationalError):
        self.connect()
        return self.conn.cursor()

db = DB()
cursor = db.cursor()

def list(request):
    cursor = db.cursor()
    statement = "SELECT item from table where selected = 1"
    cursor.execute(statement)
    results = cursor.fetchall()

目前,我唯一的解决方法是在每个使用mysql的函数中执行MySQLdb.connect()。另外我注意到当使用django的manage.py runserver时,我不会遇到这个问题,而nginx会抛出这些错误。我怀疑我是否超时连接因为list()在启动服务器的几秒钟内被调用。我正在使用的软件是否有任何更新会导致此故障/是否有任何修复?

编辑:我意识到我最近写了一个中间件来守护一个函数,这就是问题的原因。但是,我无法弄清楚为什么。这是中间件的代码

def process_request_handler(sender, **kwargs):
    t = threading.Thread(target=dispatch.execute,
        args=[kwargs['nodes'],kwargs['callback']],
        kwargs={})
    t.setDaemon(True)
    t.start()
    return
process_request.connect(process_request_handler)

13 个答案:

答案 0 :(得分:42)

有时,如果您看到“OperationalError:(2006,'MySQL服务器已经消失')”,那是因为您发出的查询太大了。例如,如果您将会话存储在MySQL中,并且您尝试在会话中放置一些非常大的内容,就会发生这种情况。要解决此问题,您需要增加MySQL中max_allowed_pa​​cket设置的值。

默认值为1048576。

因此,请查看默认值的当前值,运行以下SQL:

select @@max_allowed_packet;

要临时设置新值,请运行以下SQL:

set global max_allowed_packet=10485760;

要更加永久地解决问题,请创建至少包含以下内容的/etc/my.cnf文件:

[mysqld]
max_allowed_packet = 16M

编辑/etc/my.cnf后,如果您不知道如何,则需要重启MySQL或重启机器。

答案 1 :(得分:34)

根据MySQL documentation,当客户端无法向服务器发送问题时,会引发您的错误消息,很可能是因为服务器本身已关闭连接。在最常见的情况下,服务器将在(默认)8小时后关闭空闲连接。这可以在服务器端配置。

MySQL documentation提供了许多其他可能的原因,可能值得研究以确定它们是否适合您的情况。

在每个函数中调用connect()的替代方法(最终可能会不必要地创建新连接)将在连接对象上使用ping()方法进行调查;这可以尝试自动重新连接,从而测试连接。我努力在线找到ping()方法的decent documentation,但this question的答案可能有所帮助。

注意,处理事务时自动重新连接可能很危险,因为重新连接会导致隐式回滚(并且似乎是自动连接不是MySQLdb实现功能的主要原因)。

答案 2 :(得分:1)

检查是否允许在一个线程中创建mysql连接对象,然后在另一个线程中使用它。

如果它被禁止,请使用threading.Local进行每线程连接:

class Db(threading.local):
    """ thread-local db object """
    con = None

    def __init__(self, ...options...):
        super(Db, self).__init__()
        self.con = MySQLdb.connect(...options...)

db1 = Db(...)


def test():
    """safe to run from any thread"""
    cursor = db.con.cursor()
    cursor.execute(...)

答案 3 :(得分:1)

我也一直在努力解决这个问题。我不喜欢在mysqlserver上增加超时的想法。与CONNECTION_MAX_AGE自动重新连接无法正常工作。不幸的是,我最终包装了查询数据库的每个方法,如此

def do_db( callback, *arg, **args):
    try:
        return callback(*arg, **args)
    except (OperationalError, InterfaceError) as e:  # Connection has gone away, fiter it with message or error code if you could catch another errors
        connection.close()
        return callback(*arg, **args)

do_db(User.objects.get, id=123)  # instead of User.objects.get(id=123)

正如您所看到的,我更倾向于捕获异常,而不是每次查询数据库之前对其进行ping操作。因为捕获异常是一种罕见的情况。我希望django能够自动重新连接,但他们似乎refused发出了这个问题。

答案 4 :(得分:0)

这段代码多大了?自从至少0.96以来,Django已经在设置中定义了数据库。我能想到的另一件事是多数据库支持,它改变了一些东西,但即使是1.1或1.2。

即使你需要一个特殊的数据库用于某些视图,我认为你可能最好在设置中定义它。

答案 5 :(得分:0)

SQLAlchemy现在有一篇很好的文章,说明如何使用ping来对你的连接的新鲜感持悲观态度:

http://docs.sqlalchemy.org/en/latest/core/pooling.html#disconnect-handling-pessimistic

从那里开始,

from sqlalchemy import exc
from sqlalchemy import event
from sqlalchemy.pool import Pool

@event.listens_for(Pool, "checkout")
def ping_connection(dbapi_connection, connection_record, connection_proxy):
    cursor = dbapi_connection.cursor()
    try:
        cursor.execute("SELECT 1")
    except:
        # optional - dispose the whole pool
        # instead of invalidating one at a time
        # connection_proxy._pool.dispose()

        # raise DisconnectionError - pool will try
        # connecting again up to three times before raising.
        raise exc.DisconnectionError()
    cursor.close()

进行测试以确保上述工作:

from sqlalchemy import create_engine
e = create_engine("mysql://scott:tiger@localhost/test", echo_pool=True)
c1 = e.connect()
c2 = e.connect()
c3 = e.connect()
c1.close()
c2.close()
c3.close()

# pool size is now three.

print "Restart the server"
raw_input()

for i in xrange(10):
    c = e.connect()
    print c.execute("select 1").fetchall()
    c.close()

答案 6 :(得分:0)

我遇到了这个问题,但没有更改配置的选项。我终于发现问题是在我的50000记录循环中发生了49500条记录,因为这是我再次尝试(在很久以前尝试过之后)打到我的第二个数据库的时间。

所以我改变了我的代码,以便每隔几千条记录,我再次触摸第二个数据库(使用一个非常小的表的count()),并修复它。毫无疑问" ping"或其他一些触摸数据库的方法也可以。

答案 7 :(得分:0)

在我看来,关于此类警告的必然常见问题是您的应用程序已达到MySQL的wait_timeout值。

我在开发的PythonFLASK应用程序中遇到了同样的问题,我很容易解决。

P.S:我使用的是MySQL 5.7.14

var i =0;
function init(){
    if(i<10){
        createNumbers(process);
        i++;
        requestAnimationFrame(init);
    }
}

一个重要的观察结果:如果您通过MySQL批处理模式搜索变量,则值将按原样显示。但如果你执行“SHOW VARIABLES LIKE'等待%';”或者“SHOW VARIABLES LIKE'interactive%';”,为'interactive_timeout'配置的值将出现在两个变量中,我不知道为什么,但事实是,为'/ etc中的每个变量配置的值/mysql/mysql.conf.d/mysqld.cnf',将受到MySQL进程的尊重。

问候!

答案 8 :(得分:0)

这个错误很神秘,因为MySQL没有报告它断开连接的原因,它只是消失了。

这种断线似乎有很多原因。我刚刚发现,如果查询字符串太大,服务器将断开连接。这可能与max_allowed_packets设置有关。

答案 9 :(得分:0)

首先,你应该确保MySQL会话&amp;全球环境wait_timeoutinteractive_timeout值。其次,您的客户应尝试在这些环境值下方重新连接到服务器。

答案 10 :(得分:0)

这可能是由于DB连接已从主线程复制到子线程中。使用python的多处理库生成不同的进程时,我遇到了相同的错误。连接对象在派生期间在进程之间复制,并且在子线程中进行数据库调用时会导致MySQL OperationalErrors。

这里是解决此问题的很好的参考:Django multiprocessing and database connections

答案 11 :(得分:0)

对我来说,这是在调试模式下发生的。

所以我在调试模式下尝试了持久连接,请检查链接:Django - Documentation - Databases - Persistent connections

在设置中:

'default': {
       'ENGINE': 'django.db.backends.mysql',
       'NAME': 'dbname',
       'USER': 'root',
       'PASSWORD': 'root',
       'HOST': 'localhost',
       'PORT': '3306',
       'CONN_MAX_AGE': None
    },

答案 12 :(得分:0)

当您在不去数据库的耗时操作之后尝试使用连接时,可能会发生此错误。由于该连接有一段时间未使用,因此命中 MySQL 超时,并静默断开连接。

您可以尝试在耗时的非 DB 操作之后调用 close_old_connections(),以便在连接不可用时打开新连接。请注意,如果您有交易,请勿使用 close_old_connections()