MYSTERRIA3.0

Конвертируем проект из cp1251 в utf-8

Думаю, нет смысла расписывать, зачем это делается. Тренд, удобство, вэбХдва=0, интернационализация - навскидку, почему это может понадобится.

Разобравшись с причиной, начнем подбирать подходящий нам метод. There is more than one way to do it! (c.) Я предложу на выбор два способа: по пути наименьшего сопротивления (ради тренда) и основательный. Отличаются они тем, что в первом случае перевод будет носить косметический характер и не будет затрагивать базу, второй же подойдет, если требуется, скажем, интернационализация проекта.

В простейшем случае мы не будем трогать базу вообще. Конвертнем скрипты и пропишем в месте коннекта с базой данных SET NAMES utf8 для выставления connection charset. Дело в том, что MySQL сам на лету конвертирует данные запроса из кодовой страницы коннекта (кодовая страница общения ваших скриптов с сервером БД) именуемой connection charset в кодовую страницу таблицы, куда данные будут сохренены и обратно, когда данные извлекаются. То есть мы можем по сути общаться с сервером БД, используя любой чарсет и сервер будет транслировать данные таблиц в него на лету. Ограничения этого метода в том, что если мы попросим сервер записать в latin1 таблицу японский еироглиф, то ничего путнего из этого не выйдет, этот символ в момент трансляции японского чарсета в latin1 будет безвозвратно потерян.

Случаи, когда в latin1 таблицах хранят utf я не рассматриваю, так как это по сути обман базы данных, ибо кодовая страница соединения выставляется не честно. А о том, что разработчик всех перехитрил он узнает при попытке восстановить базу из дампа.

Исходя из выше описанного для корректной работы с разными языками нам придется не просто выставить connection charset, но и честно конвертировать базу избавив сервер от трансляции кодовых страниц, а нас от проблем с потерянными символами.

Далее я опишу 2 этапа трансляции проекта: первый - трансляция базы, второй трансляция скриптов. Если вы идете по легкому пути, первую часть можете пропустить.

Перед тем, как вы начнете, закройте доступ к вашей базе данных из-вне.

Часть 1: трансляция базы.

Первое, что нам следует сделать, это дамп базы. Снимать дамп будем не целиком TableDefinitions (TDs)+data, а раздельно. Т.е. в один файл структуру, в другой данные. Это нужно для того, чтобы не позаменять в данных проекта чего лишнего. Если, например, какой-то пользователь писал что-то про cp1251, а может быть еще и TDs-ы цитировал, мы не должны их испортить.

Обратите внимание, что снимать TDs-ы надо с ключем --routines, чтобы не забыть хранимки и --triggers, чтобы триггеры оказались вместе со структурой. А вот дамп данных в свою очередь с ключем --skip-triggers, иначе получите CREATE-ы триггеров в дампе с данными. Впринципе, ничего страшного в нахождении триггеров в дампе с данными нет, но я предпочитаю отделять мух от котлет.
Кроме того, если вы работаете с InnoDB таблицами, вам понадобится добавить ключ --single-transaction при снятии дампа с данными.

mysqldump -uroot -p --routines --triggers --no-data yourdb > db.tds.sql
mysqldump -uroot -p --skip-triggers -n -t --default-character-set=utf8 yourdb > db.data.sql

Итак, у нас 2 дампа, один с TDs-ами, другой с данными. Берем любимый редактор с возможностью bulk-замены (sed, хотя, можете бить камнями, mcedit) и правим TDs-ы. Коллэйшены либо просто убить, либо заменить на utf-ные, затем все cp1251 меняем в utf8.

В простейшем случае вот список замен (именно в этом порядке) для db.tds.sql:
'collate cp1251_bulgarian_ci' => ''
'COLLATE=cp1251_bulgarian_ci' => ''
'cp1251' => 'utf8'

Дальше стоит обратить внимание на тип данных TEXT. Дело в том, что при конвертации из однобайтовой в мультибайтовую кодировку данные у нас распухнут и могут перестать умещаться в старых типах. Самый простой путь решения этой проблемы - заменить в TDs-ах тип данных TEXT на MEDIUMTEXT. Если же у вас исходные данные в  MEDIUMTEXT, проверьте, ну нужно ли увеличить его до LONGTEXT, но это вряд ли.

В деле трансляции данных из одного чарсета в другой распространен метод использования iconv-a и трансляции дампа им. Этот метод подходит, если у нас достаточно небольшой дамп (iconv использует оперативку) и если у нас идеальные данные. В реальном проекте не без помощи спамеров в базу попадает зачастую такой набор байтов, который MySQL не берется разобрать и сохраняет как получится. iconv на этом месте скажет, что с такими данными он работать не будет и ключ //TRANSLIT вам не поможет. Поэтому мы сразу при снятии дампа попросили MySQL по возможности транслировать наш дамп в utf8.
Можно было оставить трансляцию на момент импорта дампа обратно, т.е. мы импортировали бы cp1251 дамп в utf базу и MySQL по уже описанному мной алгоритму сохранил бы все в utf-е.

Ну а дальше самый отчаянный момент:

mysql> DROP DATABASE yourdb;
mysql> CREATE DATABASE yourdb DEFAULT CHARSET utf8;
mysql -uroot -p yourdb < db.tds.sql
mysql -uroot -p yourdb < db.data.sql


Ну вот и все. Теперь у нас честная UTF-8 база.

...или почти все, если вы использовали в своих таблицах MD5 от данных. После конвертирования в UTF ваши хэши больше не совпадают со строками, из которых были получены, если в этих строках содержались символы, выходящие за рамки ASCII. Так что внимательно проверяйте таблицы и генерите MD5 заново.

Часть 2. Конвертируем скрипты

Здесь все просто. Во-первых перепишите строки коннекта к БД на использование юникода. Если вы не знаете как это сделать или не уверены, поддерживает ли это ваш драйвер, то первый запрос после коннекта должен выглядеть так:

SET NAMES utf8;

Во-вторых зачастую в наших скриптах есть данные, которые выводятся пользователю. Это не очень верно с точки зрения проектирования приложений, но кто без греха. Поэтому сами скрипты так же следует конвертировать в utf. Делается это так

iconv -f WINDOWS-1251 -t UTF-8 script > script.utf8

В-третьих, если проект был русскоязычный и вы вполне обоснованно ожидали, что /^[\w]+$/  совпадет со строкой "вася", то придется вас огорчить, спецификация PCRE имеет другое мнение по этому вопросу. В частности этот регэксп придется переписать на /^[\p{L}\p{Nd}_]$/.

Дальше все зависит от языка, который вы используете. В perl желательно выставить прагму use utf; и учесть то, что данные, полученные из DBI в utf-е придется руками декодить во внутреннюю кодировку Перла, если с ними нужно будет работать, скажем, в регулярках. В PHP придется вдумчиво заменять функции на их аналоги с mb_ префиксом (и даже лазить в php.ini)

P.S.

Я знаю, что по стандарту UTF-8 пишется именно так. Тем не менее в MySQL он пишется как utf8, так что когда речь идет о БД я использую ее написание.

Я так же знаю про вариант с ALTER TABLE CONVERT TO CHARACTER SET utf8; Не знаю, насколько хорошо и быстро он будет работать с большими базами, а так же не раздует ли от него файл ibdata при использовании InnoDB таблиц. Если раздует, придется опять же делать дамп. Поскольку задачи у меня все больше прикладные, а не академические, иду проверенным путем.

Рубрики: MySQL

↑ Наверх


blog comments powered by Disqus

Контакты

Igor Zinkovsky aka TLoD,Snake. Писать на электропочту, стучаться в аську 302380533, искать в Санкт-Петербурге.

© 2002-2019