python MySQLDB tiempo de espera de consulta

Estoy tratando de imponer un límite de tiempo en las consultas en python MySQLDB. Estoy en una situación donde no tengo control sobre las consultas, pero es necesario garantizar que no pase por encima de un límite de tiempo establecido. He intentado usar la señal.SIGALRM para interrumpir la llamada a ejecutar, pero esto no parece funcionar. La señal se envía, pero no quedar atrapados hasta después de la llamada a ejecutar los acabados.

Escribí un caso de prueba para demostrar este comportamiento:

#!/usr/local/bin/python2.6

import time
import signal

from somewhere import get_dbc

class Timeout(Exception):
    """ Time Exceded """

def _alarm_handler(*args):
    raise Timeout

dbc = get_dbc()

signal.signal(signal.SIGALRM, _alarm_handler)
signal.alarm(1)

try:
    print "START:  ", time.time()
    dbc.execute("SELECT SLEEP(10)")
except Timeout:
    print "TIMEOUT!", time.time()'

La «SELECCIONE SLEEP(10)» es la simulación de una consulta lenta, pero yo veo el mismo comportamiento con una real consulta lenta.

El Resultado:

START:   1254440686.69
TIMEOUT! 1254440696.69

Como usted puede ver, se trata de dormir durante 10 segundos, a continuación, puedo obtener el tiempo de espera de Excepción.

Preguntas:

  1. ¿Por qué no me dan la señal hasta después de ejecutar los acabados?
  2. Hay otra manera confiable para limitar el tiempo de ejecución de consulta?
InformationsquelleAutor mluebke | 2009-10-01

6 Kommentare

  1. 8

    @nosklo retorcido solución basada en es elegante y viable, pero si usted quiere evitar la dependencia de trenzado, la tarea es aún factible, e.g:

    import multiprocessing
    
    def query_with_timeout(dbc, timeout, query, *a, **k):
      conn1, conn2 = multiprocessing.Pipe(False)
      subproc = multiprocessing.Process(target=do_query,
                                        args=(dbc, query, conn2)+a, 
                                        kwargs=k)
      subproc.start()
      subproc.join(timeout)
      if conn1.poll():
        return conn1.recv()
      subproc.terminate()
      raise TimeoutError("Query %r ran for >%r" % (query, timeout))
    
    def do_query(dbc, query, conn, *a, **k):
      cu = dbc.cursor()
      cu.execute(query, *a, **k)
      return cu.fetchall()
    • El conn parámetro de do_query no parece ser usados, cualquier motivo no solo para eliminarlo?
    • no, el conn parámetro es realmente necesario para que esto funcione. El do_query comando se debe utilizar para enviar el resultado en lugar de sólo returning.
    • Jaja, cinco años más tarde. Seguro, si fueron modificados para utilizar realmente conn para canalizar el resultado de la espalda, entonces eso sería hacer un montón de sentido. Supongo que la verdadera respuesta aquí es «esto debe ser editado para usar ese argumento para hacer su utilidad clara». Es no necesario para que funcione, después de todo, es sólo puede ser.
  2. 2

    He intentado usar la señal.SIGALRM para interrumpir la llamada a ejecutar, pero esto no parece funcionar. La señal se envía, pero no quedar atrapados hasta después de la llamada a ejecutar los acabados.

    librería de mysql maneja interrumpido sistemas de llamadas internamente por lo que no verá efectos secundarios de SIGALRM hasta después de la llamada a la API completa (corto de matar el actual proceso o subproceso)

    Usted puede tratar de remendar MySQL-Python y el uso MYSQL_OPT_READ_TIMEOUT opción (añadido en mysql 5.0.25)

  3. 1

    ¿Por qué no me dan la señal hasta después de ejecutar los acabados?

    La consulta es ejecutada a través de una función de C, que bloquea el Python VM desde la ejecución hasta que se devuelve.

    Hay otra manera confiable para limitar el tiempo de ejecución de consulta?

    Esto es (OMI), una muy fea solución, pero hace trabajo. Se puede ejecutar la consulta en un proceso independiente (ya sea a través de fork() o la multiprocesamiento módulo). Ejecutar el temporizador de la alarma en el proceso principal, y el que lo recibe, envía un SIGINT o SIGKILL para el proceso hijo. Si utiliza multiprocessing, puede utilizar el Process.terminate() método.

  4. 1

    Uso adbapi. Permite hacer una db llamada asincrónica.

    from twisted.internet import reactor
    from twisted.enterprise import adbapi
    
    def bogusQuery():
        return dbpool.runQuery("SELECT SLEEP(10)")
    
    def printResult(l):
        # function that would be called if it didn't time out
        for item in l:
            print item
    
    def handle_timeout():
        # function that will be called when it timeout
        reactor.stop()
    
    dbpool = adbapi.ConnectionPool("MySQLdb", user="me", password="myself", host="localhost", database="async")
    bogusQuery().addCallback(printResult)
    reactor.callLater(4, handle_timeout)
    reactor.run()
  5. 1

    Genérico notas

    He experimentado el mismo problema últimamente con varias condiciones que tenía que cumplir:

    • solución debe ser seguro para subprocesos
    • múltiples conexiones a base de datos desde la misma máquina puede estar activo al mismo tiempo, matar el exacto conexión/consulta
    • aplicación contiene conexiones a diferentes bases de datos portátiles de controlador para cada DB host

    Hemos tenido a partir de la clase de diseño (por desgracia no puedo publicar fuentes reales):

    class AbstractModel: pass 
    class FirstDatabaseModel(AbstractModel): pass # Connection to one DB host
    class SecondDatabaseModel(AbstractModel): pass # Connection to one DB host

    Y creado varios hilos para cada modelo.


    Solución Python 3.2

    En nuestra aplicación un modelo de = una base de datos. Por lo que he creado «el servicio de conexión» para cada modelo (por lo que podría ejecutar KILL en la conexión en paralelo). Por lo tanto, si una instancia de FirstDatabaseModel fue creado, 2 conexión de base de datos se han creado; si 5 casos se han creado sólo 6 conexiones utilizados fueron:

    class AbstractModel:
        _service_connection = None # Formal declaration
    
        def __init__(self):
            ''' Somehow load config and create connection
            '''
            self.config = # ...
            self.connection = MySQLFromConfig(self.config)
            self._init_service_connection()
    
            # Get connection ID (pseudocode)
            self.connection_id = self.connection.FetchOneCol('SELECT CONNECTION_ID()') 
    
        def _init_service_connection(self):
            ''' Initialize one singleton connection for model
            '''
            cls = type(self)
            if cls._service_connection is not None:
                return
    
            cls._service_connection = MySQLFromConfig(self.config)

    Ahora necesitamos un asesino:

    def _kill_connection(self):
        # Add your own mysql data escaping
        sql = 'KILL CONNECTION {}'.format(self.connection_id)
    
        # Do your own connection check and renewal
        type(self)._service_connection.execute(sql)

    Nota: connection.execute = create cursor, ejecutar, cerrar el cursor.

    Y hacer killer hilo de seguro el uso de threading.Lock:

    def _init_service_connection(self):
        ''' Initialize one singleton connection for model
        '''
        cls = type(self)
        if cls._service_connection is not None:
            return
    
        cls._service_connection = MySQLFromConfig(self.config)
        cls._service_connection_lock = threading.Lock()
    
    def _kill_connection(self):
        # Add your own mysql data escaping
        sql = 'KILL CONNECTION {}'.format(self.connection_id)
        cls = type(self)
    
        # Do your own connection check and renewal
        try:
            cls._service_connection_lock.acquire()    
            cls._service_connection.execute(sql)
        finally:
            cls._service_connection_lock.release()

    Y, finalmente, añadir temporizado de ejecución utilizando el método threading.Timer:

    def timed_query(self, sql, timeout=5):
        kill_query_timer = threading.Timer(timeout, self._kill_connection)
        kill_query_timer.start()
    
        try:
            self.connection.long_query() 
        finally:
            kill_query_timer.cancel()
  6. -1

    ¿Por qué no me dan la señal hasta después de ejecutar los acabados?

    El proceso de espera de e/S de red en un sistema de estado (UNIX cosa, no relacionados con Python o MySQL). Se pone la señal después de la llamada al sistema acabados (probablemente como EINTR código de error, aunque no estoy seguro).

    Hay otra manera confiable para limitar el tiempo de ejecución de consulta?

    Creo que es hecha generalmente por una herramienta externa como mkill que los monitores de MySQL para las consultas de larga ejecución y los mata.

    • De hecho, su primera parte es malo. Si un proceso en un recv, select o poll llamada recibe una señal, la llamada al sistema devuelve -EINTR. Lo que hace que Python cuelgo aquí es el reinicio de la lógica.

Kommentieren Sie den Artikel

Bitte geben Sie Ihren Kommentar ein!
Bitte geben Sie hier Ihren Namen ein

Pruebas en línea