Logo

Qual charset devo usar?

ISO8859_1, WIN1252 ou UTF8? - Guia completo para SGBDs

Nota: Este guia aborda conceitos universais de charset que se aplicam a MS SQL Server, PostgreSQL e Firebird. Embora os exemplos práticos sejam baseados no Firebird, os princípios fundamentais são os mesmos para todos os SGBDs mencionados, com poucas ou nenhuma diferença na forma como os charsets funcionam.

Por que essa pergunta é importante?

Se você usa o Windows, você deve estar se perguntando porque essa pergunta é importante. Por gentileza abra o cmd e execute comigo o comando chcp, ele retorna o código de página de caracteres que está em uso no momento. Ele retornará o código de página 850.

Vamos no Google e pesquisar esta página com os termos "Code page 850" e seremos encaminhados para Wikipedia:

https://en.wikipedia.org/wiki/Code_page_850

E então descobrimos que estamos usando o ISO8859_1 também conhecido como "Latin1". Então, toda vez que nos referimos ao ISO8859_1, vamos chamar de Latin1, ok?

Histórico: ISO8859_1(Latin1), WIN1252 e Unicode

Se você chegou a conhecer o MS-DOS deve se lembrar do comando chcp que usávamos no autoexec.bat para tornar possível ver as acentuações na tela. Então desde os primórdios do MS-DOS usamos Latin1 (ou ISO8859_1) para tornar possível ver nossas acentuações no terminal. Latin1 (ou ISO8859_1) leva o prefixo "iso" porque foi aprovado pelo comitê internacional de mesmo nome: International Organization for Standardization (Organização Internacional de Normalização) o equivalente a nossa ABNT só que internacional.

Mas uma coisa é o terminal, algo considerado legado por alguns para rodar aplicações. Quando o Windows surgiu com uma interface gráfica, a Microsoft resolveu criar um novo código de página chamado "Win1252" por isso tem o "Win" no nome, foi feito exclusivamente para o ambiente Windows e teve a aprovação da ANSI, a ABNT americana.

Por que criar seu próprio padrão ao invés de continuar usando o ISO8859_1 (Latin1)? Essa é uma pergunta difícil de responder, o ISO8859_1 foi criado inicialmente pela Apple e depois aprovado pela ISO e naquela época a Microsoft gostava de ditar os padrões da indústria e talvez por isso tenham criado o Win1252. Ele deveria ser desnecessário já que teve como base o próprio ISO8859_1 e ambos com poucas exceções são cambiáveis entre si. Pessoalmente, só conheço duas diferenças entre ambos, o Win1252 possui um travessão longo que é diferente do traço normal e um jogo de aspas com serifa (curva).

O problema da fragmentação no Windows

Ter um padrão de geração de caracteres diferentes só criou problemas para a Microsoft, seu terminal (cmd) é Latin1, a interface gráfica é Win1252, o sistema de arquivos é UNICODE. A pouco tempo atrás, se você usasse um bloco de notas para editar um arquivo php ou html tinha boas chances de ter o seu trabalho perdido. Sistemas como o Linux são tudo UNICODE e normalmente não é preciso lidar com conversões entre código de páginas diferentes.

Um outro problema como o ambiente Windows é que ele não é mais tão universal como antes, hoje as aplicações estão em nuvem e a maioria dos serviços de banco de dados são hospedados em computadores que não rodam Windows. Se você hospedar dados num servidor Windows armazenando dados no formato WIN1252 provavelmente teria de pensar o que aconteceria se resolvesse hospedar um banco de dados num sistema operacional Linux que segue apenas padrões internacionais ISO onde o WIN1252 não existe. Você talvez tenha de lidar com problemas de transliteração de caracteres.

Dica: Para demonstrar essa transliteração, você pode usar o Notepad++ para visualizar como os mesmos caracteres são interpretados de forma diferente dependendo da codificação escolhida.

Recomendação: ISO8859_1 para portabilidade

Então, o desejável se queremos uma aplicação ou banco de dados que seja portável entre sistemas operacionais é desistir do WIN1252 e em seu lugar optar pelo ISO8859_1 (latin1). Agora, daqui em diante vou falar apenas do Latin1 (ISO8859_1) e o padrão Unicode. Mas ao me referir ao Latin1, lembre-se que também é aplicável ao WIN1252.

Unicode: a solução moderna

O UNICODE veio para resolver alguns problemas. Há uma ótima palestra Firebird Conference 2011 Luxembourg onde o discursante falou muito sobre charsets, na sua palestra intitulada "Character Sets and Unicode in Firebird" e citou as vantagens:

Referência: https://www.firebirdsql.org/file/community/ppts/fbcon11/FbCon2011-Charsets-Heymann.pdf

Nota: Embora a referência seja de uma palestra sobre Firebird, os conceitos de Unicode são universais e se aplicam igualmente ao MS SQL Server, PostgreSQL e outros SGBDs modernos.

Testando no terminal do Windows

Vamos comprovar que estamos usando o latin1, vamos agora mudar para win1252:

chcp 1252
echo linha #1: teste com com página latin1(iso8859_1)>teste.txt
echo linha #2: atenção, minha acentuação é inoqüa>>teste.txt
hexeditor teste.txt

Parece tudo certo, né? Mas vamos mudar agora o código da página para utf:

chcp 65001
echo linha #3: teste com com página unicode>>teste.txt
echo linha #4: atenção, minha acentuação é inoqüa>>teste.txt
hexeditor teste.txt

Agora temos duas linhas iniciais exibidas corretamente, mas as duas últimas erroneamente, por quê? Porque ao iniciar o arquivo pela primeira vez, não há indicativos de codepage dentro do arquivo e então o Windows assume que é Ansi.

Vamos criar as mesmas 4 linhas no Notepad++, note que o mesmo já é criado em UTF8, mas se olharmos no bloco de notas, notaremos que nenhuma das linhas parece correta porque nem Latin1 e nem UTF são compatíveis com o WIN1252 (Ansi).

Se usarmos o Notepad++ e convertermos o arquivo para UTF-8 (Formatar → Codificação UTF-8 ou Europa Ocidental) as duas primeiras ou as duas últimas linhas estão corretas, mas nunca as 4 linhas.

Por que não usar Unicode sempre?

Usamos o tempo todo, a Microsoft já se rendeu ao UNICODE no desenvolvimento, embora o Windows ainda tenha uma salada de código de páginas, por exemplo, o sistema de arquivos é unicode, o ambiente gráfico ainda é win1252 e o cmd (terminal) Latin1, por isso, há muitas inconsistências quando programamos em Windows visando o unicode. Se um programador gerar um script SQL e salvá-lo no disco usando WIN1252, mas os caracteres gerados nele estiverem em UNICODE, haverá problemas.

Exemplo prático: Se você tentar executar um script SQL criado em Win1252 em um banco de dados UTF-8 usando qualquer ferramenta de administração (como IBExpert para Firebird, pgAdmin para PostgreSQL, ou SQL Server Management Studio), o script aparentemente inofensivo pode não funcionar corretamente. Este problema é comum em todos os SGBDs.

Isso é um inferno que apenas existe no Windows, e que quando você tentar portar para Linux tem de resolver problemas que antes não existiam. Por isso, para nossa comodidade, os bancos de dados incluam WIN1252 entre os seus charsets, embora o mesmo não seja o ideal.

Charset e performance em bancos de dados

Ainda sobre banco de dados, nem sempre o UNICODE é desejável, pois um único caractere armazenado nele pode ocupar de 1 a 4 bytes, por isso, não é largamente utilizado se uma aplicação já "fala" ISO8859_1 ou WIN1252, pois nações que usam ideogramas ou outros símbolos tipográficos também sabem "falar" ISO8859_1 ou WIN1252, embora o inverso não seja verdadeiro, se você fosse de uma empresa japonesa teria de escrever uma aplicação que usasse os ideogramas japoneses e muito provavelmente também o ocidental e só o UNICODE resolveria este problema.

Então se você mora num país ocidental que usa os caracteres A-Z pode-se considerar sortudo porque temos opções de escolha, e geralmente optamos pela mais econômica que também é a mais performática, desconsiderar UNICODE e escolher entre WIN1252 ou ISO8859_1.

Um banco de dados inteiramente em UNICODE é menos performático quando comparado ao Latin1, não apenas por ser mais guloso em termos de espaço, lembre-se um varchar(64) a depender do UNICODE usado, ao invés de 64 caracteres pode conter apenas 16, mas também porque o algoritmo de "case insensitive" e "accent insensitive" podem vir a ser mais complexos. O MySQL por exemplo, é reconhecido por adotar uma gambiarra de UNICODE que às vezes criam problemas.

Nota sobre SGBDs: Os conceitos de performance relacionados a charset são universais. No MS SQL Server, você pode usar VARCHAR (1 byte por caractere em Latin1) ou NVARCHAR (2 bytes por caractere em Unicode). No PostgreSQL, o comportamento é similar, com VARCHAR usando o charset do banco e TEXT também respeitando a codificação. No Firebird, os tipos VARCHAR e CHAR podem usar diferentes charsets. Em todos os casos, Unicode tende a ocupar mais espaço e pode impactar a performance de índices.

Exemplo prático: Criando um banco de teste

Nota: Os exemplos a seguir são baseados no Firebird, mas os conceitos e princípios são os mesmos para MS SQL Server e PostgreSQL. A sintaxe pode variar ligeiramente, mas a lógica de funcionamento dos charsets é universal.

IBExpert – Criar banco de dados:

C:\TEMP\TEST_ISO8859_1.FDB
CHARSET: ISO8859_1
COLLATE: PT_BR
ALIAS: TEST_ISO8859_1.FDB
PORTA: 3040

Criar a tabela:

CREATE TABLE T1(
    FRUTA_ISO8859 VARCHAR(10) CHARACTER SET ISO8859_1 COLLATE PT_BR,
    FRUTA_WIN1252 VARCHAR(10) CHARACTER SET WIN1252 COLLATE WIN_PTBR,
    FRUTA_UNICODE VARCHAR(10) CHARACTER SET UTF8 COLLATE UNICODE_CI_AI
);

Equivalente em outros SGBDs:

Popular valores acentuados com o script:

INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Maracujá', 'Maracujá', 'Maracujá');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Açaí', 'Açaí', 'Açaí');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Gravatá', 'Gravatá', 'Gravatá');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Avelã', 'Avelã', 'Avelã');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Melão', 'Melão', 'Melão');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Maçã', 'Maçã', 'Maçã');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Mamão', 'Mamão', 'Mamão');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Jiló', 'Jiló', 'Jiló');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Babaçu', 'Babaçu', 'Babaçu');

COMMIT WORK;

Verificar tamanho em bytes:

Será que todos os caracteres têm o mesmo tamanho em bytes? Execute:

SELECT
  a.fruta_iso8859||'('||OCTET_LENGTH(a.fruta_iso8859)||')' AS iso8859,
  a.fruta_win1252||'('||OCTET_LENGTH(a.fruta_win1252)||')' AS win1252,
  a.fruta_unicode||'('||OCTET_LENGTH(a.fruta_unicode)||')' AS unicode
FROM T1 a;

Equivalente em outros SGBDs:

Entre as nossas frutas o limite de caracteres é 10, o maracujá foi a fruta que mais ocupou espaço em bytes com 9 bytes em unicode. O que vai acontecer se aumentar para 10 caracteres e acentuarmos ainda mais letras? Vamos conferir:

UPDATE T1 SET
  FRUTA_UNICODE='Maráçujãío' -- 10 caracteres, deveria caber, o limite é 10
WHERE FRUTA_UNICODE='Maracujá';

Conseguimos, mas note a divergência entre o tamanho de caracteres e o tamanho de byte ocupados:

SELECT
  a.fruta_iso8859||OCTET_LENGTH(a.fruta_iso8859),
  a.fruta_win1252||OCTET_LENGTH(a.fruta_win1252),
  a.fruta_unicode||OCTET_LENGTH(a.fruta_unicode)
FROM T1 a
WHERE FRUTA_UNICODE='Maráçujãío';

Maracujá tem 10 caracteres, mas ocupa agora 14 bytes. Então o tamanho de um campo não é o mesmo que dizer que o tamanho ocupado. O que acha que aconteceria se todas as 10 letras tivessem acento? Vamos tentar:

UPDATE T1 SET
FRUTA_UNICODE='áéíóúáéíóú' -- 10 caracteres
WHERE FRUTA_UNICODE='Maráçujãío';

Chegamos então à conclusão que em unicode – em especial UTF-8 – o armazenamento do seu banco será maior contendo acentuações do que seria em Latin1 ou Ansi.

Com o armazenamento tão barato, devo me preocupar?

Se seu objetivo for internacionalizar, não deve se preocupar com o armazenamento, mas deve se preocupar com os limites teóricos de seus metadados, como nome de objetos. Por exemplo, se eu criar uma tabela cuja chave é um varchar(x) qual é o tamanho máximo de uma chave dentro de um índice?

Informação sobre limites de índices: Cada SGBD tem seus próprios limites, mas o princípio é o mesmo: quando o limite é especificado em bytes, campos Unicode podem armazenar menos caracteres do que campos Latin1.

  • FirebirdSQL: A partir do Firebird 2.0, o comprimento máximo de uma string indexada é calculado pela fórmula: max_char_length = FLOOR((page_size / 4 - 9) / N) onde N é o número de bytes por caractere no charset.
  • MS SQL Server: O limite de índice é de 900 bytes para chaves não-clusterizadas e 1700 bytes para índices clusterizados. Com NVARCHAR (Unicode), cada caractere ocupa 2 bytes.
  • PostgreSQL: O limite de índice B-tree é de aproximadamente 2704 bytes (1/3 do tamanho da página padrão de 8KB). Com UTF-8, caracteres acentuados podem ocupar 2-4 bytes.

Então quando estiver usando unicode tome cuidado com os limites do seu SGBD que forem em bytes, pois o que antes era 1 byte = 1 caractere não é mais aplicável. Os limites que eram estabelecidos em caracteres, estes não mudam, se o limite para tamanho de nome para uma tabela é 63 caracteres, continuará sendo 63 caracteres não importando se é unicode ou não.

Aqui temos uma gritante diferença para ISO8859_1 ou WIN1252, pois nestes cada caractere é 1 byte, enquanto em unicode os SGBDs assumem que cada caractere pode consumir mais bytes. Então se você criar um campo varchar(18000) e inserir 18.000 caracteres acentuados, ele irá deixar, mas o espaço ocupado será 18.000 × 2 ou 18.000 × 4 dependendo do acento e do SGBD.

Portanto, quando criar um campo que você sabe que no máximo terá X caracteres, leve em consideração:

Vamos ao COLLATE

Charset é o conjunto de caracteres disponíveis, majoritariamente ISO8859_1, WIN1252 e UNICODE. Cada qual com o seu conjunto limitado de caracteres. Se seu banco de dados precisa de caracteres latinos e ocidentais ISO8859_1 está bom para você, você não deveria usar o WIN1252 porque ele foi provido apenas para Windows e sua condição pode mudar no futuro.

Mas se precisa de um conjunto grande de caracteres que permita escrever na mesma sentença caracteres ocidentais e ideogramas japoneses então você tem que optar pelo UNICODE, geralmente UTF-8.

O collate é a forma como os caracteres serão tratados e/ou ordenados. Será que 'A' vem antes 'á'? Será que 'Pharmacia' e 'Farmacia' é a mesma coisa? Será que 'José' e 'Jose' também são iguais?

Quem cria os collates define isso, no Brasil não tem essa coisa de 'Ph' de pharmacia dos anos 30, mas em outros países podem haver agrupamento de caracteres (collate) que devam ser tratados conjuntamente e não de forma individual.

Exemplo prático de Collate:

CREATE TABLE T2 (
    NOME  VARCHAR(30) NOT NULL COLLATE PT_BR
);
INSERT INTO T2 (NOME) VALUES ('FARMACIA');
INSERT INTO T2 (NOME) VALUES ('FARMÁCIA');
INSERT INTO T2 (NOME) VALUES ('Jose');
INSERT INTO T2 (NOME) VALUES ('José');
INSERT INTO T2 (NOME) VALUES ('JOSÉ');

Note agora a ordenação de dados entre dois collates diferentes usando o mesmo charset:

SELECT * FROM T2 a ORDER BY a.nome COLLATE PT_PT;

Agora o outro:

SELECT * FROM T2 a ORDER BY a.nome COLLATE PT_BR;

Os collates estão atrelados ao charset porque sem eles, o banco não teria a regionalidade de ordenação, ou quais sinais diacríticos são iguais a suas versões sem esses sinais e assim por diante.

Collates em diferentes SGBDs:

  • FirebirdSQL: Suporta vários collations para UTF8 como UCS_BASIC, UNICODE, UNICODE_CI (case-insensitive), UNICODE_CI_AI (case-insensitive e accent-insensitive).
  • MS SQL Server: Oferece collates como Latin1_General_CI_AS (case-insensitive, accent-sensitive), SQL_Latin1_General_CP1252_CI_AI (case e accent insensitive), entre outros.
  • PostgreSQL: Usa collates do sistema operacional, como pt_BR.UTF-8, C (binário), ou collates customizados. Suporta COLLATE "C" para comparações binárias.

Embora a sintaxe varie, o conceito de collate é universal: ele define como os caracteres são comparados e ordenados.

Mesmo que um banco de dados tenha uma tabela usando 3 charsets diferentes, contendo os mesmos dados, o collate poderá fazer com que os dados se comportem usando a mesma regra linguística. Por exemplo, para o Brasil, case/accent insensitive significa que o collate não fará distinção entre maiúsculos e minúsculos e que a ordenação seguirá um mesmo padrão.

Testando ordenação por charset:

Vamos testar se a ordenação foi influenciada pelo charset executando esta query (exemplo específico do Firebird, mas o conceito se aplica a todos os SGBDs):

EXECUTE BLOCK
RETURNS(
  iso8859_1 VARCHAR(10),
  win1252 VARCHAR(10),
  unicode VARCHAR(10))
AS
BEGIN
  --iso8859_1
  iso8859_1='Sim';
  win1252='-';
  unicode='-';
  SUSPEND;
  FOR SELECT
    a.fruta_iso8859, a.fruta_win1252, a.fruta_unicode
    FROM T1 a
    ORDER BY a.fruta_iso8859
    INTO iso8859_1, win1252, unicode
  DO BEGIN
    SUSPEND;
  END
  -- win1252
  iso8859_1='-';
  win1252='Sim';
  unicode='-';
  SUSPEND;
  FOR SELECT
    a.fruta_iso8859, a.fruta_win1252, a.fruta_unicode
    FROM T1 a
    ORDER BY a.fruta_win1252
    INTO iso8859_1, win1252, unicode
  DO BEGIN
    SUSPEND;
  END
  -- unicode
  iso8859_1='-';
  win1252='-';
  unicode='Sim';
  SUSPEND;
  FOR SELECT
    a.fruta_iso8859, a.fruta_win1252, a.fruta_unicode
    FROM T1 a
    ORDER BY a.fruta_unicode
    INTO iso8859_1, win1252, unicode
  DO BEGIN
    SUSPEND;
  END
END

Notamos na saída do comando EXECUTE BLOCK que a ordenação pelo charset ISO8859_1 (latin1), WIN1252 ou unicode não teve diferença!

Nota: Embora este exemplo use a sintaxe específica do Firebird (EXECUTE BLOCK), o conceito de testar ordenação por diferentes charsets é aplicável a todos os SGBDs. No MS SQL Server, você usaria procedimentos armazenados ou CTEs. No PostgreSQL, você usaria funções ou CTEs similares.

Inserindo caracteres não acentuados:

Vamos complicar e inserir caracteres não acentuados, executando essa sequência de ExecSQL:

INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Maracuja', 'Maracuja', 'Maracuja');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Açai', 'Açai', 'Açai');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Gravata', 'Gravata', 'Gravata');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Avela', 'Avela', 'Avela');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Melao', 'Melao', 'Melao');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Maça', 'Maça', 'Maça');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Mamao', 'Mamao', 'Mamao');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Jilo', 'Jilo', 'Jilo');
INSERT INTO T1 (FRUTA_ISO8859, FRUTA_WIN1252, FRUTA_UNICODE)
            VALUES ('Babacu', 'Babacu', 'Babacu');

Executamos o EXECUTE BLOCK outra vez e notamos que não houve diferença, a ordem foi a mesma para todos os casos, apenas a versão não acentuada teve precedência.

Resumo e Recomendações

Charset Bytes por caractere Uso recomendado Vantagens Desvantagens
ISO8859_1 (Latin1) 1 byte Sistemas legados, aplicações brasileiras que não precisam de internacionalização Melhor performance, menor uso de espaço, compatível com padrões ISO Limitado a caracteres latinos ocidentais
WIN1252 1 byte Aplicações Windows que não serão migradas para Linux Melhor performance, menor uso de espaço, inclui símbolo Euro Específico do Windows, problemas em migração para Linux
UTF8 1-4 bytes Sistemas novos, aplicações internacionais, bancos que precisam suportar múltiplos idiomas Universal, suporta todos os caracteres, padrão moderno Maior uso de espaço, menor performance em alguns casos, complexidade nos índices

Recomendações finais:

← Voltar para o SGBD Hub