2015-12-09 5 views
7

Ich habe eine Flask-Anwendung, die in Apache ausgeführt wird und auf PyMySQL basiert. Die Anwendung bietet eine Reihe von REST-Befehlen. Es läuft unter Python 3.PyMySQL in Flask/Apache gibt manchmal leeres Ergebnis zurück

Ohne die gesamte Quelle, wird das Programm strukturiert wie:

#!flask/bin/python 
import json 
import pymysql 
from flask import * 

# Used to hopefully share the connection if the process isn't restarted 
mysql_connection = None 

# Gets the mysql_connection, or opens it 
GetStoreCnx(): 
    global mysql_connection 
    if (mysql_connection != None): 
     store_connection_string = "" 
     # Get the connection string from the config file 
     with open('config/storedb.json', 'r') as f: 
      store_connection_string = json.load(f) 
     mysql_connection = pymysql.connect(**store_connection_string) 
    return mysql_connection; 


class Server(Flask): 
    def __init__(self, *args, **kwargs): 
     super().__init__(*args, **kwargs) 

# Return results via REST 
@app.route('/results1', methods=['GET']) 
def get_result1(): 
    cnx = GetStoreCnx(); 
    cursor = cnx.cursor(); 
    query = """ 
     SELECT 
      result_name, 
      successful 
     FROM 
      results 
     """ 
    cursor.execute(query) 
    cnx.commit() 
    result = cursor.fetchall() 
    return json.dumps(result) 

# Run server, if needed 
if __name__ == '__main__': 
    app.run(host='0.0.0.0', debug=True) 

Es gibt ein paar mehr REST Anrufe sind - aber sie alle im wesentlichen das gleiche tun (dh - erhalten eine Verbindung, erstellen Sie einen Cursor, führen Sie eine grundlegende Select-Abfrage aus - die manchmal mehr als 2 Felder hat, führen Sie die Abfrage aus, holen Sie das Ergebnis und geben Sie es als JSON-Objekt zurück. Die Commits dort sollten unnötig sein, aber es scheint ein laufendes Problem mit PyMySQL zu sein, das dazu führte, alte Daten zu bekommen.

Das Problem besteht darin, dass diese REST-Aufrufe manchmal leere JSON-Sätze zurückgeben (z. B.). Weitere Untersuchungen zeigten, dass der Aufruf execute manchmal ein vollständig leeres Ergebnis zurückgibt, aber keine Ausnahme auslöst. Dies geschieht regelmäßig - aber nicht die ganze Zeit. Einige Aufrufe geben Werte erfolgreich zurück. Wenn ich versuche, den Anruf in Gang zu halten, bis es ein Ergebnis zurückgibt (wie:

while(cursor.execute(query) < 1): 
    pass 

) der Prozess in eine Endlosschleife eintritt, schließlich (schnell) zu verhindern Apache noch mehr Anfragen von der Wartung.

Der Server ist (im Moment) nur etwa 5 Anrufe pro Sekunde. Das Problem tritt nicht auf, wenn ich den Entwicklungsserver verwende.

Gibt es eine Möglichkeit, diesen Fehler zu verhindern? Ist es ein Fehler in PyMySQL? Was mache ich, um korrekte Verbindungen zu MySQL zu verhindern?

Antwort

1

Sie sind eine globale MySQL-Verbindung zu schaffen, die von der Anwendung verwendet wird, erklärt hovever pymysql ein threadsafety von 1, die die dbapi2 specification nach bedeutet:

1 Threads may share the module, but not connections. 

Als conncurrent Anfragen in der Flasche wird von verschiedenen serviert Threads sollten Sie die Verbindung nicht teilen. Der Grund, dass Sie keine Probleme bei der Verwendung des Entwicklungsservers haben, ist, dass er single-threaded läuft.

Um dies zu vermeiden, können Sie entweder:

  • eine neue Verbindung für jeden Thread erstellen, speichern Sie es als lokalen Thread zur weiteren Verwendung
  • eine neue Verbindung für jede Anforderung erstellen, speichern Sie es in flask.g für weitere verwenden

Dazu Ihre GetStoreCnx Funktion wie folgt geändert werden:

import threading 
thread_local = threading.local() 

def GetStoreCnx():   
    if not hasattr(thread_local, 'mysql_connection'): 
     store_connection_string = "" 
     # Get the connection string from the config file 
     with open('config/storedb.json', 'r') as f: 
      store_connection_string = json.load(f) 
     mysql_connection = pymysql.connect(**store_connection_string) 
     thread_local.mysql_connection = mysql_connection 
    return thread_local.mysql_connection; 

SQLAlchemy macht etwas ähnliches mit seiner scoped_session(). Dies sollte auch mit flask.g anstelle von thread_local für eine Verbindung pro Anfrage funktionieren.