sql >> Base de Datos >  >> RDS >> Mysql

Cuándo cerrar cursores usando MySQLdb

En lugar de preguntar cuál es la práctica estándar, ya que a menudo es poco clara y subjetiva, puede intentar consultar el módulo en sí para obtener orientación. En general, usando with La palabra clave como sugirió otro usuario es una gran idea, pero en esta circunstancia específica puede que no le brinde la funcionalidad que espera.

A partir de la versión 1.2.5 del módulo, MySQLdb.Connection implementa el protocolo administrador de contexto con el siguiente código (github ):

def __enter__(self):
    if self.get_autocommit():
        self.query("BEGIN")
    return self.cursor()

def __exit__(self, exc, value, tb):
    if exc:
        self.rollback()
    else:
        self.commit()

Hay varias preguntas y respuestas sobre with ya, o puede leer Comprender la declaración "with" de Python , pero esencialmente lo que sucede es que __enter__ se ejecuta al comienzo de with bloquear y __exit__ se ejecuta al salir del with cuadra. Puede usar la sintaxis opcional with EXPR as VAR para vincular el objeto devuelto por __enter__ a un nombre si tiene la intención de hacer referencia a ese objeto más adelante. Entonces, dada la implementación anterior, aquí hay una forma simple de consultar su base de datos:

connection = MySQLdb.connect(...)
with connection as cursor:            # connection.__enter__ executes at this line
    cursor.execute('select 1;')
    result = cursor.fetchall()        # connection.__exit__ executes after this line
print result                          # prints "((1L,),)"

La pregunta ahora es, ¿cuáles son los estados de la conexión y el cursor después de salir de with? ¿cuadra? El __exit__ el método que se muestra arriba solo llama a self.rollback() o self.commit() , y ninguno de esos métodos continúa llamando al close() método. El cursor en sí no tiene __exit__ método definido, y no importaría si lo hiciera, porque with solo está administrando la conexión. Por lo tanto, tanto la conexión como el cursor permanecen abiertos después de salir del with cuadra. Esto se confirma fácilmente agregando el siguiente código al ejemplo anterior:

try:
    cursor.execute('select 1;')
    print 'cursor is open;',
except MySQLdb.ProgrammingError:
    print 'cursor is closed;',
if connection.open:
    print 'connection is open'
else:
    print 'connection is closed'

Debería ver el resultado "el cursor está abierto; la conexión está abierta" impresa en la salida estándar.

Creo que debe cerrar el cursor antes de confirmar la conexión.

¿Por qué? La API C de MySQL , que es la base de MySQLdb , no implementa ningún objeto de cursor, como se indica en la documentación del módulo:"MySQL no admite cursores; sin embargo, los cursores se emulan fácilmente". De hecho, el MySQLdb.cursors.BaseCursor la clase hereda directamente de object y no impone tal restricción a los cursores con respecto a la confirmación/reversión. Un desarrollador de Oracle dijo esto :

cnx.commit() antes de cur.close() suena más lógico para mí. Tal vez pueda seguir la regla:"Cierre el cursor si ya no lo necesita". Por lo tanto, confirme () antes de cerrar el cursor. Al final, para Connector/Python, no hace mucha diferencia, pero sí para otras bases de datos.

Supongo que eso es lo más cerca que vas a llegar a la "práctica estándar" sobre este tema.

¿Hay alguna ventaja significativa en encontrar conjuntos de transacciones que no requieran confirmaciones intermedias para que no tenga que obtener nuevos cursores para cada transacción?

Lo dudo mucho, y al tratar de hacerlo, puede introducir un error humano adicional. Es mejor decidirse por una convención y ceñirse a ella.

¿Hay muchos gastos generales para obtener nuevos cursores, o simplemente no es gran cosa?

La sobrecarga es insignificante y no afecta en absoluto al servidor de la base de datos; está completamente dentro de la implementación de MySQLdb. Puede ver BaseCursor.__init__ en github si tiene mucha curiosidad por saber qué sucede cuando crea un nuevo cursor.

Volviendo a cuando hablábamos de with , quizás ahora puedas entender por qué MySQLdb.Connection clase __enter__ y __exit__ los métodos le dan un nuevo objeto de cursor en cada with bloque y no se moleste en seguirlo o cerrarlo al final del bloque. Es bastante liviano y existe únicamente para su conveniencia.

Si realmente es tan importante para usted microgestionar el objeto del cursor, puede usar contextlib.cierre para compensar el hecho de que el objeto del cursor no tiene __exit__ definido método. De hecho, también puede usarlo para obligar al objeto de conexión a cerrarse solo al salir de un with cuadra. Esto debería mostrar "my_curs está cerrado; my_conn está cerrado":

from contextlib import closing
import MySQLdb

with closing(MySQLdb.connect(...)) as my_conn:
    with closing(my_conn.cursor()) as my_curs:
        my_curs.execute('select 1;')
        result = my_curs.fetchall()
try:
    my_curs.execute('select 1;')
    print 'my_curs is open;',
except MySQLdb.ProgrammingError:
    print 'my_curs is closed;',
if my_conn.open:
    print 'my_conn is open'
else:
    print 'my_conn is closed'

Tenga en cuenta que with closing(arg_obj) no llamará al objeto de argumento __enter__ y __exit__ métodos; será solo llamar al close del objeto de argumento método al final de with cuadra. (Para ver esto en acción, simplemente defina una clase Foo con __enter__ , __exit__ y close métodos que contienen print simple declaraciones, y compare lo que sucede cuando hace with Foo(): pass a lo que sucede cuando haces with closing(Foo()): pass .) Esto tiene dos implicaciones importantes:

Primero, si el modo de confirmación automática está habilitado, MySQLdb BEGIN una transacción explícita en el servidor cuando usa with connection y confirmar o deshacer la transacción al final del bloque. Estos son comportamientos predeterminados de MySQLdb, destinados a protegerlo del comportamiento predeterminado de MySQL de confirmar de inmediato todas y cada una de las declaraciones DML. MySQLdb asume que cuando usa un administrador de contexto, desea una transacción y usa el BEGIN explícito para omitir la configuración de confirmación automática en el servidor. Si estás acostumbrado a usar with connection , podría pensar que la confirmación automática está deshabilitada cuando en realidad solo se estaba omitiendo. Podrías llevarte una sorpresa desagradable si agregas closing a su código y perder la integridad transaccional; no podrá revertir los cambios, es posible que comience a ver errores de simultaneidad y es posible que no sea inmediatamente obvio por qué.

Segundo, with closing(MySQLdb.connect(user, pass)) as VAR vincula el objeto de conexión a VAR , a diferencia de with MySQLdb.connect(user, pass) as VAR , que vincula un nuevo objeto de cursor a VAR . ¡En el último caso no tendría acceso directo al objeto de conexión! En su lugar, tendría que usar la connection del cursor atributo, que proporciona acceso de proxy a la conexión original. Cuando el cursor está cerrado, su connection el atributo está establecido en None . Esto da como resultado una conexión abandonada que se mantendrá hasta que suceda uno de los siguientes:

  • Se eliminan todas las referencias al cursor
  • El cursor sale del alcance
  • La conexión se agota
  • La conexión se cierra manualmente a través de las herramientas de administración del servidor

Puede probar esto monitoreando las conexiones abiertas (en Workbench o mediante usando SHOW PROCESSLIST ) mientras ejecuta las siguientes líneas una por una:

with MySQLdb.connect(...) as my_curs:
    pass
my_curs.close()
my_curs.connection          # None
my_curs.connection.close()  # throws AttributeError, but connection still open
del my_curs                 # connection will close here