En lugar de preguntar cuál es la práctica estándar, ya que a menudo es poco claro y subjetivo, puede intentar consultar el módulo en sí para obtener orientación. En general, usar la withpalabra 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.Connectionimplementa el protocolo del 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()
withYa hay varias preguntas y respuestas sobre , o puede leer Entender la declaración "con" de Python , pero esencialmente lo que sucede es que se __enter__ejecuta al comienzo del withbloque y se __exit__ejecuta al salir del withbloque. Puede utilizar la sintaxis opcional with EXPR as VARpara vincular el objeto devuelto por __enter__a un nombre si desea 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:
cursor.execute('select 1;')
result = cursor.fetchall()
print result
La pregunta ahora es, ¿cuáles son los estados de la conexión y el cursor después de salir del withbloque? El __exit__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 un __exit__método definido, y no importaría si lo hiciera, porque withsolo administra la conexión. Por tanto, tanto la conexión como el cursor permanecen abiertos después de salir del withbloque. 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 la salida "cursor abierto; conexión abierta" impresa en stdout.
Creo que debe cerrar el cursor antes de confirmar la conexión.
¿Por qué? La API de MySQL C , que es la base de MySQLdb, no implementa ningún objeto de cursor, como se implica en la documentación del módulo: "MySQL no admite cursores; sin embargo, los cursores se emulan fácilmente". De hecho, la MySQLdb.cursors.BaseCursorclase hereda directamente objecty no impone tal restricción a los cursores con respecto al compromiso / retroceso. Un desarrollador de Oracle dijo lo siguiente :
cnx.commit () antes de cur.close () me suena más lógico. Tal vez pueda seguir la regla: "Cierre el cursor si ya no lo necesita". Por lo tanto, commit () antes de cerrar el cursor. Al final, para Connector / Python, no hace mucha diferencia, pero para otras bases de datos podría.
Supongo que eso es lo más cercano a la "práctica estándar" sobre este tema.
¿Existe 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 intentar hacerlo, puede introducir un error humano adicional. Mejor decidirse por una convención y ceñirse a ella.
¿Hay muchos gastos generales para obtener nuevos cursores o simplemente no es un gran problema?
La sobrecarga es insignificante y no toca el servidor de la base de datos en absoluto; está completamente dentro de la implementación de MySQLdb. Usted puede mirar en BaseCursor.__init__github si tiene mucha curiosidad por saber qué está sucediendo cuando crea un nuevo cursor.
Volviendo a cuando estábamos discutiendo with, tal vez ahora pueda entender por qué la MySQLdb.Connectionclase__enter__ y los __exit__métodos le brindan un nuevo objeto de cursor en cada withbloque y no se moleste en seguirlo o cerrarlo al final del bloque. Es bastante ligero y existe únicamente para su conveniencia.
Si es realmente tan importante para usted microgestionar el objeto cursor, puede usar contextlib.closing para compensar el hecho de que el objeto cursor no tiene un __exit__método definido . De hecho, también puede usarlo para forzar que el objeto de conexión se cierre al salir de un withbloque. Esto debería mostrar "my_curs is closed; my_conn is closed":
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á a los métodos __enter__y del objeto argumento __exit__; será solamente llamar el objeto de discusión closemétodo al final del withbloque. (Para ver esto en acción, sólo tiene que definir una clase Foocon __enter__, __exit__y closemétodos que contiene simples printdeclaraciones, y comparar lo que sucede cuando usted hacewith Foo(): pass a lo que sucede cuando lo hace with closing(Foo()): pass.) Esto tiene dos implicaciones importantes:
Primero, si el modo de confirmación automática está habilitado, MySQLdb realizará BEGINuna transacción explícita en el servidor cuando utilice su código y pierda la integridad transaccional; no podrá deshacer los cambios, es posible que comience a ver errores de simultaneidad y es posible que no sea inmediatamente obvio por qué.with connection y confirme o deshaga la transacción al final del bloque. Estos son comportamientos predeterminados de MySQLdb, destinados a protegerlo del comportamiento predeterminado de MySQL de confirmar inmediatamente todas y cada una de las declaraciones DML. MySQLdb asume que cuando usa un administrador de contexto, desea una transacción y usa el explícito BEGINpara omitir la configuración de confirmación automática en el servidor. Si está acostumbrado a usar with connection, podría pensar que la confirmación automática está deshabilitada cuando en realidad solo se estaba omitiendo. Puede obtener una sorpresa desagradable si agregaclosing
En segundo lugar, with closing(MySQLdb.connect(user, pass)) as VARse une el objeto de conexión para VAR, en contraste con with MySQLdb.connect(user, pass) as VAR, que se une un nuevo objeto cursor a VAR. En el último caso, no tendría acceso directo al objeto de conexión. En su lugar, tendría que utilizar el connectionatributo del cursor , que proporciona acceso de proxy a la conexión original. Cuando el cursor está cerrado, su connectionatributo se establece en None. Esto da como resultado una conexión abandonada que se mantendrá hasta que ocurra una de las siguientes situaciones:
- Se eliminan todas las referencias al cursor
- El cursor sale fuera de alcance
- La conexión se agota
- La conexión se cierra manualmente mediante las herramientas de administración del servidor.
Puede probar esto monitoreando las conexiones abiertas (en Workbench o usandoSHOW PROCESSLIST ) mientras ejecuta las siguientes líneas una por una:
with MySQLdb.connect(...) as my_curs:
pass
my_curs.close()
my_curs.connection
my_curs.connection.close()
del my_curs
cursor.close()es parte de la API de Python DB , que no se escribió específicamente con MySQL en mente.my_curscontiene la última referencia alconnectionobjeto. Una vez que esa referencia ya no existe, elconnectionobjeto debe ser recolectado como basura.withyMySQLdb.Connection's__enter__y__exit__funciones. Nuevamente, gracias @Air.Es mejor reescribirlo usando la palabra clave 'con'. 'Con' se ocupará de cerrar el cursor (es importante porque es un recurso no administrado) automáticamente. El beneficio es que también cerrará el cursor en caso de excepción.
from contextlib import closing import MySQLdb ''' At the beginning you open a DB connection. Particular moment when you open connection depends from your approach: - it can be inside the same function where you work with cursors - in the class constructor - etc ''' db = MySQLdb.connect("host", "user", "pass", "database") with closing(db.cursor()) as cur: cur.execute("somestuff") results = cur.fetchall() # do stuff with results cur.execute("insert operation") # call commit if you do INSERT, UPDATE or DELETE operations db.commit() cur.execute("someotherstuff") results2 = cur.fetchone() # do stuff with results2 # at some point when you decided that you do not need # the open connection anymore you close it db.close()fuente
withsea una buena opción si desea usarlo en Flask u otro marco web. Si la situación eshttp://flask.pocoo.org/docs/patterns/sqlite3/#sqlite3, habrá problemas.with closing(self.db.cursor()) as cur: cur.execute("UPDATE table1 SET status = %s WHERE id = %s",(self.INTEGR_STATUS_PROCESSING, id)) self.db.commit()Nota: esta respuesta es para PyMySQL , que es un reemplazo directo de MySQLdb y efectivamente la última versión de MySQLdb desde que MySQLdb dejó de mantenerse. Creo que todo aquí también es cierto para el MySQLdb heredado, pero no lo he comprobado.
En primer lugar, algunos hechos:
withsintaxis de Python llama al__enter__método del administrador de contexto antes de ejecutar el cuerpo delwithbloque y su__exit__método después.__enter__método que no hace nada más que crear y devolver un cursor, y un__exit__método que confirma o revierte (dependiendo de si se lanzó una excepción). Que no se cierre la conexión.__enter__método que no hace nada y un__exit__método que "cierra" el cursor (lo que simplemente significa anular la referencia del cursor a su conexión principal y desechar cualquier dato almacenado en el cursor).__del__método que las cierraJuntando estas cosas, vemos que un código ingenuo como este es, en teoría, problemático:
# Problematic code, at least in theory! import pymysql with pymysql.connect() as cursor: cursor.execute('SELECT 1') # ... happily carry on and do something unrelatedEl problema es que nada ha cerrado la conexión. De hecho, si pega el código anterior en un shell de Python y luego lo ejecuta
SHOW FULL PROCESSLISTen un shell de MySQL, podrá ver la conexión inactiva que creó. Dado que el número predeterminado de conexiones de MySQL es 151 , que no es enorme , teóricamente podría comenzar a tener problemas si tuviera muchos procesos que mantengan abiertas estas conexiones.Sin embargo, en CPython, hay una gracia salvadora que garantiza que un código como mi ejemplo anterior probablemente no hará que deje muchas conexiones abiertas. Esa gracia salvadora es que tan pronto como
cursorsale del alcance (por ejemplo, la función en la que se creó finaliza, ocursorse le asigna otro valor), su recuento de referencias llega a cero, lo que hace que se elimine, eliminando el recuento de referencias de la conexión. a cero, lo que provoca__del__que se llame al método de la conexión, lo que fuerza el cierre de la conexión. Si ya pegó el código anterior en su shell de Python, ahora puede simularlo ejecutandocursor = 'arbitrary value'; tan pronto como haga esto, la conexión que abrió desaparecerá de laSHOW PROCESSLISTsalida.Sin embargo, confiar en esto no es elegante y, en teoría, podría fallar en implementaciones de Python distintas de CPython. Más limpio, en teoría, sería explícitamente
.close()la conexión (para liberar una conexión en la base de datos sin esperar a que Python destruya el objeto). Este código más robusto se ve así:import contextlib import pymysql with contextlib.closing(pymysql.connect()) as conn: with conn as cursor: cursor.execute('SELECT 1')Esto es feo, pero no depende de que Python destruya sus objetos para liberar su (número finito disponible de) conexiones de base de datos.
Tenga en cuenta que cerrar el cursor , si ya está cerrando la conexión explícitamente así, no tiene sentido.
Finalmente, para responder a las preguntas secundarias aquí:
No, crear una instancia de un cursor no afecta a MySQL en absoluto y básicamente no hace nada .
Esto es situacional y difícil de dar una respuesta general. Como dice https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html , "una aplicación puede encontrar problemas de rendimiento si se compromete miles de veces por segundo, y diferentes problemas de rendimiento si se compromete solo cada 2-3 horas " . Paga una sobrecarga de rendimiento por cada confirmación, pero al dejar las transacciones abiertas durante más tiempo, aumenta la posibilidad de que otras conexiones tengan que pasar tiempo esperando bloqueos, aumenta el riesgo de interbloqueos y aumenta potencialmente el costo de algunas búsquedas realizadas por otras conexiones .
1 MySQL hace tener una construcción que llama un cursor pero sólo existe dentro de procedimientos almacenados; son completamente diferentes a los cursores PyMySQL y no son relevantes aquí.
fuente
Creo que será mejor que intente usar un cursor para todas sus ejecuciones y lo cierre al final de su código. Es más fácil trabajar con él y también podría tener beneficios de eficiencia (no me cite en eso).
conn = MySQLdb.connect("host","user","pass","database") cursor = conn.cursor() cursor.execute("somestuff") results = cursor.fetchall() ..do stuff with results cursor.execute("someotherstuff") results2 = cursor.fetchall() ..do stuff with results2 cursor.close()El punto es que puede almacenar los resultados de la ejecución de un cursor en otra variable, liberando así su cursor para realizar una segunda ejecución. Te encuentras con problemas de esta manera solo si estás usando fetchone () y necesitas realizar una segunda ejecución del cursor antes de haber iterado a través de todos los resultados de la primera consulta.
De lo contrario, diría que cierre los cursores tan pronto como haya terminado de obtener todos los datos de ellos. De esa manera, no tiene que preocuparse por atar cabos sueltos más adelante en su código.
fuente
Sugiero hacerlo como php y mysql. Comience i al comienzo de su código antes de imprimir los primeros datos. Por lo tanto, si recibe un error de conexión, puede mostrar un
50xmensaje de error (No recuerde cuál es el error interno). Manténgalo abierto durante toda la sesión y ciérrelo cuando sepa que ya no lo necesitará.fuente