Uma das decisões que temos que tomar ao criar um database é: qual character set (charset) usar? A resposta a essa pergunta é crucial, já que vários bancos de dados (PostgreSQL, por exemplo) não permitem que uma tabela tenha um charset diferente do restante do banco de dados. Nos próximos parágrafos irei explicar os conceitos relacionados à codificação de caracteres e como isso afeta a sua decisão.
Antes de mais nada, um pouco de terminologia: os character sets (ex: Unicode) determinam o conjunto de caracteres disponíveis; os encodings (ex: UTF-8, ISO-8859-1) determinam como os caracteres serão convertidos em bytes; e os collates (ex: latin1_general_ci) definem como os caracteres são ordenados e comparados (ex: letras maiúsculas próximas ou separadas das equivalentes minúsculas, caracteres acentuados próximos ou separados dos equivalentes sem acento, etc). Esses conceitos causam confusão em muitas pessoas, por isso é muito comum ver ferramentas dizendo “character set” (também chamado “charset”) querendo dizer “encoding” e vice-versa, ou seja, não se apegue muito à definição desses dois termos. Para uma explicação mais detalhada sobre codificação de caracteres, sugiro que leia este artigo.
Beleza, Vitor, mas qual encoding eu devo escolher?
A escolha do encoding vai depender de quais caracteres sua aplicação vai aceitar. Se ela aceitar caracteres não ocidentais (ex: 日本語, Ελληνικά, עִברִית), emojis (ex: 🤔) ou algum outro caractere especial no banco de dados (ex: “ᴇ”, que é diferente de “E”), use UTF-8. Alguns ambientes em que isso acontece muito são chats, informações de Bio (ex: Instagram), aplicações internacionais e em tabelas que armazenem XML ou JSON, que são geralmente codificadas em UTF-8.
O encoding UTF-16 aceita os mesmos caracteres do UTF-8, só que permite que os caracteres não ocidentais geralmente ocupem menos espaço em memória do que seus equivalentes em UTF-8, porém com a desvantagem de os caracteres ASCII ocuparem 2 bytes cada, sendo que o UTF-8 ocupa apenas 1 para eles.
Para quem usa MySQL, uma pegadinha: o charset utf8 não implementa todos os caracteres Unicode disponíveis (os emojis, por exemplo, não são reconhecidos), sendo resolvido com a criação do encoding utf8mb4. Na prática, o utf8 não deve ser usado, devendo ser utilizado o utf8mb4.
Se você não precisa de nenhum desses caracteres, uma opção é usar os encodings da Europa Ocidental (ISO-8859-1, também conhecido como Latin1, ISO-8859-15 e WIN1252, também conhecido como CP1252). A vantagem desses encodings é que eles usam apenas 1 byte por caractere (UTF-8 pode usar de 1 a 4 bytes por caractere), economizando espaço em memória e disco. Esses encodings contêm praticamente todos os caracteres ocidentais, incluindo acentos, cedilha e outros caracteres usados em idiomas ocidentais (ex: ß em alemão ou ñ em espanhol). A diferença entre eles é que o ISO-8859-1 não possui o símbolo do Euro (€), além de alguns caracteres acentuados (ex: Š), apesar de ter todos os acentos e caracteres usados em português. O encoding WIN1252, por não ser um padrão ANSI (foi criado pela Microsoft), possui alguns caracteres acentuados com código diferente dos padrões ISO-8859-1 e ISO-8859-15. As diferenças entre esses encodings podem ser vistas neste link.
Mais uma pegadinha do MySQL: o charset latin1 representa o encoding WIN1252, e não o ISO-8859-1, como é o padrão, ou seja, sua aplicação não terá problemas com o caractere € e outros disponíveis em WIN1252.
O que acontece se eu escolher um encoding errado?
Se você escolher um encoding que não aceite todos os caracteres vindos das aplicações, o banco de dados pode se comportar de forma imprevisível, podendo truncar caracteres ou substituir por outros caracteres. A string “acentuação” poderia, por exemplo, ser gravada no banco de dados como “acentuao”, “acentua��o”, “acentuação” ou “acentua¿¿o”, não podendo ser recuperado posteriormente. Por conta disso, é muito importante escolher bem o encoding do seu banco de dados.
Já tenho um banco com determinado encoding e preciso mudar, e agora?
Nem sempre isso é motivo para pânico. Vários bancos de dados possuem formas de se contornar a situação nos casos de ser necessário criar uma tabela ou coluna usando um encoding diferente do padrão.
Em MySQL, por exemplo, é possível definir um charset diferente para a tabela ou mesmo para cada coluna, como no exemplo abaixo:
CREATE TABLE tabela1 ( id INT NOT NULL, descricao VARCHAR(100) NOT NULL CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ) CHARACTER SET latin1 COLLATE latin1_general_ci;
No exemplo acima, a tabela terá o encoding latin1 por padrão, mas a coluna descricao
terá o encoding utf8mb4.
Outros bancos de dados, como Oracle e SQL Server, possuem tipos de dados com encodings mais amplos, geralmente UTF-16. No caso do SQL Server, esse tipo é o NVARCHAR
; no Oracle é o NVARCHAR2
. Basta criar ou converter a coluna para permitir os caracteres adicionais.
No caso do PostgreSQL, não existe uma forma de se definir um encoding diferente para uma tabela ou campo, a única solução é efetuar um dump da base, apagar a base antiga, recriá-la em UTF-8 e restaurar o dump, como pode ser visto neste artigo. Para evitar a necessidade de uma migração futura, vale a pena criar os bancos de dados Postgres desde o início usando UTF-8.
E os collates, qual eu escolho?
A escolha de um collate vai depender de 2 fatores:
- das regras de ordenação
- da diferenciação de maiúsculas e minúsculas
A regra de ordenação depende do idioma de origem. Até 2010, por exemplo, o “ch” em espanhol era considerado como uma letra só e ficava entre o C e o D, ou seja, charada ficava depois de cocina, ao contrário do português, em que ele ficaria antes. No caso da língua portuguesa, não há uma regra específica, ele entra na regra geral de boa parte dos países da Europa ocidental.
Outro ponto é definir nas regras de ordenação é se os caracteres acentuados deverão ficar próximos ou depois dos caracteres sem acento (accent sensitive ou accent insensitive). Por exemplo, se a palavra “ação” tiver que ficar entre “acao” e “adorar”, ele é accent insensitive; se “ação” tiver que ficar depois dos dois, ele é accent sensitive.
A diferenciação de maiúsculas e minúsculas (case sensitive ou case insensitive) segue a mesma regra: se “acao” tiver que ficar antes de “ADORAR”, ele é case insensitive; se tiver que ficar depois (isto é, separando as maiúsculas das minúsculas), ele é case sensitive.
No caso do MySQL, por exemplo, um collate em UTF-8 com a regra geral da Europa ocidental e sem distinção entre maiúsculas e minúsculas seria o utf8mb4_general_ci
.
Alguns bancos de dados, como por exemplo o Oracle, permitem uma ordenação binária, ou seja, usando os códigos ASCII/Unicode de cada caractere, o que aumenta a performance na ordenação mas obriga a usar ordenação case sensitive e pode causar problemas com a ordenação de alguns caracteres acentuados, dependendo do encoding utilizado.
Tá, resume aí que ficou complicado pra minha cabeça:
Se você quiser armazenar caracteres não ocidentais, emojis ou algum outro caractere especial no banco de dados, use UTF-8 (utf8mb4 no MySQL); se não precisar desses caracteres mas precisar do símbolo do Euro (€), use ISO-8859-15 ou WIN1252 (também conhecido como CP1252). Se nem o caracter € for necessário, o ISO-8859-1 (também conhecido como Latin1) atende. O MySQL foge do padrão e considera o WIN1252 como Latin1.
Mesmo que sua aplicação consiga trabalhar em Latin1, vale a pena manter o banco em UTF-8 se a performance dele for melhor que a do Latin1, como acontece com o MySQL 8, por exemplo.
Já em relação ao collate, isso vai depender do encoding definido, das regras de ordenação (ex: binária, portuguesa, espanhola, etc) e se haverá distinção entre maiúsculas e minúsculas (ex: latin1_general_ci usa encoding Latin1, ordenação binária e case insensitive, ou seja, sem distinção entre maiúsculas e minúsculas).