wake-up-neo.com

Python und Django OperationalError (2006, 'MySQL-Server ist weg')

Original: Ich habe vor kurzem angefangen, MySQL OperationalErrors aus einem Teil meines alten Codes zu beziehen und kann das Problem scheinbar nicht zurückverfolgen. Da es vorher funktioniert hat, dachte ich, es könnte ein Software-Update gewesen sein, das etwas kaputt gemacht hat. Ich benutze Python 2.7 mit Django Runfcgi mit Nginx. Hier ist mein ursprünglicher Code:

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()

Ich habe folgendes versucht, aber es funktioniert immer noch nicht:

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()

Derzeit besteht meine einzige Problemumgehung darin, MySQLdb.connect() in jeder Funktion auszuführen, die mysql verwendet. Ich habe auch bemerkt, dass ich bei der Verwendung von Djangos manage.py runserver dieses Problem nicht hätte, während nginx diese Fehler auslöst. Ich bezweifle, dass die Zeitüberschreitung bei der Verbindung auftritt, da list() innerhalb von Sekunden nach dem Starten des Servers aufgerufen wird. Gab es Updates für die Software, die ich verwende, die dazu führen könnte, dass dies fehlschlägt, oder gibt es eine Korrektur dafür?

Edit: Mir wurde klar, dass ich vor kurzem ein Middleware geschrieben habe, um eine Funktion zu dämonisieren, und dies war die Ursache des Problems. Ich kann jedoch nicht herausfinden warum. Hier ist der Code für die Middleware

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)
26
Franz Payer

Wie in der MySQL-Dokumentation beschrieben, wird eine Fehlermeldung ausgegeben, wenn der Client keine Frage an den Server senden kann. In den meisten Fällen schließt der Server nach einer (Standardeinstellung) von 8 Stunden eine untätige Verbindung. Dies ist serverseitig konfigurierbar.

Die MySQL-Dokumentation gibt eine Reihe anderer möglicher Ursachen an, die sich lohnen sollten, um herauszufinden, ob sie zu Ihrer Situation passen.

Eine Alternative zum Aufruf von connect() in jeder Funktion (die möglicherweise unnötig neue Verbindungen erstellt) wäre die Untersuchung der ping()-Methode für das Verbindungsobjekt. Dies testet die Verbindung mit der Option, eine automatische Wiederverbindung durchzuführen. Ich hatte Mühe, online eine anständige Dokumentation für die ping()-Methode zu finden, aber die Antwort auf diese Frage könnte hilfreich sein. 

Beachten Sie, dass das automatische Wiederverbinden eine gefährliche Rolle bei der Verarbeitung von Transaktionen spielen kann, da die Wiederverbindung scheinbar zu einem impliziten Rollback führt (und der Hauptgrund dafür ist, dass Autoreconnect keine Funktion der MySQLdb-Implementierung ist).

34

Wenn Sie "OperationalError: (2006, MySQL-Server ist nicht mehr vorhanden") angezeigt wird, liegt dies möglicherweise daran, dass Sie eine zu große Abfrage ausgeben. Dies kann beispielsweise der Fall sein, wenn Sie Ihre Sitzungen in MySQL speichern und versuchen, etwas wirklich Großes in die Sitzung zu integrieren. Um das Problem zu beheben, müssen Sie den Wert der Einstellung "max_allowed_packet" in MySQL erhöhen.

Der Standardwert ist 1048576.

Sehen Sie sich den aktuellen Wert für den Standardwert an und führen Sie das folgende SQL aus:

select @@max_allowed_packet;

Um vorübergehend einen neuen Wert festzulegen, führen Sie die folgende SQL aus:

set global max_allowed_packet=10485760;

Um das Problem dauerhafter zu beheben, erstellen Sie eine /etc/my.cnf -Datei mit mindestens den folgenden:

[mysqld]
max_allowed_packet = 16M

Nach der Bearbeitung von /etc/my.cnf müssen Sie MySQL oder Ihren Computer neu starten, wenn Sie nicht wissen, wie.

42

Ich habe auch mit dieser Ausgabe zu kämpfen. Ich mag die Idee nicht, das Timeout auf dem mysqlserver zu erhöhen. Autoreconnect mit CONNECTION_MAX_AGE funktioniert auch nicht wie erwähnt. Leider habe ich jede Methode, die die Datenbank abfragt, so verpackt

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)

Wie Sie sehen, ziehe ich die Ausnahme lieber vor, als die Datenbank jedes Mal vor der Abfrage zu pingen. Weil eine Ausnahme ein seltener Fall ist. Ich würde davon ausgehen, dass Django sich automatisch wieder verbindet, aber sie schienen das - abgelehnte Problem.

1
deathangel908

Prüfen Sie, ob Sie in einem Thread ein mysql-Verbindungsobjekt erstellen dürfen, und verwenden Sie es in einem anderen Thread.

Wenn dies nicht zulässig ist, verwenden Sie threading.Local für Per-Thread-Verbindungen:

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(...)
1
Dima Tisnek

Dieser Fehler ist rätselhaft, da MySQL nicht meldet, warum die Verbindung getrennt wurde. Es geht einfach weg.

Es scheint, dass es viele Ursachen für diese Art der Trennung gibt. Ich habe gerade festgestellt, dass, wenn die Abfragezeichenfolge zu groß ist, der Server die Verbindung trennen wird. Dies bezieht sich wahrscheinlich auf die Einstellung max_allowed_packets.

0
Chris Johnson

Zunächst sollten Sie sicherstellen, dass die Werte für die MySQL-Sitzung und die globalen Umgebungen wait_timeout und interactive_timeout festgelegt sind. Zweitens sollte Ihr Client versuchen, die Verbindung mit dem Server unter diesen Umgebungswerten wieder herzustellen.

0
Ryan Chou

Für mich geschah dies im Debug-Modus.

Also habe ich versucht, persistente Verbindungen im Debug-Modus zu testen, siehe Link: Django - Dokumentation - Datenbanken - persistente Verbindungen .

In Einstellungen:

'default': {
       'ENGINE': 'Django.db.backends.mysql',
       'NAME': 'dbname',
       'USER': 'root',
       'PASSWORD': 'root',
       'Host': 'localhost',
       'PORT': '3306',
       'CONN_MAX_AGE': None
    },
0
baloda

Ich hatte dieses Problem und hatte nicht die Möglichkeit, meine Konfiguration zu ändern. Ich fand schließlich heraus, dass das Problem 49500 Datensätze in meiner 50000-Record-Schleife auftrat, da dies der Zeitpunkt war, an dem ich erneut versuchte (nachdem ich vor langer Zeit versucht hatte), meine zweite Datenbank aufzurufen. 

Also habe ich meinen Code geändert, so dass ich alle paar tausend Datensätze die zweite Datenbank erneut anrief (mit einem count () einer sehr kleinen Tabelle), und das hat sie behoben. Zweifellos würde "Ping" oder ein anderes Mittel zum Berühren der Datenbank ebenfalls funktionieren.

0
HelenM

Meiner Meinung nach ist das wichtigste Problem bei einer solchen Warnung die Tatsache, dass Ihre Anwendung den wait_timeout-Wert von MySQL erreicht hat.

Ich hatte das gleiche Problem mit einer PythonFLASK-App, die ich entwickelt und sehr einfach gelöst habe.

PS: Ich habe MySQL 5.7.14 verwendet

$ grep timeout /etc/mysql/mysql.conf.d/mysqld.cnf 
# https://support.rackspace.com/how-to/how-to-change-the-mysql-timeout-on-a-server/
# wait = timeout for application session (tdm)
# inteactive = timeout for keyboard session (terminal)
# 7 days = 604800s / 4 hours = 14400s 
wait_timeout = 604800
interactive_timeout = 14400

Eine wichtige Bemerkung: Wenn Sie die Variablen über den MySQL-Batch-Modus suchen, werden die Werte so angezeigt, wie sie sind. Wenn Sie jedoch "SHOW VARIABLES LIKE" wait% "ausführen, oder "SHOW VARIABLES LIKE 'interactive%';", der für 'interactive_timeout' konfigurierte Wert, wird für beide Variablen angezeigt, und ich weiß nicht warum, aber die Tatsache ist, dass die für jede Variable bei '/ etc /mysql/mysql.conf.d/mysqld.cnf 'wird vom MySQL-Prozess beachtet.

Grüße!

0
ivanleoncz

In SQLAlchemy wird jetzt ausführlich beschrieben, wie Pinging verwendet werden kann, um die Aktualität Ihrer Verbindung pessimistisch zu machen:

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

Von dort,

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()

Und ein Test, um sicherzustellen, dass das oben genannte funktioniert:

from sqlalchemy import create_engine
e = create_engine("mysql://scott:[email protected]/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()
0
Milimetric

Wie alt ist dieser Code? Django hat seit mindestens .96 Datenbanken in den Einstellungen definiert. Ich kann mir nur die Multi-DB-Unterstützung vorstellen, die die Dinge ein wenig verändert hat, aber selbst das war 1.1 oder 1.2.

Selbst wenn Sie für bestimmte Ansichten eine spezielle Datenbank benötigen, ist es wahrscheinlich besser, wenn Sie sie in den Einstellungen definieren. 

0
Tim Baxter

Dies kann darauf zurückzuführen sein, dass DB-Verbindungen in Ihren untergeordneten Threads vom Haupt-Thread kopiert werden. Ich hatte den gleichen Fehler, als ich mit der Multiprocessing-Bibliothek von python verschiedene Prozesse erzeugte. Die Verbindungsobjekte werden während des Forkens zwischen Prozessen kopiert und führen zu MySQL OperationalErrors, wenn DB-Aufrufe im untergeordneten Thread ausgeführt werden.

Hier ist eine gute Referenz, um das zu lösen: Django Multiprocessing und Datenbankverbindungen

0
Vedant Goenka