Au lieu de demander quelle est la pratique standard, car elle est souvent peu claire et subjective, vous pouvez essayer de consulter le module lui-même pour obtenir des conseils. En général, utiliser le with
mot - clé comme un autre utilisateur l'a suggéré est une excellente idée, mais dans ce cas précis, il peut ne pas vous donner tout à fait les fonctionnalités que vous attendez.
Depuis la version 1.2.5 du module, MySQLdb.Connection
implémente le protocole du gestionnaire de contexte avec le code suivant ( 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()
Il existe déjà plusieurs questions / réponses with
, ou vous pouvez lire l'instruction «with» de Python , mais ce qui se passe essentiellement, c'est qu'elle __enter__
s'exécute au début du with
bloc et __exit__
s'exécute en quittant le with
bloc. Vous pouvez utiliser la syntaxe facultative with EXPR as VAR
pour lier l'objet renvoyé par __enter__
à un nom si vous prévoyez de référencer cet objet ultérieurement. Donc, étant donné l'implémentation ci-dessus, voici un moyen simple d'interroger votre base de données:
connection = MySQLdb.connect(...)
with connection as cursor:
cursor.execute('select 1;')
result = cursor.fetchall()
print result
La question est maintenant, quels sont les états de la connexion et du curseur après avoir quitté le with
bloc? La __exit__
méthode présentée ci-dessus appelle uniquement self.rollback()
ou self.commit()
, et aucune de ces méthodes n'appelle la close()
méthode. Le curseur lui-même n'a pas de __exit__
méthode définie - et n'aurait pas d'importance s'il le faisait, car il with
ne gère que la connexion. Par conséquent, la connexion et le curseur restent ouverts après la sortie du with
bloc. Ceci est facilement confirmé en ajoutant le code suivant à l'exemple ci-dessus:
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'
Vous devriez voir la sortie "le curseur est ouvert; la connexion est ouverte" imprimée sur stdout.
Je pense que vous devez fermer le curseur avant de valider la connexion.
Pourquoi? L' API C MySQL , qui en est la base MySQLdb
, n'implémente aucun objet curseur, comme le suggère la documentation du module: "MySQL ne prend pas en charge les curseurs; cependant, les curseurs sont facilement émulés." En effet, la MySQLdb.cursors.BaseCursor
classe hérite directement des object
curseurs et n'impose aucune restriction de ce type en ce qui concerne la validation / la restauration. Un développeur Oracle avait ceci à dire :
cnx.commit () avant cur.close () me semble le plus logique. Peut-être pouvez-vous suivre la règle: "Fermez le curseur si vous n'en avez plus besoin." Ainsi commit () avant de fermer le curseur. En fin de compte, pour Connector / Python, cela ne fait pas beaucoup de différence, mais ou pour d'autres bases de données, cela pourrait.
Je pense que c'est aussi proche que vous allez arriver à la "pratique standard" sur ce sujet.
Y a-t-il un avantage significatif à trouver des ensembles de transactions qui ne nécessitent pas de validations intermédiaires afin que vous n'ayez pas à obtenir de nouveaux curseurs pour chaque transaction?
J'en doute beaucoup, et en essayant de le faire, vous risquez d'introduire une erreur humaine supplémentaire. Mieux vaut décider d'une convention et s'y tenir.
Y a-t-il beaucoup de frais généraux pour obtenir de nouveaux curseurs, ou n'est-ce pas un problème?
La surcharge est négligeable et ne touche pas du tout le serveur de base de données; c'est entièrement dans l'implémentation de MySQLdb. Vous pouvez regarder BaseCursor.__init__
sur github si vous êtes vraiment curieux de savoir ce qui se passe lorsque vous créez un nouveau curseur.
En revenant à plus tôt lorsque nous discutions with
, vous pouvez peut-être maintenant comprendre pourquoi la MySQLdb.Connection
classe __enter__
et les __exit__
méthodes vous donnent un tout nouvel objet curseur dans chaque with
bloc et ne prennent pas la peine de le suivre ou de le fermer à la fin du bloc. Il est assez léger et existe uniquement pour votre commodité.
S'il est vraiment important pour vous de microgérer l'objet curseur, vous pouvez utiliser contextlib.closing pour compenser le fait que l'objet curseur n'a pas de __exit__
méthode définie . Pour cette question, vous pouvez également l'utiliser pour forcer l'objet de connexion à se fermer à la sortie d'un with
bloc. Cela devrait afficher "my_curs est fermé; my_conn est fermé":
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'
Notez que with closing(arg_obj)
cela n'appellera pas les méthodes __enter__
et de l'objet argument __exit__
; il n'appel de l'objet de l' argument de la méthode à la fin du bloc. (Pour voir en action, définir simplement une classe avec , et des méthodes simples contenant des déclarations, et comparer ce qui se passe quand vous faites à ce qui se passe quand vous faites .) Cela a deux conséquences importantes:close
with
Foo
__enter__
__exit__
close
print
with Foo(): pass
with closing(Foo()): pass
Premièrement, si le mode autocommit est activé, MySQLdb effectuera BEGIN
une transaction explicite sur le serveur lorsque vous utiliserez with connection
et validerez ou annulerez la transaction à la fin du bloc. Ce sont des comportements par défaut de MySQLdb, destinés à vous protéger du comportement par défaut de MySQL consistant à valider immédiatement toutes les instructions DML. MySQLdb suppose que lorsque vous utilisez un gestionnaire de contexte, vous souhaitez une transaction et utilise l'explicite BEGIN
pour contourner le paramètre de validation automatique sur le serveur. Si vous avez l'habitude d'utiliser with connection
, vous pourriez penser que l'autocommit est désactivé alors qu'en réalité il était seulement contourné. Vous pourriez avoir une mauvaise surprise si vous ajoutezclosing
à votre code et perdez l'intégrité transactionnelle; vous ne pourrez pas annuler les modifications, vous pouvez commencer à voir des bogues de concurrence et pourquoi ne pas être immédiatement évident.
Deuxièmement, with closing(MySQLdb.connect(user, pass)) as VAR
lie l' objet de connexion à VAR
, contrairement à with MySQLdb.connect(user, pass) as VAR
, qui lie un nouvel objet curseur à VAR
. Dans ce dernier cas, vous n'auriez pas d'accès direct à l'objet de connexion! Au lieu de cela, vous devrez utiliser l' connection
attribut du curseur , qui fournit un accès proxy à la connexion d'origine. Lorsque le curseur est fermé, son connection
attribut est défini sur None
. Cela entraîne une connexion abandonnée qui restera jusqu'à ce que l'un des événements suivants se produise:
- Toutes les références au curseur sont supprimées
- Le curseur sort de la portée
- La connexion expire
- La connexion est fermée manuellement via les outils d'administration du serveur
Vous pouvez tester cela en surveillant les connexions ouvertes (dans Workbench ou en utilisantSHOW PROCESSLIST
) tout en exécutant les lignes suivantes une par une:
with MySQLdb.connect(...) as my_curs:
pass
my_curs.close()
my_curs.connection
my_curs.connection.close()
del my_curs
cursor.close()
fait partie de l'API Python DB , qui n'a pas été écrite spécifiquement avec MySQL à l'esprit.my_curs
contient la dernière référence à l'connection
objet. Une fois que cette référence n'existe plus, l'connection
objet doit être récupéré.with
etMySQLdb.Connection
« s__enter__
et__exit__
fonctions. Encore une fois, merci @Air.Il est préférable de le réécrire en utilisant le mot clé «avec». 'With' s'occupera de fermer automatiquement le curseur (c'est important car c'est une ressource non gérée). L'avantage est qu'il fermera également le curseur en cas d'exception.
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()
la source
with
soit une bonne option si vous souhaitez l'utiliser dans Flask ou dans un autre framework Web. Si tel est le cas,http://flask.pocoo.org/docs/patterns/sqlite3/#sqlite3
il y aura des problèmes.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()
Remarque: cette réponse concerne PyMySQL , qui est un remplacement de MySQLdb et effectivement la dernière version de MySQLdb depuis que MySQLdb a cessé d'être maintenu. Je crois que tout ici est également vrai de l'héritage MySQLdb, mais je n'ai pas vérifié.
Tout d'abord, quelques faits:
with
syntaxe de Python appelle la__enter__
méthode du gestionnaire de contexte avant d'exécuter le corps duwith
bloc, et sa__exit__
méthode ensuite.__enter__
méthode qui ne fait rien d'autre que créer et retourner un curseur, et une__exit__
méthode qui valide ou annule (selon si une exception a été levée). Cela ne ferme pas la connexion.__enter__
méthode qui ne fait rien et une__exit__
méthode qui "ferme" le curseur (ce qui signifie simplement annuler la référence du curseur à sa connexion parent et rejeter toutes les données stockées sur le curseur).__del__
méthode qui les fermeEn mettant ces choses ensemble, nous voyons qu'un code naïf comme celui-ci est en théorie problématique:
# Problematic code, at least in theory! import pymysql with pymysql.connect() as cursor: cursor.execute('SELECT 1') # ... happily carry on and do something unrelated
Le problème est que rien n'a fermé la connexion. En effet, si vous collez le code ci-dessus dans un shell Python, puis exécutez
SHOW FULL PROCESSLIST
sur un shell MySQL, vous pourrez voir la connexion inactive que vous avez créée. Puisque le nombre de connexions par défaut de MySQL est de 151 , ce qui n'est pas énorme , vous pourriez théoriquement commencer à rencontrer des problèmes si vous aviez de nombreux processus gardant ces connexions ouvertes.Cependant, dans CPython, il existe une grâce salvatrice qui garantit que le code comme mon exemple ci-dessus ne vous fera probablement pas laisser de nombreuses connexions ouvertes. Cette grâce salvatrice est que dès qu'elle
cursor
est hors de portée (par exemple, la fonction dans laquelle elle a été créée se termine oucursor
reçoit une autre valeur qui lui est assignée), son compteur de références atteint zéro, ce qui entraîne sa suppression, ce qui supprime le nombre de références de la connexion. à zéro, ce qui entraîne l'__del__
appel de la méthode de connexion qui force la fermeture de la connexion. Si vous avez déjà collé le code ci-dessus dans votre shell Python, vous pouvez maintenant simuler cela en exécutantcursor = 'arbitrary value'
; dès que vous faites cela, la connexion que vous avez ouverte disparaîtra de laSHOW PROCESSLIST
sortie.Cependant, s'en remettre à cela n'est pas élégant et pourrait théoriquement échouer dans les implémentations Python autres que CPython. Plus propre, en théorie, serait d'expliciter
.close()
la connexion (pour libérer une connexion sur la base de données sans attendre que Python détruise l'objet). Ce code plus robuste ressemble à ceci:import contextlib import pymysql with contextlib.closing(pymysql.connect()) as conn: with conn as cursor: cursor.execute('SELECT 1')
C'est moche, mais cela ne dépend pas de Python qui détruit vos objets pour libérer vos connexions de base de données (nombre fini disponible de).
Notez que la fermeture du curseur , si vous fermez déjà la connexion explicitement de cette manière, est totalement inutile.
Enfin, pour répondre aux questions secondaires ici:
Non, instancier un curseur ne frappe pas du tout MySQL et ne fait rien .
C'est situationnel et il est difficile de donner une réponse générale. Comme le dit https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html , «une application peut rencontrer des problèmes de performances si elle s'engage des milliers de fois par seconde, et des problèmes de performances différents si il ne s'engage que toutes les 2-3 heures " . Vous payez une surcharge de performances pour chaque validation, mais en laissant les transactions ouvertes plus longtemps, vous augmentez le risque que d'autres connexions passent du temps à attendre des verrous, augmentez votre risque de blocage et augmentez potentiellement le coût de certaines recherches effectuées par d'autres connexions. .
1 MySQL n'ont une construction qu'il appelle un curseur , mais ils existent que les procédures stockées à l' intérieur; ils sont complètement différents des curseurs PyMySQL et ne sont pas pertinents ici.
la source
Je pense que vous ferez mieux d'essayer d'utiliser un curseur pour toutes vos exécutions et de le fermer à la fin de votre code. Il est plus facile de travailler avec, et cela pourrait également avoir des avantages en termes d'efficacité (ne me citez pas là-dessus).
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()
Le fait est que vous pouvez stocker les résultats de l'exécution d'un curseur dans une autre variable, libérant ainsi votre curseur pour effectuer une deuxième exécution. Vous ne rencontrez des problèmes de cette façon que si vous utilisez fetchone () et que vous devez effectuer une deuxième exécution du curseur avant de parcourir tous les résultats de la première requête.
Sinon, je dirais qu'il suffit de fermer vos curseurs dès que vous avez terminé d'en extraire toutes les données. De cette façon, vous n'avez pas à vous soucier de régler les détails plus tard dans votre code.
la source
Je suggère de le faire comme php et mysql. Commencez i au début de votre code avant d'imprimer les premières données. Donc, si vous obtenez une erreur de connexion, vous pouvez afficher un
50x
message d'erreur (Je ne me souviens pas de l'erreur interne). Et gardez-le ouvert pendant toute la session et fermez-le lorsque vous savez que vous n'en aurez plus besoin.la source