MySQL possède un jeu de caractères nommé utf8 qui n'est pas de l'UTF-8. Il stocke au plus trois octets par caractère, et tous les emojis ainsi que certains caractères plus rares en demandent quatre. Un texte qui en contient un est soit rejeté, soit tronqué en silence à cet endroit — voilà pourquoi un commentaire peut disparaître à partir d'un paragraphe.

Utilisez utf8mb4, partout

ALTER DATABASE shop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE comments CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Et dans la connexion, la moitié qu'on oublie

// PDO
new PDO('mysql:host=localhost;dbname=shop;charset=utf8mb4', $u, $p);
; my.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
Une table utf8mb4 lue sur une connexion latin1 renvoie du texte abîmé et en réécrit du tout aussi abîmé. La table est correcte, la connexion ne l'est pas, et les données se corrompent en chemin. Réglez les deux.

Quelle collation

  • utf8mb4_unicode_ci — trie et compare selon les règles Unicode, toutes langues confondues. Le choix sûr par défaut.
  • utf8mb4_0900_ai_ci — le défaut de MySQL 8, Unicode plus récent, un peu plus rapide.
  • utf8mb4_bin — les octets exacts. Pour les jetons et les empreintes, jamais pour des noms.

C'est la collation qui décide de la comparaison. Sous une collation insensible à la casse, WHERE name = 'ali' correspond à Ali — ce que vous voulez d'ordinaire pour une recherche, et non pour une clé d'API.

Trouver ce qui cloche encore

SELECT table_name, table_collation
  FROM information_schema.tables
 WHERE table_schema = 'shop' AND table_collation NOT LIKE 'utf8mb4%';

SELECT table_name, column_name, character_set_name
  FROM information_schema.columns
 WHERE table_schema = 'shop' AND character_set_name IS NOT NULL
   AND character_set_name <> 'utf8mb4';

Les colonnes se convertissent séparément des tables. Une table déclarée utf8mb4 peut encore contenir des colonnes latin1 datant d'années — et ce sont celles-là qui cassent.

Sauvegardez avant de convertir. L'opération réécrit chaque ligne, elle peut durer sur une grande table, et les limites de taille d'index mordent parfois sur les versions plus anciennes.