Anúncios incomodam? Ir Sem anúncios Hoje

UTF-8 e Unicode Por que aquele emoji quebrou seu banco de dados

Atualizado em

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 inseriu um emoji e o MySQL lançou valor incorreto da string. Aqui está o porquê — pontos de código versus bytes, o engano do MySQL utf8 vs utf8mb4, pares de surrogate no JavaScript e como resolver isso realmente.
ANUNCIADO Remover?

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:

CaractereDescriçãoPonto de código UnicodeBytes UTF-8 (hexadecimal)Contagem de Bytes
ALetra latina maiúscula AU+0041411
éLetra e com acento agudoU+00E9C3 A92
Símbolo do euroU+20ACE2 82 AC3
Caractere chinês "meio"U+4E2DE4 B8 AD3
😀Emoji de sorrisoU+1F600F0 9F 98 804
🔥Emoji de fogoU+1F525F0 9F 94 A54
𝕳H fraktur matemáticaU+1D573F0 9D 95 B34

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ão utf8Conexã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
Quer eliminar anúncios? Fique sem anúncios hoje mesmo

Instale nossas extensões

Adicione ferramentas de IO ao seu navegador favorito para acesso instantâneo e pesquisa mais rápida

Ao Extensão do Chrome Ao Extensão de Borda Ao Extensão Firefox Ao Extensão Opera

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!

ANUNCIADO Remover?
ANUNCIADO Remover?
ANUNCIADO Remover?

Notícias com destaques técnicos

Envolver-se

Ajude-nos a continuar fornecendo ferramentas gratuitas valiosas

Compre-me um café
ANUNCIADO Remover?