Vous êtes ici : Accueil Tutoriels oursql : réponse à des problèmes courants

oursql : réponse à des problèmes courants

Réponses aux erreurs 2006 mysql has gone away, 2013 Lost connection to MySQL server during query et 2027 malformed packet.

Le module oursql est un module permettant l'accès à MySQL. A cet instant, il est le seul à fonctionner avec Python3. Il est donc un passage obligé pour ceux qui doivent utiliser cette nouvelle branche.

J'ai récemment eu à intervenir sur un cas particulier : Il s'agissait d'une application web en cours de conception qui utilisait un formulaire ajax. De manière totalement non prévisible, l'utilisation du formulaire faisait planter le serveur avec des erreurs sur le nombre de paramètres passés lors de l'exécution d'une requête, puis l'erreur 2006 mysql has gone away. Sur internet, des astuces recommandaient de faire une boucle d'exception pour fermer le curseur ou la connexion pour en ouvrir un ou une autre et ceci aux différents stades où l'erreur se produisaient. Ce qui engendrait des erreurs 2013 Lost connection to MySQL server during query ou encore 2027 malformed packet.

On peut même aller plus loin en utilisant une boucle : tant que l'on a une erreur, fermer le curseur, la connexion, ouvrir une nouvelle connexion, un nouveau curseur et exécuter à nouveau la requête. En procédant ainsi, on ne fait qu'empirer la situation. On ne corrige pas une telle erreur par ce qui ressemble à de l'entêtement. Ou plutôt si, on le fait mais en posant des sondes et en analysant ce qu'il se passe, afin de comprendre. Et après, on jette ce code et on se pose les vrais questions.

Parce que, si on cherche un peu sur internet, on voit effectivement quelques astuces qui s'apparentent plus à du bricolage qu'à quelque chose de sérieux pour aboutir finalement à la conclusion que c'est la faute de oursql qui n'est pas à la hauteur et que de toute façon, MySQL n'est pas une vraie base de données et qu'elle n'est pas faite pour la production. Certes, moi-même je ne suis pas un amateur de MySQL, préférant largement PostGRESQL, mais il est absolument impossible de tenir ce discours, car il est tout sauf exact.

A ces accusations, j'opposerais la réalité : On peut trouver des tas de défauts à oursql, en particulier sur sa lenteur par rapport à MySQLDb, mais il est n'est pas exact de penser que ce module présente des anomalies qui le rendent inutilisables en production. Il n'est pas parfaitement optimisé, mais il est parfaitement fonctionnel. Lorsque je lis que la solution est de ne plus utiliser les requêtes préparées, mais directement des requêtes avec les variables dedans, mon sang ne fait qu'un tour : Non, pas çà !

De plus, l'utilisation professionnelle et efficace de MySQL, de nos jours, est une réalité. Je ne dirais pas forcément du bien des anciennes versions, ni de l'histoire passée de MySQL, mais aujourd'hui, il s'agit d'une base sérieuse, efficace et tout à fait apte à gérer des données correctement. La seule différence entre MySQL et une base comme PostGRESQL tient à leurs fonctionnalités, leur déployabilité, leur extensibilité et surtout, la partie la plus visible, leur langage SQL.

Donc, la faute ne revient pas à MySQL et contrairement à ce que l'on entend ou lit, MySQL ne perd pas automatiquement les connexions, ne les ferme pas automatiquement et l'erreur que l'on a là ne lui est absolument pas imputable.

Cependant, cette confusion peut être comprise et est légitime : MySQL est très souvent associée à PHP, qui a un fonctionnement complètement différent de celui de Python. En PHP, à chaque requête, le code source est entièrement relu (modulo une possibilité de mise en cache d'opcode) et toutes les instructions d'initialisation sont repassées : à chaque requête, sa connexion qui est automatiquement fermée à la fin du renvoi de la réponse. Mais Python n'est pas PHP. Un serveur Web Python se lance, le code source est compilé et le serveur est alors prêt à répondre à toutes les requêtes en ayant fait toutes les procédures d'initialisation. On appelle cela la persistance.

Cette différence de comportement fait que ce qui est vrai en PHP ne l'est pas forcément en Python. Python n'a pas besoin de fermer sa connexion à la base de données et de l'ouvrir à nouveau à la demande. En réalité, il faut revenir à la base des recommandations SQL pour comprendre l'erreur.

Lorsque de telles erreurs se produisent, elle sont certes émises par le cœur du module oursql, mais en réalité, elles font suite à une mauvaise utilisation des curseurs, ou de l'utilisation de plusieurs objets connexion qui entrent en conflits. Il vaut mieux donc ne pas conclure tout de suite au supposé manque de fiabilité de oursql, mais plutôt se pencher sur son propre code et voir ce qui cloche.

La solution consiste à utiliser un tunnel de traitement unique pour gérer toutes les requêtes de l'application.

On peut alors créer un composant dédié, se créer une panoplie de fonctions pour gérer tous les cas particuliers ou alors faire appel à SQLAlchemy, un module de haut niveau qui va gérer tout çà pour nous et qui sera ce tunnel de traitement.

En effet, oursql est un module bas niveau. Lorsqu'on l'utilise, il faut gérer correctement la connexion et utiliser proprement les curseurs. Cependant, dans des cas où le code n'est pas linéaire et que le module est mal utilisé, la détection d'une mauvaise pratique conduisant à des erreurs aussi difficilement identifiables que celles présentées est très difficile.

Or, me direz-vous, SQLAlchemy est un ORM. Lorsque l'on utilise oursql directement, c'est que l'on écrit les requêtes soi-même. C'est vrai, SQLAlchemy est connu comme étant un (excellent) ORM, mais il permet également d'écrire soi-même ses requêtes, à ceci près qu'au lieu d'écrire :

select * from table where id=?

Il faudra écrire:

select * from table where id=:id

Et que au lieu d'appeler une fonction d'exécution en lui donnant une liste de valeurs à connecter à la requête, il faudra lui passer les arguments de manière nommée (ou en dépilant un dictionnaire).

Pour commencer, lorsque l'on est dans un projet Web avec SQLAlchemy, on a déjà tout qui est configuré. Il suffit alors de récupérer la connexion ainsi:

connection = DBSession.bind

Dans le cas on l'on utilisait que oursql, il faut alors revoir la manière de créer cette connexion:

connection = create_engine('mysql://user:pass@host/db')

Il ne reste plus qu'à préparer les requêtes proprement et de manière sécurisée :

from sqlalchemy import text
req = text('select * from table where id=:id')

Et on peut enfin exécuter les requêtes (deux méthodes) :

resultat = e.execute(req, id=id)
resultat = e.execute(req, **{'id': id})

Le résultat est un itérable contenant des objets de type RowProxy qui peuvent être accédés ainsi :

resultat[0][0]
resultat[0]['id']
resultat[0].id

Et voilà. En utilisant ce composant, tous les problèmes exposés ici seront réglés. C'est une solution qui a l'avantage de ne pas remettre en cause trop de vos composants et qui s'intègre aisément, en particulier dans les projets Web.

Contenus correlés
Le point sur Python3 + MySQL
Spinner