UTF-8 e Unicode Por que aquele emoji quebrou seu banco de dados
Seu aplicativo inseriu um emoji e o MySQL lançou erro de valor incorreto. Veja por que — pontos de código versus bytes, a mentira do MySQL utf8 versus utf8mb4, pares de surrogate do JavaScript e como realmente corrigir isso.
Seu aplicativo estava funcionando bem. Então, um usuário digitou um emoji em um campo de texto, e o MySQL lançou Incorrect string value: '\xF0\x9F\x98\x80' for column 'bio'. Ou talvez o emoji tenha desaparecido silenciosamente. Ou a inserção inteira tenha falhado e você tenha perdido dados. Tudo porque de um caractere de 4 bytes que a coluna do seu banco de dados não esperava.
Isso não é um truque do MySQL nem um erro do PHP. É uma consequência do funcionamento real da codificação Unicode → UTF-8, e uma vez que você entende isso, nunca mais será surpreendido por isso novamente.
Pontos de código versus bytes: a diferença real
O Unicode atribui a cada caractere um ponto de código — um número. A letra A é U+0041. O símbolo do euro é U+20AC. O emoji 😀 é U+1F600. Isso é a identidade abstrata do caractere.
O UTF-8 é um codificação — uma forma de armazenar pontos de código como bytes. O truque é que o UTF-8 é de largura variável: ele usa de 1 a 4 bytes, dependendo do valor do ponto de código. Assim, ele mantém compatibilidade com o ASCII (todos os caracteres ASCII são 1 byte no UTF-8) enquanto também codifica todos os caracteres existentes.
As regras de codificação:
- U+0000 a U+007F (ASCII) → 1 byte
- U+0080 a U+07FF (extensão latina, árabe, hebraico, etc.) → 2 bytes
- U+0800 a U+FFFF (a maioria dos caracteres CJK, pontuação, símbolos) → 3 bytes
- U+10000 a U+10FFFF (emojis, scripts raras, símbolos matemáticos) → 4 bytes
É por isso que o emoji 😀 (U+1F600) ocupa 4 bytes: seu ponto de código está acima de U+FFFF.
Tamanhos em bytes do UTF-8: uma tabela de referência
Aqui está o que os caracteres comuns realmente custam em bytes:
| Caractere | Descrição | Ponto de código Unicode | Bytes UTF-8 (hexadecimal) | Contagem de Bytes |
|---|---|---|---|---|
| A | Letra latina maiúscula A | U+0041 | 41 | 1 |
| é | Letra e com acento agudo | U+00E9 | C3 A9 | 2 |
| € | Símbolo do euro | U+20AC | E2 82 AC | 3 |
| 中 | Caractere chinês "meio" | U+4E2D | E4 B8 AD | 3 |
| 😀 | Emoji de sorriso | U+1F600 | F0 9F 98 80 | 4 |
| 🔥 | Emoji de fogo | U+1F525 | F0 9F 94 A5 | 4 |
| 𝕳 | H fraktur matemática | U+1D573 | F0 9D 95 B3 | 4 |
Para verificar isso por si mesmo, use o Calculadora de Comprimento de String — ele mostra tanto a contagem de caracteres quanto a contagem de bytes para qualquer texto que você colar. Cole 😀 e você verá 1 caractere mas 4 bytes.
O engano do MySQL utf8
Aqui é onde os desenvolvedores são prejudicados. O MySQL possui um conjunto de caracteres chamado utf8. Parece certo. Está errado — o MySQL só suporta sequências de até 3 bytes. Os emojis (4 bytes) não são suportados. utf8 O conjunto de caracteres UTF-8 real no MySQL é
(introduzido em MySQL 5.5.3, lançado em 2010). Se sua coluna usa utf8mb4 e alguém inserir um emoji, o MySQL pode truncar silenciosamente os dados ou lançar: utf8 Também atualize a configuração de conexão do banco de dados do seu aplicativo. No MySQL PDO:
Incorrect string value: '\xF0\x9F\x98\x80' for column 'bio' at row 1
A solução:
-- 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;
A armadilha do VARCHAR(255)
$pdo = new PDO($dsn, $user, $pass, [
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"
]);
no MySQL significa 255
VARCHAR(255) caracteres , não 255 bytes — mas o limite de armazenamento para uma única linha é calculado em bytes. Com, cada caractere pode ocupar até 4 bytes, então uma utf8mb4coluna reserva até 1.020 bytes. Isso importa quando você está usando o limite padrão de índice do InnoDB (767 bytes) para indexar colunas varchar: VARCHAR(255) O problema dos pares de surrogate no 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
O JavaScript usa UTF-16 internamente, não UTF-8. E o UTF-16 tem sua própria codificação de múltiplos unidades para pontos de código acima de U+FFFF:
pares de surrogate — dois blocos de 16 bits que juntos representam um caractere. Isso significa que
no JavaScript conta unidades de código UTF-16, não caracteres: String.length Para operações de string que precisam ser sensíveis a caracteres, use o operador de espalhamento 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)
O exemplo do emoji familiar vale a pena pausar. 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)
é composto por quatro emojis unidos por Zero Width Joiners (U+200D). Uma abordagem ingênua 👨👩👧👦 dá 11. Os clusters de grafemas reais: 1. Isso importa se você estiver implementando limites de caracteres — um limite baseado em .length vai se comportar de forma inesperada quando os usuários digitarem sequências de emojis. String.length Como verificar a codificação na prática
no PHP conta bytes, não caracteres. Isso atrapalha constantemente os desenvolvedores PHP ao lidar com strings multibyte — uma string com 10 emojis reportará um comprimento de 40. Use
Pitão
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() quando você precisa de contagem de caracteres. mb_strlen() Checagem 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
Se você quiser ver a contagem de bytes versus de caracteres para qualquer texto sem escrever código, o
trata isso instantaneamente — cole qualquer texto e ele mostra a contagem de caracteres, de palavras e de bytes ao lado. Calculadora de Comprimento de String A lista de verificação do erro de codificação
Conjunto de caracteres do MySQL:
- É ? Verifique com
utf8mb4, e nãoutf8Conexão do MySQL:SHOW CREATE TABLE. - Seu aplicativo está enviando ? Verifique sua configuração DSN ou de conexão.
SET NAMES utf8mb4PHP strlen vs mb_strlen: - Você está usando funções que contam bytes onde precisa contar caracteres? JavaScript .length:
- Você está contando unidades de código onde precisa de clusters de grafemas? Cabeçalhos HTTP:
- Seu resposta está enviando Arquivo de codificação:
Content-Type: text/html; charset=utf-8? - Seus arquivos de origem e dumps SQL estão salvos como UTF-8 sem BOM? UTF-8 e Unicode: Por que aquele emoji quebrou seu banco de dados 2
Instale nossas extensões
Adicione ferramentas de IO ao seu navegador favorito para acesso instantâneo e pesquisa mais rápida
恵 O placar chegou!
Placar é uma forma divertida de acompanhar seus jogos, todos os dados são armazenados em seu navegador. Mais recursos serão lançados em breve!
Ferramentas essenciais
Ver tudo Novas chegadas
Ver tudoAtualizar: Nosso ferramenta mais recente foi adicionado em 20 de junho de 2026
