Quand fermer cursors en utilisant MySQLdb
je construis une application web WSGI et j'ai une base de données MySQL. J'utilise MySQLdb, qui fournit des curseurs pour exécuter des instructions et obtenir des résultats. Quelle est la pratique standard pour obtenir et fermer les curseurs? en particulier, combien de temps mes curseurs devraient durer? Devrais-je obtenir un nouveau curseur pour chaque transaction?
je crois que vous devez fermer le curseur avant de valider la connexion. Est-il un avantage significatif pour trouver des ensembles de les transactions qui ne nécessitent pas de commit intermédiaire de sorte que vous ne devez pas obtenir de nouveaux curseurs pour chaque transaction? Il y a beaucoup de frais généraux pour l'obtention de nouveaux curseurs, ou est-il tout simplement pas une grosse affaire?
5 réponses
  au lieu de demander ce qu'est la pratique standard, puisque c'est souvent imprécis et subjectif, vous pourriez essayer de regarder le module lui-même pour des conseils. En général, l'utilisation du mot-clé  with  comme un autre utilisateur suggéré est une excellente idée, mais dans ce cas précis, il peut ne pas vous donner tout à fait la fonctionnalité que vous attendez.  
  depuis la version 1.2.5 du module,  MySQLdb.Connection  implémente le    context manager protocol    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 plusieurs questions et Réponses sur with déjà, ou vous pouvez lire  comprendre Python's" avec "statement  , mais essentiellement ce qui se passe est que __enter__ exécute au début du bloc with , et __exit__ exécute à la sortie du bloc with . Vous pouvez utiliser la syntaxe optionnelle  with EXPR as VAR  pour lier l'objet retourné par    __enter__  à un nom si vous avez l'intention de faire référence à cet objet plus tard. Donc, étant donné l'implémentation ci-dessus, voici une façon simple d'interroger votre base de données:  
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 question est maintenant, quels sont les états de la connexion et du curseur après avoir quitté le bloc with ? La méthode __exit__ montrée ci-dessus appelle seulement self.rollback() ou self.commit() , et aucune de ces méthodes n'appelle la méthode close() . Le curseur lui-même n'a pas de  __exit__   méthode définie – et ne serait pas important si elle l'était, parce que  with  est seulement la gestion de la connexion. Par conséquent, la connexion et le curseur restent ouverts après avoir quitté le bloc  with . Ceci est facilement confirmé par l'ajout du 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é à stdout.
je crois que vous devez fermer le curseur avant commettre la connexion.
  pourquoi? Le  MySQL C API   , qui est la base de MySQLdb , n'implémente aucun objet curseur, comme indiqué dans la documentation du module:  " MySQL ne supporte pas les curseurs; cependant, les curseurs sont facilement émulés."   en effet, la classe  MySQLdb.cursors.BaseCursor  hérite directement de  object  et n'impose aucune restriction de ce type aux curseurs en ce qui concerne le commit/rollback. Oracle développeur    avait ceci à dire   :  
cnx.commit() avant cur.close() semble très logique pour moi. Peut-être que vous peut 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 Connecteur / Python, il ne fait pas beaucoup de différence, mais ou autre bases de données qu'il peut.
Je m'attends à ce que ce soit aussi proche que vous allez obtenir à" standard la pratique" sur ce sujet.
y a-t-il un avantage significatif à trouver des ensembles de transactions qui ne nécessitent pas de commit intermédiaire de sorte que vous n'ayez pas à obtenir de nouveaux curseurs pour chaque transaction?
j'en doute fort, et en essayant de le faire, vous pouvez introduire une erreur humaine. Mieux vaut décider d'une convention et s'y tenir.
est-il beaucoup de frais généraux pour obtenir de nouveaux curseurs, ou est-ce juste pas une grosse affaire?
  le overhead est négligeable, et ne touche pas du tout le serveur de base de données; il 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 quand nous discutions  with , peut-être maintenant vous pouvez comprenez pourquoi les méthodes MySQLdb.Connection classe __enter__ et __exit__ vous donnent un tout nouvel objet curseur dans chaque bloc with  et ne vous donnez pas la peine de le suivre ou de le fermer à la fin du bloc. Il est assez léger et n'existe que pour votre confort.  
 si c'est vraiment important pour vous de gérer l'objet curseur, vous pouvez utiliser   contextlib.fermeture    pour compenser le fait que l'objet curseur n'a pas méthode définie  __exit__ . D'ailleurs, vous pouvez également l'utiliser pour forcer l'objet de connexion à se fermer en sortant d'un bloc  with . Cela devrait sortie "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) n'appellera pas les méthodes __enter__ et __exit__ de l'objet argument; il appellera  seulement  la méthode close de l'objet argument à la fin du bloc with . (Voir dans cette action, il suffit de définir une classe  Foo  avec  __enter__  ,  __exit__  , et  close  méthodes contenant des déclarations simples  print , et de comparer ce qui se passe quand vous faites  with Foo(): pass  à ce qui se passe quand vous faites  with closing(Foo()): pass  .) Cela a deux conséquences importantes:  
  tout d'abord, si le mode autocommit est activé, MySQLdb va  BEGIN  une transaction explicite sur le serveur lorsque vous utilisez  with connection  et commit ou roll 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 de commettre immédiatement toutes les déclarations DML. MySQLdb suppose que lorsque vous utilisez un gestionnaire de contexte, vous voulez une transaction, et utilise le  BEGIN  explicite pour contourner le paramètre autocommit sur le serveur. Si vous êtes habitué à utiliser  with connection  , vous pourriez penser autocommit est désactivé alors qu'en fait il était seulement contourné. Vous pourriez avoir une surprise désagréable si vous ajoutez  closing  à votre code et perdez l'intégrité transactionnelle; vous ne serez pas en mesure de faire reculer les changements, vous pouvez commencer à voir des bogues de concurrence et il peut ne pas être immédiatement évident pourquoi.  
  le Second,  with closing(MySQLdb.connect(user, pass)) as VAR  lie    objet de connexion    à  VAR  , contrairement à  with MySQLdb.connect(user, pass) as VAR  , qui lie    un nouveau curseur de l'objet    à  VAR  . Dans ce dernier cas, vous n'auriez pas d'accès direct au objet de connexion! Vous devriez plutôt utiliser l'attribut  connection  du curseur, qui fournit un accès par procuration à la connexion originale. Lorsque le curseur est fermé, son attribut  connection  est défini à  None  . Il en résulte une connexion abandonnée qui restera dans les parages jusqu'à ce que l'un des événements suivants se produise:  
- toutes les références au curseur sont supprimées
- le curseur sort du champ d'application
- Le temps de connexion à
- la connexion se ferme manuellement via les outils d'administration du serveur
  vous pouvez le tester en contrôlant les connexions ouvertes (dans Workbench ou par    en utilisant  SHOW PROCESSLIST     ) tout en exécutant les lignes suivantes une par une:  
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
il est préférable de le réécrire en utilisant le mot-clé 'with'. 'With' s'occupera de fermer le curseur (c'est important car c'est une ressource non gérée) automatiquement. L'avantage est qu'il va fermer 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()
je pense que vous serez mieux d'essayer d'utiliser un curseur pour tous vos exécutions et de la fermer à la fin de votre code. Il est plus facile de travailler avec, et il pourrait avoir des avantages d'efficacité (ne pas me citer qu'une).
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 point 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 seconde exécution. Vous rencontrez des problèmes de cette façon seulement si vous utilisez fetchone(), et avez besoin de exécutez un deuxième curseur avant d'avoir itéré tous les résultats de la première requête.
sinon, je vous dirais de fermer vos curseurs dès que vous aurez fini d'en extraire toutes les données. De cette façon, vous n'avez pas à vous soucier de régler les derniers détails plus tard dans votre code.
Note: Cette réponse est pour PyMySQL , qui est un remplacement direct pour MySQLdb et effectivement la dernière version de MySQLdb depuis MySQLdb cessé d'être maintenu. Je crois que tout ici est aussi vrai de L'héritage MySQLdb, mais n'ont pas vérifié.
tout d'Abord, quelques faits:
-   Python     withla syntaxe appelle la méthode__enter__du gestionnaire de contexte avant d'exécuter le corps du blocwith, puis sa méthode__exit__.   
 Les connexions
-   ont une méthode     __enter__qui ne fait rien d'autre que de créer et de retourner un curseur, et une méthode__exit__qui commet ou retourne (selon qu'une exception a été lancée). n'est pas fermer la connexion.
- les curseurs de PyMySQL sont purement une abstraction implémentée en Python; il n'y a pas de concept équivalent dans MySQL lui-même. 1
-   les curseurs ont une   __enter__méthode qui ne fait rien et une__exit__méthode qui "ferme" le curseur (ce qui signifie juste annuler la référence du curseur à sa connexion parent et jeter toutes les données stockées sur curseur.)
- Curseurs tiennent une référence à la connexion qui les a engendré, mais les connexions ne tiennent pas une référence aux curseurs qu'ils ont créé.
-   Branchements     __del__méthode de la ferme
- par https://docs.python.org/3/reference/datamodel.html , CPython (l'implémentation Python par défaut) utilise le comptage de référence et automatiquement supprime un objet lorsque le nombre de références atteint zéro.
en mettant ces choses ensemble, nous voyons que le 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 lancez  SHOW FULL PROCESSLIST  dans un shell MySQL, vous pourrez voir la connexion inutilisée que vous avez créée. Depuis Le nombre de connexions par défaut de MySQL est    151   , qui n'est pas  énorme  , vous pourriez théoriquement commencer à courir dans des problèmes si vous aviez beaucoup de processus garder ces connexions ouvertes.  
  cependant, dans CPython, il y a une grâce d'économie qui assure que le code comme mon exemple au-dessus de    probablement    ne vous fera pas quitter autour des charges de connexions ouvertes. Cette grâce salvatrice est que dès que  cursor   sort du champ d'application (par exemple la fonction dans laquelle il a été créé se termine, ou  cursor  obtient une autre valeur qui lui est assignée), son nombre de référence atteint zéro, ce qui le fait supprimer, ce qui fait tomber le nombre de référence de la connexion à zéro, ce qui fait que la méthode  __del__  est appelée qui force-ferme la connexion. Si vous avez déjà collé le code ci-dessus dans votre shell Python, alors vous pouvez maintenant simuler ceci en exécutant  cursor = 'arbitrary value' ; dès que vous faites ceci, la connexion que vous ouvert disparaîtra de la sortie SHOW PROCESSLIST .  
  cependant, se fier à cela est inélégant, et théoriquement pourrait échouer dans des implémentations Python autres que CPython. Nettoyant, en théorie, serait explicitement  .close()  la connexion (pour libérer une connexion sur la base de données sans attendre Python pour détruire 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 laid, mais ne dépend pas de la destruction de Python vos objets pour libérer votre (finis disponibles nombre de connexions de base de données.
notez que fermer le curseur 1519230920" , si vous fermez déjà la connexion explicitement comme ceci, est totalement inutile.
enfin, pour répondre aux questions secondaires ici:
est - ce qu'il y a beaucoup de frais généraux pour obtenir de nouveaux curseurs, ou est-ce juste pas une grosse affaire?
Non, l'instanciation d'un curseur ne frappe pas MySQL et ne fait rien .
y a-t-il un avantage significatif à trouver des ensembles de transactions qui ne nécessitent pas de commit intermédiaire de sorte que vous n'ayez pas à obtenir de nouveaux curseurs pour chaque transaction?
c'est situationnel et difficile à répondre de façon générale. Comme https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html le met, " une application pourrait rencontrer des problèmes de performance si elle engage des milliers de fois par seconde, et des problèmes de performance différents si elle engage seulement toutes les 2-3 heures " . Vous payez un frais généraux de performance pour chaque commit, mais en laissant les transactions ouvertes plus longtemps, vous augmentez la chance d'autres connexions ayant à passer du temps à attendre la serrure, augmenter votre risque de les blocages, et potentiellement augmenter le coût de certaines recherches effectuées par d'autres connexions.
1 MySQL ne ont une construction qu'il appelle une curseur mais ils n'existent qu'à l'intérieur de procédures stockées; ils sont complètement différents de PyMySQL curseurs et ne sont pas pertinentes ici.
  je suggère de le faire comme php et mysql. Démarrez i Au début de votre code avant l'impression des premières données. Ainsi, si vous obtenez une erreur de connexion, vous pouvez afficher un message d'erreur  50x  (ne vous rappelez pas ce qu'est une erreur interne). Et gardez-le ouvert pendant toute la session et fermez-le quand vous savez que vous n'en aurez plus besoin.