Le moins qu'on puisse dire est que la gestion des chaînes de caractères et de leur encodage sous MySQL est chaotique. Commençons déjà par définir deux éléments :

  • jeu de caractère : il s'agit de la façon dont les caractères sont stockés (ou représentés en mémoire). Pour pouvoir lire une chaîne de caractères, il est nécessaire de connaître son jeu de caractères[1]
  • collation : il s'agit de l'ensemble des règles utilisées pour comparer plusieurs chaines entre elles (notamment lors des tris). Cela n'influe pas sur le stockage ou la lecture des caractères, il n'en sera donc pas question ici

Les paramètres influant sur l'encodage des caractères sont multiples, on trouve des paramètres côté serveur et des paramètres côté client. Une seule erreur ou inattention, et vous êtes bons pour convertir votre base ligne par ligne.

Paramètres côté serveur

Vous pouvez définir des jeux de caractère au niveau serveur, base, table et colonne. Si vous ne précisez pas de valeur pour un niveau, c'est celui du dessus qui est utilisé. Mais attention, si j'ai une base en UTF-8, que je crée une table sans rien préciser, cela revient à indiquer le même charset que la base pour son default charset. Cette nuance est importante, cela signifie que si vous passez la base en latin1 (l'équivalent de ISO-8859-1 chez les dauphins), la table sera toujours en UTF-8.

Pour définir l'encodage au niveau du serveur, on passe par character-set-server (soit en ligne de commande soit via le fichier de configuration du serveur). Cela définit le jeu de caractère utilisé pour les tables si celui-ci n'est pas précisé.

Pour les bases et les tables, il s'agit de la clause DEFAULT CHARSET charset passée au CREATE.

Pour les colonnes, il se définit via la clause CHARSET à la suite du type de données.

Par le biais de ces paramètres, vous indiquez dans quel codage vous voulez que vos données soient enregistrées. Le problème est que si au niveau de votre client vous vous trompez dans les paramètres et que vous envoyez des données incompatibles avec le codage de stockage, vous n'aurez pas d'erreur (on peut stocker le caractère ȧ dans une table en latin1). Par contre, si vous avez bien fait les choses, vous aurez un warning du type Incorrect string value: '\xCB\x99' for column 'pouet' at row 1. Si vous utilisez MySQL via un langage de programmation, il vous faudra faire un show warnings après chaque requête (à moins que vous n'ayez un langage qui vous épargne cette peine et lance des exceptions).

Paramètres côté client

Il s'agit des variables character_set_client, character_set_connection et character_set_results (oui, il parait évident qu'un seul paramètre aurait été trop simple).

La première, character_set_client est définit soit via un SET au sein de la connexion, soit via l'option --default-charset des clients en ligne de commande. Mais attention, cette variable n'est pas forcément prise en compte par le serveur (pour de la retrocompatibilité avec MySQL 4 ou parce que sa valeur n'est pas prise en charge par le serveur). Elle définit l'encodage des chaînes envoyées par le client. Donc, si vous utilisez Java ou une console en UTF-8 (ou PHP et que vous n'êtes pas dans un garage), cette variable doit contenir la valeur utf8.

La seconde character_set_connection, vous ne devriez pas en avoir besoin, elle doit être la même que character_set_client et la description est assez confuse. Elle indique vers quel jeu de caractères doivent être traduites les chaines reçues par le serveur. J'avoue que je ne comprends pas vraiment l'intérêt de la chose (à partir du moment où le client a le bon character_set_client, il a juste à envoyer ça au serveur avec les infos d'encodage et celui ci décode puis traduit vers le charset de la colonne correspondante, mais apparemment y a une nuance supplémentaire). Si quelqu'un a plus d'infos, je suis (très) intéressé.

La dernière character_set_results indique dans quel jeu de caractère vous voulez les résultats. Pourquoi avoir fait la nuance avec character_set_client je n'en sais rien, a priori il faut être pas mal atteint pour avoir une appli qui lit les données dans un charset différent de celui dans lesquelles elle les écrit.

Alors, que faire ?

C'est le plus intéressant quand même, si vous êtes pressé, vous auriez du commencer par là...

Le plus simple est d'avoir tout en UTF-8. De cette façon, vous pouvez gérer l'intégralité des caractères utilisés dans le monde (ça en fait un paquet). Vous ne posez aucune limite quant au traductions et vous ne vous posez pas de question. Si vous avez la main sur le serveur, vous lui passez un character-set-server=utf8 pour que les valeurs par défaut soient les bonnes. Attention : cela n'influera que sur les nouvelles bases créées, les anciennes garderont leur charset. Attention 2 : cela n'influe pas du tout sur le client, il utilisera ses propres paramètres de configuration qui sont toujours en latin1 par défaut alors que la vaste majorité des distributions utilisent l'UTF-8 et ne changent pas ce paramètre.

La configuration du client dépend de celui qu'on utilise. Pour la ligne de commande, on éditera le fichier /etc/my.cnf ou ~/.my.cnf selon ses droits et ce que l'on a envie de faire pour y ajouter un default-character-set=utf8. Pour les gens utilisant JDBC, la détection devrait se faire automatiquement à partir de MySQL 4.1, sinon il faut passer les options characterEncoding et useUnicode dans l'URL. Sous PHP, avant la version 5.2.3, il faut envoyer manuellement un SET NAMES 'utf8' (qui positionne les 3 variables client à la valeur qu'on lui passe en paramètre) via mysql_query, à partir de la version 5.2.3, la fonction mysql_set_charset s'occupe de tout ça.

Notes

[1] Il existe des mécanimes de détection automatique mais ceux-ci ont des résultats généralement assez aléatoires. Par exemple, une chaine UTF-8 sera également une chaîne ISO-8858-1 valide, simplement, les é seront lus é, au niveau de la détection, pas de possibilité de s'en rendre compte.