UTF-8 et Unicode Pourquoi un emoji a brisé votre base de données
Votre application a inséré un emoji et MySQL a lancé une erreur Incorrect string value. Voici pourquoi — les points de code versus les octets, le mensonge de MySQL utf8 vs utf8mb4, les paires de surrogats JavaScript, et la manière réelle de les corriger.
Votre application fonctionnait parfaitement. Puis un utilisateur a tapé un emoji dans un champ de texte, et MySQL a lancé Incorrect string value: '\xF0\x9F\x98\x80' for column 'bio'. Ou peut-être que l'emoji est apparu sans que rien ne se produise. Ou que l'INSERT a échoué entièrement et que vous avez perdu des données. Tout cela à cause d'un caractère de quatre octets que la colonne de votre base de données n'attendait pas.
Ce n'est pas un défaut de MySQL ni un bug de PHP. C'est une conséquence de la manière dont Unicode → UTF-8 fonctionne réellement, et une fois que vous l'avez compris, vous ne serez plus surpris par cela.
Code points vs octets : la différence réelle
Unicode attribue à chaque caractère un code point — un nombre. La lettre A est U+0041. Le signe euro est U+20AC. L'emoji 😀 est U+1F600. C'est l'identité abstraite du caractère.
UTF-8 est un encodage — une manière de stocker les code points sous forme d'octets. Le truc est que UTF-8 est de largeur variable : il utilise de 1 à 4 octets selon la valeur du code point. C'est ainsi qu'il reste compatible avec ASCII (tous les caractères ASCII sont 1 octet en UTF-8) tout en permettant d'encoder chaque caractère existant.
Les règles d'encodage :
- U+0000 à U+007F (ASCII) → 1 octet
- U+0080 à U+07FF (étendu latin, arabe, hébreu, etc.) → 2 octets
- U+0800 à U+FFFF (la plupart des caractères CJK, ponctuation, symboles) → 3 octets
- U+10000 à U+10FFFF (emojis, scripts rares, symboles mathématiques) → 4 octets
C'est pour cette raison que l'emoji 😀 (U+1F600) occupe 4 octets : son code point est au-dessus de U+FFFF.
Tailles en octets de UTF-8 : une table de référence
Voici ce que coûtent réellement les caractères courants en octets :
| Caractère | Description | Point de code Unicode | Octets UTF-8 (hexadécimal) | Nombre d'octets |
|---|---|---|---|---|
| UN | Lettre latine majuscule A | U+0041 | 41 | 1 |
| é | Lettre e avec accent aigu | U+00E9 | C3 A9 | 2 |
| € | Signe euro | U+20AC | E2 82 AC | 3 |
| 中 | Caractère chinois « milieu » | U+4E2D | E4 B8 AD | 3 |
| 😀 | Emoji souriant | U+1F600 | F0 9F 98 80 | 4 |
| 🔥 | Emoji feu | U+1F525 | F0 9F 94 A5 | 4 |
| 𝕳 | H fraktur mathématique | U+1D573 | F0 9D 95 B3 | 4 |
Pour le vérifier vous-même, utilisez le Calculateur de longueur de chaîne — il affiche à la fois le nombre de caractères et le nombre d'octets pour tout texte que vous collez. Collez 😀 et vous verrez 1 caractère mais 4 octets.
L'erreur de MySQL sur utf8
Voici où les développeurs sont victimes. MySQL dispose d'un jeu de caractères appelé utf8. Cela semble correct. C'est faux — le jeu de caractères utf8 de MySQL ne supporte que des séquences de 3 octets. Les emojis (4 octets) ne sont pas supportés. utf8 Le jeu de caractères UTF-8 complet dans MySQL est
(introduit dans MySQL 5.5.3, publié en 2010). Si votre colonne utilise utf8mb4 et qu'un utilisateur insère un emoji, MySQL soit tronque silencieusement les données soit lance : utf8 La solution :
Incorrect string value: '\xF0\x9F\x98\x80' for column 'bio' at row 1
Mettez également à jour la configuration de la connexion à la base de données de votre application. Dans MySQL PDO :
-- Convert the table
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Or for a specific column
ALTER TABLE users MODIFY bio TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- And set your connection charset
SET NAMES utf8mb4;
Le piège de VARCHAR(255)
$pdo = new PDO($dsn, $user, $pass, [
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"
]);
dans MySQL signifie 255
VARCHAR(255) caractères , pas 255 octets — mais la limite de stockage d'une seule ligne est calculée en octets. Avec, chaque caractère peut prendre jusqu'à 4 octets, donc une utf8mb4colonne réserve jusqu'à 1 020 octets. Cela importe lorsque vous utilisez la limite par défaut de l'index prefix (767 octets) pour indexer les colonnes varchar : VARCHAR(255) Le problème des paires de surrogats en JavaScript
-- This fails on older MySQL with default innodb_large_prefix=OFF
CREATE INDEX idx_email ON users (email); -- email is VARCHAR(255) utf8mb4
-- Fix: use a prefix index
CREATE INDEX idx_email ON users (email(191)); -- 191 * 4 = 764 bytes, under 767
-- Or enable large prefixes (MySQL 5.7+, on by default in 8.0)
-- Set innodb_large_prefix = ON in my.cnf
JavaScript utilise UTF-16 internement, pas UTF-8. Et UTF-16 a son propre encodage multi-unité pour les code points au-dessus de U+FFFF :
paires de surrogats — deux unités de code de 16 bits qui représentent ensemble un caractère. Cela signifie que
dans JavaScript compte les unités de code UTF-16, pas les caractères : String.length Pour les opérations de chaîne nécessitant une connaissance caractéristique, utilisez l'opérateur de diffusion ou
'😀'.length // → 2 (two UTF-16 surrogate code units)
[...'😀'].length // → 1 (spread operator uses Unicode code points)
// Checking the character at index 0
'😀'[0] // → '\uD83D' (the high surrogate, not the emoji)
'😀'.codePointAt(0) // → 128512 (0x1F600, correct)
L'exemple de l'emoji familial vaut la peine d'être arrêté. Intl.Segmenter:
// Count actual grapheme clusters
const segmenter = new Intl.Segmenter();
const chars = [...segmenter.segment('👨👩👧👦')];
chars.length // → 1 (family emoji is one grapheme cluster)
'👨👩👧👦'.length // → 11 (UTF-16 code units)
est composé de quatre emojis reliés par des jointures de largeur nulle (U+200D). Une approche naïve 👨👩👧👦 vous donne 11. Les clusters graphémiques réels : 1. Cela importe si vous implémentez des limites de caractères — une limite basée sur .length se comportera de manière inattendue lorsque les utilisateurs saisissent des séquences d'emojis. String.length Comment vérifier l'encodage en pratique
dans PHP compte les octets, pas les caractères. Cela fait constamment tomber les développeurs PHP lorsqu'ils gèrent des chaînes multioctets — une chaîne de 10 emojis affichera une longueur de 40. Utilisez
Python
s = '😀'
print(len(s)) # 1 (Python 3 counts code points)
print(len(s.encode('utf-8'))) # 4 bytes
print(s.encode('utf-8').hex()) # f09f9880
PHP
$s = '😀';
echo strlen($s); // 4 (bytes, not characters)
echo mb_strlen($s); // 1 (characters)
echo mb_strlen($s, '8bit'); // 4 (bytes, explicit)
strlen() lorsque vous souhaitez compter les caractères. mb_strlen() Vérification rapide
MySQL
-- Check charset of a table
SHOW CREATE TABLE users\G
-- Check charset of a specific column
SELECT CHARACTER_SET_NAME, COLLATION_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'users' AND COLUMN_NAME = 'bio';
-- Character count vs byte count
SELECT CHAR_LENGTH('😀'), LENGTH('😀');
-- → 1, 4
Si vous souhaitez voir le nombre de caractères et le nombre d'octets pour un texte arbitraire sans écrire de code, le
le gère instantanément — collez tout texte et il affiche le nombre de caractères, le nombre de mots et le nombre d'octets côte à côte. Calculateur de longueur de chaîne La checklist de la bug d'encodage
Jeu de caractères MySQL :
- Est-il ? Vérifiez avec
utf8mb4, et non pasutf8Connexion MySQL :SHOW CREATE TABLE. - Votre application envoie-t-elle ? Vérifiez votre DSN ou la configuration de connexion.
SET NAMES utf8mb4PHP strlen vs mb_strlen : - Utilisez-vous des fonctions de comptage en octets où vous avez besoin de compter les caractères ? JavaScript .length :
- Comptez-vous les unités de code où vous avez besoin de clusters graphémiques ? En-têtes HTTP :
- Votre réponse envoie-t-elle Format de fichiers :
Content-Type: text/html; charset=utf-8? - Vos fichiers sources et vos dumps SQL sont-ils enregistrés en UTF-8 sans BOM ? UTF-8 et Unicode : Pourquoi un emoji a brisé votre base de données 2
Installez nos extensions
Ajoutez des outils IO à votre navigateur préféré pour un accès instantané et une recherche plus rapide
恵 Le Tableau de Bord Est Arrivé !
Tableau de Bord est une façon amusante de suivre vos jeux, toutes les données sont stockées dans votre navigateur. D'autres fonctionnalités arrivent bientôt !
Outils essentiels
Tout voir Nouveautés
Tout voirMise à jour: Notre dernier outil a été ajouté le 15 juin 2026
