95 votes

Python MySQLdb: quand fermer les curseurs

Je suis en train de construire un wsgi web app, et j'ai une base de données mysql. Je suis l'aide de MySQLdb, qui fournit les curseurs pour l'exécution des instructions et obtenir des résultats. Quelle est la norme de pratique pour l'obtention et la fermeture des curseurs? En particulier, combien de temps dois mes curseurs dernier? 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 à trouver des ensembles d'opérations qui ne nécessitent pas d'intermédiaire s'engage, de sorte que vous n'avez 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?

88voto

AirThomas Points 1489

Au lieu de se demander quelle est la norme de la pratique, car c'est souvent floue et subjective, vous pouvez essayer la recherche pour le module lui-même pour obtenir des conseils. En général, l'utilisation de l' with mot-clé comme un autre utilisateur proposé est une excellente idée, mais dans ce cas précis, il ne peut pas vous donner tout les fonctionnalités que vous attendez.

À partir de la version 1.2.5 du module, MySQLdb.Connection implémente le gestionnaire de contexte de protocole 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 Q&A sur with déjà, ou vous pouvez lire la Compréhension Python "avec la" déclaration, mais essentiellement ce qui se passe c'est qu' __enter__ s'exécute au démarrage de l' with bloc, et __exit__ s'exécute à la sortie de l' with bloc. Vous pouvez utiliser la syntaxe facultative with EXPR as VAR de lier l'objet renvoyé par __enter__ d'un nom, si vous avez l'intention de la référence de l'objet plus tard. Donc, étant donné ce qui précède la mise en œuvre, voici une façon simple pour 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 de savoir, quels sont les états de la connexion et le curseur après la sortie de l' with bloc? L' __exit__ méthode indiquée ci-dessus uniquement les appels self.rollback() ou self.commit(), et aucune de ces méthodes vont à l'appel de l' close() méthode. Le curseur lui-même n'a pas d' __exit__ méthode définie. Par conséquent, la connexion et le curseur reste ouvert après la sortie de l' 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 de "curseur est ouvert; connexion est ouverte" imprimé sur la sortie standard stdout.

Je crois que vous devez fermer le curseur avant de valider la connexion.

Pourquoi? L' API C MySQL, qui est la base pour MySQLdb, ne pas mettre en œuvre tout objet curseur, implicite dans la documentation du module: "MySQL ne prend pas en charge les curseurs; toutefois, les curseurs sont facilement émulé." En effet, l' MySQLdb.cursors.BaseCursor classe hérite directement de object et n'impose aucune restriction sur les curseurs à l'égard de commit/rollback. Un Oracle developer avait ceci à dire:

cnx.commit() avant de cur.close() semble le plus logique pour moi. Peut-être que vous qui peut passer par la règle: "Fermer le curseur si vous n'avez pas besoin de plus." Donc commit() avant de fermer le curseur. En fin de compte, pour Connecteur/Python, il n'a pas beaucoup de différence, mais ou d'autres bases de données qu'il peut.

Je m'attends c'est aussi proche que vous allez obtenir à la "norme" sur ce sujet.

Est-il un avantage significatif à trouver des ensembles d'opérations qui ne nécessitent pas d'intermédiaire s'engage, de sorte que vous n'avez 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 décider sur un projet de convention et le bâton avec elle.

Il y a beaucoup de frais généraux pour l'obtention de nouveaux curseurs, ou est-il tout simplement pas une grosse affaire?

La surcharge est négligeable, et ne touchez pas au serveur de base de données; c'est entièrement à l'intérieur de la mise en œuvre 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.

Remontant à plus tôt, quand nous discutions with, peut-être que maintenant vous pouvez comprendre pourquoi l' MySQLdb.Connection classe __enter__ et __exit__ méthodes vous donner un tout nouveau curseur de l'objet dans chaque with bloc et ne pas la peine de garder trace de celui-ci ou de sa fermeture à la fin du bloc. Il est assez léger et il existe purement pour votre commodité.

Si c'est vraiment important pour vous de s'immiscer dans l'objet curseur, vous pouvez utiliser contextlib.fermeture pour compenser le fait que le curseur de l'objet n'a pas défini __exit__ méthode. Pour cette question, vous pouvez également l'utiliser pour forcer la connexion de l'objet à fermer lui-même à la sortie d'un with bloc. Cela devrait sortie "curs est fermé; 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'ira pas en appel de l'argument de l'objet __enter__ et __exit__ méthodes; il sera seulement appel à l'argument de l'objet close méthode à la fin de l' with bloc. (Pour le voir en action, il suffit de définir une classe à l' Foo avec __enter__, __exit__, et close méthodes contenant simple print des déclarations, et de comparer ce qui se passe quand vous n' with Foo(): pass de ce qui se passe quand vous n' with closing(Foo()): pass.) Cela a deux conséquences importantes:

Tout d'abord, si le mode de validation automatique est activée, MySQLdb sera BEGIN une transaction explicite sur le serveur lorsque vous utilisez with connection et commit ou rollback de la transaction à la fin du bloc. Ce sont des comportements par défaut. Si vous avez l'habitude d'utiliser with connection vous pourriez penser de validation automatique est désactivé alors qu'en réalité, c'est l'objet de connexion c'est la gestion de vos transactions "derrière les coulisses" grâce à l' with de la syntaxe. Vous pourriez avoir une mauvaise surprise si vous ajoutez closing de votre code et soudain, vos déclarations sont autocommitting!

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 se lie à un nouveau curseur objet d' VAR. Dans ce dernier cas, vous n'avez pas un accès direct à l'objet de connexion! Au lieu de cela, vous devez utiliser le curseur de l' connection d'attribut, qui fournit l'accès proxy à l'origine de la correction. Lorsque le curseur est fermé, son connection de définir l'attribut None. Il en résulte un abandon de la connexion qui va rester jusqu'à ce que l'un des cas suivants:

  • Toutes les références à l'curseur sont supprimés
  • Le curseur est hors de portée
  • Le temps de connexion à
  • La connexion est fermée manuellement via les outils d'administration de serveur

Vous pouvez le tester par la surveillance des connexions ouvertes dans Workbench 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

34voto

Roman Podlinov Points 1294

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 parce que c'est une ressource non gérée) automatiquement. L'avantage est qu'il fermera le curseur en cas d'exception aussi.

 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()
 

5voto

nct25 Points 110

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é ainsi (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 d'un curseur à l'exécution d'une autre variable, libérant ainsi votre curseur pour faire une deuxième exécution. Vous rencontrez des problèmes de cette façon, si vous utilisez la fetchone(), et le besoin de faire un deuxième curseur d'exécution avant d'avoir réitéré par le biais de tous les résultats de la première requête.

Sinon, je dirais juste fermer vos curseurs dès que vous avez terminé toutes les données hors d'eux. De cette façon, vous n'avez pas à vous soucier d'attacher les extrémités lâches plus loin dans votre code.

-6voto

KilledKenny Points 208

Je suggère de le faire comme php et mysql. Commencez par 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 message d'erreur 50x (ne vous souvenez pas de quelle erreur interne se trouve). Et gardez-le ouvert pendant toute la session et fermez-le lorsque vous savez que vous n'en aurez plus besoin.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X