Páginas

5 de mai de 2014

Corrigindo erros de caracteres (charset) no MySQL

Um dos problemas mais comuns, e mais silenciosos, no desenvolvimento web é a utilização correta do mapa de caracteres ou charset. O charset é um mapa que relaciona um determinado caractere com seu equivalente, seja em bits, pulsos elétricos ou qualquer outra forma que facilite a correta transmissão e compreensão do símbolo em questão. Atualmente, acredito que os mais utilizados sejam o Latin1 (ISO-8859-1) e o Unicode UTF-8 . Sendo assim, devidamente apresentados, vou citar o problema que tive justamente com esses 2 charsets e como resolvi esse problema definitivamente.

A Problemática do Silêncio 


Em muitos casos, a utilização errada de um mapa de caracteres é logo descoberta, pois o sistema começa a não exibir o caractere que deveria. O problema é quando sua aplicação está sendo impactada pelo problema e a própria aplicação está te prevenindo de ver o problema. Para ambos os casos, acredito que essa solução pode sanar quaisquer problema de charset trocado ou mal utilizado.

O problema que me deparei acontecia no seguinte cenário:
O banco de dados foi criado com charset default Latin1 e suas tabelas também. Com passar do tempo, novos desenvolvedores começaram a criar novas tabelas, agora em charset utf-8. Em pouco tempo, nos deparamos com um grande “mix” de Latin1 e utf-8 em nosso banco de dados. Nesse momento, como tínhamos mais tabelas utf-8, parecia lógico migrar todo o banco para utf-8 e padronizar tudo.

Descobrimos o Real Problema!


Silenciosamente, as tabelas utf-8 foram sendo populadas com dados que estavam utilizando Latin1, tudo devido a conexão padrão do MySQL configurar o charset padrão de acordo com o chartset padrão do banco e não de acordo com a tabela. Nesse ponto, temos tabelas Latin1 e utf-8, ambas com dados salvos utilizando Latin1. Esse problema não nos impactava ou a nenhum de nossos sistemas, devido ao script de conexão ser 1 único para toda aplicação.

Um Problema que não causa Problema? 


Sim, esse problema não afetou, prejudicou o chateou nenhum de nossos usuários, ninguém viu uma “?” no lugar de “ç”. Muitos poderiam pensar em manter as coisas como estão, a aplicação gravando e lendo corretamente, mesmo que dentro das tabelas erradas. Porém, sabemos que tudo tende a evoluir, sabemos que um dia iremos necessitar de funcionalidades específicas do Unicode, como o “Accent-Insensitive” do MySQL para buscas normais e com FULLTEXT INDEX, conexões de outras fontes, como smartphones, web services… Enfim, as possibilidades para problemas futuros são reais.

Solução 


A solução é demasiadamente simples quanto tem-se um banco de dados pequeno (<2Gb), porém quanto o banco está relativamente grande, a dificuldade cresce, por isso, caso você tenha detectado esse mesmo problema ou semelhante, aconselho agir e solucionar (utilizando minha solução se desejar =] ) o mais rápido possível.

Eu estou usando MySQL 5.5 e, para o meu objetivo, vou converter um banco com Latin1 e utf-8 misturados para um banco somente utf-8.

Mão na Massa


1. Fazer backup lógico completo do banco forçando o backup a ser realizado com o mesmo charset o qual a aplicação usou para salvar os dados nas tabelas. No meu caso, Latin1. Simultaneamente, estou usando o “|” (pipe) e o comando replace para substituir qualquer ocorrência latin1 por utf8. Atenção, caso exista ocorrência da palavra “latin1” nos dados, tais dados também serão alterados. Caso seu seu arquivo gerado seja muito pequeno, abra-o e edite manualmente os chatsets. Uma alternativa é o comando sed onde é possível especificar uma expressão regular mais “fina” para troca. No final, faço uma simples compactação com gzip por questões de espaço.

mysqldump -Cv --compact --add-drop-table --dump-date --single-transaction --quick --skip-comments --default-character-set=latin1 -u USUARIO -pSENHA -h HOST DATABASE | replace latin1 utf8 | gzip > db_dump.sql.gz

1.1 Caso prefira usar o sed, aqui vai um exemplo que faz a mesma substituição feita acima, porém o arquivo não pode estar compactado.

sed -i 's/latin1/utf8/g' db_dump.sql
1.2 Em alguns casos, se o banco recebeu dados provenientes de várias conexões com charsets diferentes, ainda há esperança! O comando iconv pode ser a luz no fim do túnel. Esse comando converte strings de um mapa de caracteres para outro de maneira simples. Convertendo o arquivo do exemplo acima, ficaria:

iconv -f LATIN1 -t UTF8 -o new_file.sql db_dump.sql

2. Segundo passo, repor o backup. Aconselho criar um novo banco de dados com ambos, “SET NAMES” e “COLLATE” padrão configurados para o próprio banco e também para cada uma das tabelas. No meu caso, como quero que todo o meu banco fique padronizado como utf-8, tenho que importar forçando o novo charset. Tudo pronto, basta executar o comando abaixo (caso você tenha feito backup da maneira mostrada no item 1):

gunzip < db_dump.sql.gz | mysql -u USUARIO -pSENHA --default-character-set=utf8 DATABASE

3. Nesse momento vamos aos testes. Para tirar prova que seus dados agora estão com charset correto, basta conectar utilizando o charset antigo e realizar uma consulta SQL que retorne algum texto com caracteres especiais. Nesse primeiro teste, os caracteres devem vir errados. Conectando novamente, dessa vez com o novo charset (No meu caso utf-8), a mesma consulta deverá retornar os caracteres especiais corretamente. Confirmado que o charset foi alterado com sucesso, agora é garantir que todos os seus scripts de conexão utilizem o mesmo charset, e que nenhum outro programador crie novas tabelas utilizando outro charset ou collate.

Caso algo tenha saído diferente do demonstrado aqui, segue o ambiente que utilizei para executar cada um desses passos. No servidor, estou utilizando CentOS 6.x com MySQL 5.5 via unix socket. No cliente, como máquina de trabalho, utilizei um ubuntu-gnome 13.10 com MySQL 5.5 do repositório oficial do ubuntu via unix socket.

Para visualizações, depuração de erros e criação de tabelas e bancos de dados, utilizo o phpmyadmin e MySQL Workbench.

Dúvidas, reclamações, correções, incrementos, tudo é muito bem vindo!

Leia no Google Drive!

Nenhum comentário:

Postar um comentário