Mysql
 sql >> Base de données >  >> RDS >> Mysql

Quand fermer les curseurs avec MySQLdb

Au lieu de demander quelle est la pratique courante, puisque c'est souvent peu clair et subjectif, vous pouvez essayer de consulter le module lui-même pour obtenir des conseils. En général, en utilisant le with comme un autre utilisateur l'a suggéré est une excellente idée, mais dans ce cas précis, il se peut qu'il ne vous offre pas tout à fait les fonctionnalités que vous attendez.

Depuis la version 1.2.5 du module, MySQLdb.Connection implémente le protocole de 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 plusieurs questions/réponses existantes sur with déjà, ou vous pouvez lire Comprendre l'instruction "with" de Python , mais essentiellement ce qui se passe est que __enter__ s'exécute au début du with bloquer 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 avez l'intention de référencer cet objet ultérieurement. Ainsi, compte tenu de l'implémentation ci-dessus, voici un moyen 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 with bloc? La __exit__ la méthode ci-dessus appelle uniquement self.rollback() ou self.commit() , et aucune de ces méthodes n'appelle le close() méthode. Le curseur lui-même n'a pas de __exit__ méthode définie - et cela n'aurait pas d'importance si c'était le cas, car with gère uniquement la connexion. Par conséquent, la connexion et le curseur restent ouverts après avoir quitté le 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'l'API MySQL C , qui est la base de MySQLdb , n'implémente aucun objet curseur, comme indiqué dans la documentation du module :"MySQL ne prend pas en charge les curseurs ; cependant, les curseurs sont facilement émulés." En effet, le MySQLdb.cursors.BaseCursor la classe hérite directement de object et n'impose aucune restriction de ce type sur les curseurs en ce qui concerne la validation/annulation. 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." Donc commit() avant de fermer le curseur. Au final, pourConnector/Python, cela ne fait pas beaucoup de différence, mais ou d'autres bases de données cela pourrait.

Je pense que c'est aussi proche que possible de 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 fort, 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 gros 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.

Revenons à plus tôt lorsque nous parlions de with , peut-être comprenez-vous maintenant pourquoi le MySQLdb.Connection classe __enter__ et __exit__ les méthodes vous donnent un tout nouvel objet curseur dans chaque with bloc et ne vous embêtez pas à en garder une trace ou à le fermer à la fin du bloc. Il est assez léger et n'existe que pour votre commodité.

S'il est vraiment important pour vous de microgérer l'objet curseur, vous pouvez utiliser contextlib.fermeture pour compenser le fait que l'objet curseur n'a pas de __exit__ défini méthode. D'ailleurs, vous pouvez également l'utiliser pour forcer l'objet de connexion à se fermer à la sortie d'un with bloc. Cela devrait afficher "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'

Notez que with closing(arg_obj) n'appellera pas le __enter__ de l'objet argument et __exit__ méthodes; ce sera seulement appeler le close de l'objet argument méthode à la fin du with bloc. (Pour voir cela en action, définissez simplement une classe Foo avec __enter__ , __exit__ , et close méthodes contenant de simples print et comparez ce qui se passe lorsque vous faites with Foo(): pass à ce qui se passe quand vous faites with closing(Foo()): pass .) Cela a deux implications importantes :

Tout d'abord, si le mode de validation automatique est activé, MySQLdb BEGIN une transaction explicite sur le serveur lorsque vous utilisez with connection et valider ou annuler 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 voulez une transaction, et utilise le BEGIN explicite pour contourner le paramètre de validation automatique sur le serveur. Si vous avez l'habitude d'utiliser with connection , vous pourriez penser que la validation automatique est désactivée alors qu'en réalité elle était simplement contournée. Vous pourriez avoir une mauvaise surprise si vous ajoutez closing à votre code et perdre l'intégrité transactionnelle ; vous ne pourrez pas annuler les modifications, vous commencerez peut-être à voir des bogues de simultanéité et la raison n'en sera peut-être pas immédiatement évidente.

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 la connection du curseur , qui fournit un accès proxy à la connexion d'origine. Lorsque le curseur est fermé, sa connection l'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 par en utilisant SHOW PROCESSLIST ) en exécutant une à une les lignes suivantes :

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