Как определить символы UTF-8 в столбце с кодировкой Latin1 - MySQL

Я собираюсь взять на себя утомительную задачу по преобразованию базы данных из Latin1 в UTF-8.

На этом этапе я просто хочу проверить, какие данные я сохранил в своих таблицах, так как это определит, какой подход я должен использовать для преобразования данных.

В частности, я хочу проверить, есть ли у меня символы UTF-8 в столбцах Latin1, как лучше всего это сделать? Если затронуты только несколько строк, я могу просто исправить это вручную.

Вариант 1. Сделать дамп MySQL и использовать Perl для поиска символов UTF-8?

Вариант 2. Использовать MySQL CHAR_LENGTH для поиска строк с многобайтовыми символами? например SELECT name FROM clients WHERE LENGTH(name) != CHAR_LENGTH(name); Этого достаточно?

На данный момент я переключил свою клиентскую кодировку Mysql на UTF-8.


person dinie    schedule 16.02.2012    source источник
comment
По определению, вы не можете хранить данные UTF-8 в столбцах Latin1. Не хотите ли подробнее рассказать о своем вопросе?   -  person deceze♦    schedule 16.02.2012
comment
Все многобайтовые символы UTF-8 больше числа 128. Но на самом деле невозможно определить, в чем заключалась цель символа: я думаю, вы хотели спросить, могу ли я обнаружить символы, отличные от ASCII в столбце с кодировкой Latin1. Очевидно, поскольку последовательность байтов 0xF0 0x53 означает две разные вещи в UTF-8 и Latin1, вы не узнаете, что это такое, даже после того, как найдете ее ...   -  person Borealid    schedule 16.02.2012
comment
@deceze Вы можете случайно сохранить данные UTF-8 в столбце LATIN1, потому что LATIN1 - это 8-битный набор символов. Просто в неправильной кодировке это выглядит беспорядком.   -  person tadman    schedule 16.02.2012
comment
@tadman Вы имеете в виду распространенную ошибку, которую я описал в kunststube.net/frontback? Это не означает, что вы храните данные UTF-8, это означает, что вы храните данные, которые база данных считает Latin1, но клиент интерпретирует как UTF-8. Трудно сказать, имеется ли это в виду здесь или нет, отсюда вопрос к OP.   -  person deceze♦    schedule 16.02.2012
comment
Это то, о чем я говорю. База данных убеждена, что это LATIN1, но клиент все равно интерпретирует их как UTF-8. Вы можете случайно поместить UTF-8 в базу данных LATIN1, но вы не можете поместить LATIN1 в UTF-8, потому что недопустимые символы UTF-8 будут усечены. Не существует недопустимого символа LATIN1.   -  person tadman    schedule 16.02.2012
comment
@Borealid 0xF0 0x53 не является допустимой последовательностью UTF8, поэтому до определенной степени это вполне выполнимо.   -  person tripleee    schedule 16.02.2012
comment
@triplee Curses, вместо этого следовало выбрать одну из более чем 30 000 неоднозначных двухбайтовых последовательностей! Но я думаю, вы понимаете мою точку зрения. Есть некоторые символы, которые не могут быть UTF-8, но многие двухсимвольные последовательности latin1 также являются допустимыми двухбайтовыми символами UTF-8.   -  person Borealid    schedule 16.02.2012
comment
@Borealid Конечно, есть и угловые случаи, но в реальном мире это вполне выполнимо. Нетрудно понять, что Bjrn - это ошибочная кодировка Björn в UTF8. Неоднозначности в пространстве Latin-1 начинаются с заглавной буквы A с акцентом, за которой следует неалфавитный знак, что маловероятно встречается в реальных текстовых данных (если только ваши родители не читают XKCD и не решили назвать вас Möjibake :-)   -  person tripleee    schedule 16.02.2012
comment
@tripleee А последовательность байтов 0xC2A0, которая представляет собой неразрывный пробел в UTF-8 и Â , за которым следует неразрывный пробел в latin1? Замечательный исчезающий персонаж!   -  person Borealid    schedule 16.02.2012


Ответы (4)


Кодировка символов, как и часовые пояса, является постоянным источником проблем.

Что вы можете сделать, так это поискать любые символы "высокого ASCII", поскольку это либо символы с диакритическими знаками LATIN1, либо первый из многобайтовых символов UTF-8. Определить разницу будет нелегко, если вы немного не обманете.

Чтобы выяснить, какая кодировка правильная, вы просто SELECT две разные версии и сравниваете их визуально. Вот пример:

SELECT CONVERT(CONVERT(name USING BINARY) USING latin1) AS latin1, 
       CONVERT(CONVERT(name USING BINARY) USING utf8) AS utf8 
FROM users 
WHERE CONVERT(name USING BINARY) RLIKE CONCAT('[', UNHEX('80'), '-', UNHEX('FF'), ']')

Это делается необычно сложным, потому что механизм регулярных выражений MySQL, кажется, игнорирует такие вещи, как \x80, и заставляет вместо этого использовать метод UNHEX().

Это дает такие результаты:

latin1                utf8
----------------------------------------
Björn                Björn
person tadman    schedule 16.02.2012
comment
Приносим извинения за поздний ответ и нечеткий начальный вопрос. Получил этот ответ, потому что он более или менее помог мне в обнаружении символов, где вероятным намерением был символ UTF8. Проголосовал за ответ deceze, потому что он содержал ситуации, которые у меня есть в другом месте в базе данных - person dinie; 23.02.2012
comment
Потрясающе - этот маленький самородок помог мне исправить проблему, когда данные в кодировке utf8 были вставлены в таблицу utf8, но интерпретировались как latin1, потому что я ввел их через интерфейс командной строки mysql ... забавно, потому что, поскольку система была настроена на UTF8, все выглядело нормально, когда ввод и выбор (только не при декодировании и рендеринге на соответствующем веб-сайте). - person Kasapo; 25.10.2012
comment
Иногда, если вы читаете и записываете данные из двух соединений с точно такой же неправильной конфигурацией, это волшебным образом срабатывает. Иногда две ошибки делают правильное. - person tadman; 25.10.2012
comment
Первый байт кодовой точки в кодировке UTF-8 выше диапазона ASCII находится в диапазоне 0xC2-0xF4 (U + 0080 начинается с байта 0xC2; U + 10FFFF начинается с 0xF4). Таким образом, диапазон в этом ответе может быть более ограничительным, чтобы уменьшить количество ложных срабатываний. - person dolmen; 11.12.2017
comment
Это также дает ложноположительные результаты = ›latin1 é letter (utf char: 195, ansi char: 233), есть ли обходной путь? - person Jack; 24.11.2018

Поскольку ваш вопрос не совсем ясен, давайте предположим несколько сценариев:

  1. До сих пор неправильное соединение: вы неправильно подключались к своей базе данных, используя кодировку latin1, но сохранили данные UTF-8 в базе данных (кодировка столбца в этом случае не имеет значения). Это случай, который я описал здесь. В этом случае это легко исправить: сбросить содержимое базы данных в файл через соединение latin1. Это переведет неправильно сохраненные данные в неправильно сохраненный UTF-8, как это работало до сих пор (подробности кровавых подробностей см. В указанной выше статье). Затем вы можете повторно импортировать данные в базу данных через правильно настроенное соединение utf8, и они будут сохранены, как и должно быть.
  2. До сих пор неправильная кодировка столбца: данные UTF-8 были вставлены в столбец latin1 через соединение utf8. В этом случае забудьте, данные исчезнут. Любой не латинский символ должен быть заменен на ?.
  3. До сих пор все было хорошо, с этого момента добавлена ​​поддержка UTF-8: у вас есть данные Latin-1, правильно сохраненные в столбце latin1, вставленные через соединение latin1, но вы хотите расширить это, чтобы также разрешить данные UTF-8 . В этом случае просто измените кодировку столбца на utf8. MySQL преобразует существующие данные за вас. Затем просто убедитесь, что для подключения к базе данных установлено значение utf8, когда вы вставляете данные UTF-8.
person deceze♦    schedule 16.02.2012
comment
Если несколько клиентов добавляли данные, и некоторые из них предполагали, что они должны отправить utf8, вы получите нечестивую смесь, которую в основном нужно разобрать вручную. Это не означает, что вы не можете автоматизировать части процесса, и на самом деле большинство случаев, вероятно, можно решить без вмешательства человека. - person tripleee; 16.02.2012
comment
Верно, но тогда вы действительно напуганы. Прежде чем даже попытаться ответить на этот сценарий, OP должен предоставить гораздо больше информации о реальной проблеме. - person deceze♦; 16.02.2012
comment
В случае 1 у меня сработала команда mysqldump --default-character-set=latin1 -u user -p database. Затем мне пришлось зайти в файл дампа и изменить SET NAMES latin1 на utf8. Затем повторно импортируйте файл дампа и все исправлено. - person James; 17.02.2017

Есть скрипт на github, который поможет в этом.

person Patrick James McDougle    schedule 28.01.2013
comment
Этот скрипт мне очень понравился, и я внес некоторые улучшения, чтобы сделать его намного быстрее и гибче. У меня также есть ветка, которая преобразуется в "правильная" кодировка utf8mb4 в MySQL. - person Synchro; 05.04.2013
comment
Этот скрипт работал ... до сих пор не понимаю, как он работал, хотя ... нужно когда-нибудь пройти через это ... Почти безболезненно перешел с latin1 на utf8, пришлось добавить mysql_set_charset("utf8"); для php, чтобы использовать его сразу после этого. - person Karthik T; 07.09.2014
comment
ОП спросил, как обнаружить символы UTF-8 в столбцах Latin1. AFAICT, сценарий mysql-convert-latin1-to-utf8 в настоящее время фактически не помогает с этим. Вместо этого он имеет изменяемый пользователем массив $collationMap, определяющий набор пар сопоставлений ключ-значение. Для каждого столбца, сопоставление которого соответствует одному из ключей, сценарий будет слепо предполагать, что его содержимое закодировано с помощью кодировки, соответствующей сопоставлению значения. Сценарий изменяет сопоставление столбца (и, неявно, набор символов) на последнее, сохраняя двоичное значение содержимого. - person ; 05.06.2018
comment
Что ж, обнаружить что-то вроде невозможно. Кто-то воткнул квадратный колышек в круглое отверстие и спросил, почему он не квадратный, когда возвращается ... Вы можете сказать, потому что получаемые данные выглядят не совсем правильно, но вы должны посмотреть на них, чтобы понять ( или определить последовательности общих символов, которые при интерпретации как UTF-8 более вероятны, чем последовательность латинских символов). - person Patrick James McDougle; 13.06.2018

Я бы создал дамп базы данных и grep для всех допустимых последовательностей UTF8. Где взять, зависит от того, что вы получите. Есть несколько вопросов по SO об идентификации недопустимого UTF8; вы можете просто изменить логику.

Изменить. Таким образом, любое поле, состоящее полностью из 7-битного ASCII, является безопасным, и любое поле, содержащее недопустимую последовательность UTF-8, можно считать Latin-1. Остальные данные следует проверить - если вам повезет, несколько очевидных замен исправят абсолютное большинство (замените ö на Latin-1 ö и т. Д.).

person tripleee    schedule 16.02.2012
comment
Этот ответ содержит довольно длинный список вероятных плохих комбинаций. - person Synchro; 05.04.2013