MySQL: Latin1 i UTF-8 w jednym

Podczas tworzenia skryptu, który opiera się na bazie danych MySQL, często nie zwracamy uwagi na najważniejszą rzecz: kodowanie znaków. Niby opieramy się na UTF-8, bo dane przecież wysyłamy w takim formacie, tabele i baza również są poprawnie stworzone ale… zapominamy o kodowaniu połączenia, co skutkuje miksem Latin1 i UTF-8.

Rozpoznawanie problemu

Taki problem najprościej rozpoznać poprzez PHPMyAdmina: jeżeli w widoku danych z tabeli widzimy wystąpienia znaku „Å” lub podobnego, oznacza to, że dane były w UTF-8, ale zostały przesłane jako Latin1, co odczulibyśmy podczas przenoszenia bazy danych na inny serwer.

Zabezpieczanie się

Rozwiązanie na poprawne kodowanie przy dodawaniu lub edycji danych jest proste. Wystarczy tuż po połączeniu wykonać proste zapytanie:

SET NAMES utf8

Ustawia one kodowanie połączenia na UTF-8, więc dane w tym miejscu nie zostaną źle zakodowane.

Naprawianie bazy

Niestety, zazwyczaj o takim problemie dowiadujemy się dopiero, kiedy posiadamy już dużo danych i ich uzupełnienie od nowa, by trochę potrwało. Tutaj z pomocą może nam przyjść funkcja mojego autorstwa:

/**
*
* @copyright (c) 2009 Łukasz Rutkowski
* @license http://creativecommons.org/licenses/by/3.0/pl/ Creative Commons Uznanie autorstwa 3.0
*
*/
function repair($tbl, $id) {
     $sql = new MySQLi('host_bazy', 'użytkownik', 'hasło', 'baza_danych');
 
     //Go to latin1
     $sql->query('SET NAMES latin1');
 
     //Get count
     $cnt = $sql->query('SELECT COUNT(' . $id . ') AS count FROM ' . $tbl);
     $cnt = $cnt->fetch_assoc();
     $cnt = $cnt['count'];
 
     //Get records
     $all = $sql->query('SELECT * FROM ' . $tbl);
 
     //Columns
     $columns = $sql->query('SHOW COLUMNS FROM ' . $tbl);
     //List columns
     while($clmn = $columns->fetch_assoc()) { $clmns[] = $clmn['Field']; }
 
     //Back to utf8
     $sql->query('SET NAMES utf8');
 
     //Generate SET clause
     while($row = $all->fetch_assoc()) {
          $i = 0;
          $set = '';
          foreach($clmns as $clmn) {
               if($clmn != $id) {
                    if($i != 0) {
                         $set .= ', ';
                    }
                    $set .= $clmn . '="' . addslashes($row[$clmn]) . '"';
 
                    $i++;
               }
        }
        $sql->query('UPDATE ' . $tbl . ' SET ' . $set . ' WHERE ' . $id . '=' . $row[$id]);
    }
    unset($sql);
}

Limit czasowy

Od razu chciałbym dodać, że przy większych bazach danych zmiana może dość długo potrwać, więc polecam na początku ustawić limit czasu na nieograniczony, żeby jego praca nie została przerwana:

set_time_limit(0);

Wykorzystanie

Przed zastosowaniem tego kodu, musimy zmienić kodowanie bazy i tabel na utf8_general_ci. Potrzebne narzędzie znajdziemy w zakładce „Operacje”, w PHPMyAdminie.
Wykorzystanie samej funkcji jest bardzo proste: jako pierwszy parametr podajemy nazwę tabeli do naprawy, a jako kolejny kolumnę z unikalnymi danymi dla każdego rekordu(praktycznie zawsze takowym jest ID, więc go polecam używać). Przedstawia się to następująco:

repair('tabela', 'id_rekordu');

Sam przyznam, że ten skrypt pomógł mi w naprawie dwu milionowej bazy danych, której ręczna naprawa by potrwała wieki, i sprawdził się znakomicie.

Na koniec chciałbym życzyć, żebyś Drogi Czytelniku nie posiadał takiego błędu w swojej bazie, bo zazwyczaj się o nim dowiadujemy, kiedy przenosimy bazę, a wtedy przez nerwy, można dosyć mocno się zirytować. Również chciałbym dodać, że powyższy skrypt zadziała dla innych mieszanek kodowań, lecz trzeba odpowiednio pozmieniać ich nazwy w kodzie(opisałem ten przypadek, ponieważ występuje najczęściej).

Za ten artykuł podziękowano 1 raz(y). Chcesz i Ty ?

3 Comments

  • Martin
    19 maja 2010 - 14:17 | Permalink

    no mistrzu. Zanim umieścisz kod zobacz czego używasz do operacji na bazie. Z czego zbudowana jest ta instancja ? $sql = new MySQLi();
    Wycięcie kodu bez podania bibliotek nadaje się tylko do kosza.

  • 22 maja 2010 - 19:00 | Permalink

    Mistrzu, MySQLi wchodzi w skład podstawowej konfiguracji PHP.

  • Tomasz Łukawski
    12 lipca 2011 - 12:25 | Permalink

    Tutaj znajdziecie proste rozwiązanie tego problemu.

    http://www.bothernomore.com/2008/12/16/character-encoding-hell/

    Pozdrawiam

  • Dodaj komentarz

    Twój adres e-mail nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

    *

    Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">