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 with
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 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()
with
Ya 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 with
bloque y se __exit__
ejecuta al salir del with
bloque. Puede utilizar la sintaxis opcional with EXPR as VAR
para 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 with
bloque? 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 with
solo administra la conexión. Por tanto, tanto la conexión como el cursor permanecen abiertos después de salir del with
bloque. 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.BaseCursor
clase hereda directamente object
y 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.Connection
clase__enter__
y los __exit__
métodos le brindan un nuevo objeto de cursor en cada with
bloque 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 with
bloque. 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 close
método al final del with
bloque. (Para ver esto en acción, sólo tiene que definir una clase Foo
con __enter__
, __exit__
y close
métodos que contiene simples print
declaraciones, 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á BEGIN
una 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 BEGIN
para 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 VAR
se 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 connection
atributo del cursor , que proporciona acceso de proxy a la conexión original. Cuando el cursor está cerrado, su connection
atributo 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_curs
contiene la última referencia alconnection
objeto. Una vez que esa referencia ya no existe, elconnection
objeto debe ser recolectado como basura.with
yMySQLdb.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
with
sea 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:
with
sintaxis de Python llama al__enter__
método del administrador de contexto antes de ejecutar el cuerpo delwith
bloque 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 unrelated
El 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 PROCESSLIST
en 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
cursor
sale del alcance (por ejemplo, la función en la que se creó finaliza, ocursor
se 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 PROCESSLIST
salida.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
50x
mensaje 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