¿Odias los anuncios? Ir Sin publicidad Hoy

UTF-8 y Unicode ¿Por qué ese emoji rompió tu base de datos?

Actualizado en

Tu aplicación insertó un emoji y MySQL lanzó Incorrect string value. Aquí está la razón — puntos de código vs bytes, el engaño de utf8 vs utf8mb4 en MySQL, los pares de representación de JavaScript y cómo solucionarlo realmente.

UTF-8 y Unicode: ¿Por qué ese emoji rompió tu base de datos 1
ANUNCIO · ¿ELIMINAR?

Tu aplicación funcionaba bien. Luego un usuario escribió un emoji en un campo de texto, y MySQL lanzó Incorrect string value: '\xF0\x9F\x98\x80' for column 'bio'. O tal vez el emoji desapareció silenciosamente. O el INSERT completo falló y perdiste datos. Todo por un carácter de cuatro bytes que tu columna de la base de datos no esperaba.

Esto no es un truco de MySQL ni un error en PHP. Es una consecuencia de cómo funciona la codificación Unicode → UTF-8, y una vez que lo entiendes, ya nunca volverás a sorprenderte por ello.

Puntos de código vs bytes: la diferencia real

Unicode asigna a cada carácter un punto de código — un número. La letra A es U+0041. El signo de euro es U+20AC. El emoji 😀 es U+1F600. Eso es la identidad abstracta del carácter.

UTF-8 es un codificación — una forma de almacenar puntos de código como bytes. La trampa es que UTF-8 es de ancho variable: utiliza de 1 a 4 bytes según el valor del punto de código. Así se mantiene compatible con ASCII (todos los caracteres ASCII ocupan 1 byte en UTF-8) mientras también codifica cada carácter existente.

Las reglas de codificación:

  • U+0000 a U+007F (ASCII) → 1 byte
  • U+0080 a U+07FF (latín extendido, árabe, hebreo, etc.) → 2 bytes
  • U+0800 a U+FFFF (la mayoría de los caracteres CJK, puntuación, símbolos) → 3 bytes
  • U+10000 a U+10FFFF (emojis, scripts raras, símbolos matemáticos) → 4 bytes

Es por eso que el emoji 😀 (U+1F600) ocupa 4 bytes: su punto de código está por encima de U+FFFF.

Tamaños de bytes en UTF-8: una tabla de referencia

Aquí está lo que cuestan en bytes los caracteres comunes:

CarácterDescripciónPunto de código UnicodeBytes UTF-8 (hex)Recuento de Bytes
ALetra latina mayúscula AU+0041411
éLetra e con acento agudoU+00E9C3 A92
Signo de euroU+20ACE2 82 AC3
Carácter chino "medio"U+4E2DE4 B8 AD3
😀Emoji de sonrisaU+1F600F0 9F 98 804
🔥Emoji de fuegoU+1F525F0 9F 94 A54
𝕳H fraktur matemáticoU+1D573F0 9D 95 B34

Para verificarlo por sí mismo, usa el Calculadora de longitud de cadena — muestra tanto el número de caracteres como el número de bytes para cualquier texto que pegues. Pega 😀 y verás 1 carácter pero 4 bytes.

El engaño de MySQL utf8

Aquí es donde los desarrolladores se quedan atrapados. MySQL tiene un conjunto de caracteres llamado utf8. Suena correcto. Está mal — el conjunto de caracteres utf8 de MySQL solo soporta secuencias de hasta 3 bytes. Los emojis (4 bytes) no están soportados. utf8 El conjunto de caracteres UTF-8 completo en MySQL es

(introducido en MySQL 5.5.3, lanzado en 2010). Si tu columna utiliza utf8mb4 y alguien inserta un emoji, MySQL lo trunca silenciosamente o lanza: utf8 También actualiza la configuración de conexión de tu aplicación a la base de datos. En MySQL PDO:

Incorrect string value: '\xF0\x9F\x98\x80' for column 'bio' at row 1

La solución:

-- 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;

La trampa de VARCHAR(255)

$pdo = new PDO($dsn, $user, $pass, [
    PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"
]);

en MySQL significa 255

VARCHAR(255) caracteres , no 255 bytes — pero el límite de almacenamiento para una sola fila se calcula en bytes. Con, cada carácter puede ocupar hasta 4 bytes, por lo que una utf8mb4columna reserva hasta 1.020 bytes. Esto importa cuando usas el límite predeterminado de índice de InnoDB (767 bytes) para columnas VARCHAR: VARCHAR(255) El problema de los pares de surrogado 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 utiliza UTF-16 internamente, no UTF-8. Y UTF-16 tiene su propia codificación de múltiples unidades para puntos de código por encima de U+FFFF:

pares de surrogado — dos unidades de código de 16 bits que juntas representan un carácter. Esto significa que

en JavaScript cuenta unidades de código UTF-16, no caracteres: String.length Para operaciones de cadena que necesitan ser conscientes de caracteres, usa el operador de expansión o

'😀'.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)

El ejemplo del emoji familiar vale la pena detenerse. 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)

es cuatro emojis unidos por separadores de ancho cero (U+200D). Un enfoque naïf 👨‍👩‍👧‍👦 te da 11. Clusters reales de grafemas: 1. Esto importa si estás implementando límites de caracteres — un límite basado en .length se comportará inesperadamente cuando los usuarios escriban secuencias de emojis. String.length Cómo verificar la codificación en la práctica

en PHP cuenta bytes, no caracteres. Esto atrapa constantemente a los desarrolladores de PHP al trabajar con cadenas multibyte — una cadena de 10 emojis reportará una longitud de 40. Usa

Pitón

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() cuando necesites contar caracteres. mb_strlen() Verificación rápida

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 quieres ver el conteo de bytes vs caracteres para cualquier texto sin escribir código, el

lo maneja instantáneamente — pega cualquier texto y muestra el conteo de caracteres, palabras y bytes al lado. Calculadora de longitud de cadena La lista de verificación del error de codificación

Conjunto de caracteres de MySQL:

  • ¿Es? ¿Es? utf8mb4, no utf8? Verifica con SHOW CREATE TABLE.
  • Conexión de MySQL: ¿Está tu aplicación enviando? SET NAMES utf8mb4? Verifica tu DSN o configuración de conexión.
  • strlen vs mb_strlen en PHP: ¿Estás usando funciones que cuentan bytes donde necesitas contar caracteres?
  • .length en JavaScript: ¿Estás contando unidades de código donde necesitas clusters de grafemas?
  • Encabezados HTTP: ¿Está enviando tu respuesta? Content-Type: text/html; charset=utf-8?
  • Codificación de archivos: ¿Están tus archivos originales y tus respaldos SQL guardados en UTF-8 sin BOM?
¿Quieres eliminar publicidad? Adiós publicidad hoy

Instalar extensiones

Agregue herramientas IO a su navegador favorito para obtener acceso instantáneo y búsquedas más rápidas

añadir Extensión de Chrome añadir Extensión de borde añadir Extensión de Firefox añadir Extensión de Opera

¡El marcador ha llegado!

Marcador es una forma divertida de llevar un registro de tus juegos, todos los datos se almacenan en tu navegador. ¡Próximamente habrá más funciones!

ANUNCIO · ¿ELIMINAR?
ANUNCIO · ¿ELIMINAR?
ANUNCIO · ¿ELIMINAR?

Noticias Aspectos técnicos clave

Involucrarse

Ayúdanos a seguir brindando valiosas herramientas gratuitas

Invítame a un café
ANUNCIO · ¿ELIMINAR?