Как легко конвертировать таблицы utf8 в utf8mb4 в MySQL 5.5

71

У меня есть база данных, которая теперь должна поддерживать 4 байта символов (китайский). К счастью, у меня уже есть MySQL 5.5 в производстве.

Поэтому я просто хотел бы сделать все сопоставления от utf8_bin до utf8mb4_bin.

Я полагаю, что это изменение не приведет к потере / увеличению производительности, за исключением небольшого объема памяти.

geoaxis
источник

Ответы:

93

Из моего руководства Как поддерживать полный Unicode в базах данных MySQL , вот запросы, которые вы можете выполнить, чтобы обновить кодировку и сопоставление базы данных, таблицы или столбца:

Для каждой базы данных:

ALTER DATABASE
    database_name
    CHARACTER SET = utf8mb4
    COLLATE = utf8mb4_unicode_ci;

Для каждой таблицы:

ALTER TABLE
    table_name
    CONVERT TO CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

Для каждого столбца:

ALTER TABLE
    table_name
    CHANGE column_name column_name
    VARCHAR(191)
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

(Не слепо копируйте и вставляйте это! Точное утверждение зависит от типа столбца, максимальной длины и других свойств. Приведенная выше строка является просто примером для VARCHARстолбца.)

Обратите внимание, что вы не можете полностью автоматизировать преобразование из utf8в utf8mb4. Как описано в шаге 4 вышеупомянутого руководства , вам необходимо проверить максимальную длину столбцов и индексных ключей, так как указанное число имеет другое значение, когда utf8mb4используется вместо utf8.

Раздел 10.1.11 Справочного руководства по MySQL 5.5 содержит дополнительную информацию по этому вопросу.

Матиас Биненс
источник
31

У меня есть решение, которое преобразует базы данных и таблицы, выполнив несколько команд. Он также преобразует все столбцы типа varchar, text, tinytext, mediumtext, longtext, char. Вы также должны сделать резервную копию базы данных на случай, если что-то сломается.

Скопируйте следующий код в файл с именем preAlterTables.sql:

use information_schema;
SELECT concat("ALTER DATABASE `",table_schema,"` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;") as _sql 
FROM `TABLES` where table_schema like "yourDbName" group by table_schema;
SELECT concat("ALTER TABLE `",table_schema,"`.`",table_name,"` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;") as _sql  
FROM `TABLES` where table_schema like "yourDbName" group by table_schema, table_name;
SELECT concat("ALTER TABLE `",table_schema,"`.`",table_name, "` CHANGE `",column_name,"` `",column_name,"` ",data_type,"(",character_maximum_length,") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",IF(is_nullable="YES"," NULL"," NOT NULL"),";") as _sql 
FROM `COLUMNS` where table_schema like "yourDbName" and data_type in ('varchar','char');
SELECT concat("ALTER TABLE `",table_schema,"`.`",table_name, "` CHANGE `",column_name,"` `",column_name,"` ",data_type," CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",IF(is_nullable="YES"," NULL"," NOT NULL"),";") as _sql 
FROM `COLUMNS` where table_schema like "yourDbName" and data_type in ('text','tinytext','mediumtext','longtext');

Замените все вхождения yourDbName на базу данных, которую вы хотите преобразовать. Затем запустите:

mysql -uroot < preAlterTables.sql | egrep '^ALTER' > alterTables.sql

Это сгенерирует новый файл alterTables.sql со всеми запросами, необходимыми для преобразования базы данных. Запустите следующую команду, чтобы начать преобразование:

mysql -uroot < alterTables.sql

Вы также можете настроить это для работы с несколькими базами данных, изменив условие для table_schema. Например table_schema like "wiki_%", преобразует все базы данных с префиксом имени wiki_. Для преобразования всех баз данных замените условие на table_type!='SYSTEM VIEW'.

Проблема, которая может возникнуть. У меня было несколько столбцов varchar (255) в ключах mysql. Это вызывает ошибку:

ERROR 1071 (42000) at line 2229: Specified key was too long; max key length is 767 bytes

Если это произойдет, вы можете просто изменить столбец на меньший, например, varchar (150), и снова запустить команду.

Обратите внимание : этот ответ преобразует базу данных utf8mb4_unicode_ciвместо того utf8mb4_bin, что задано в вопросе. Но вы можете просто заменить это.

MrJingles87
источник
Отличный сценарий, всего несколько заметок; Текущие установки MiariaDb требуют ввода пароля, поэтому mysql -uroot -pThatrootPassWord < alterTables.sqlработает. И, как вы уже заметили, utf8mb4_bin - это то, что, помимо прочего, рекомендует nextcloud.
Юлий
но utf8mb4_0900_ai_ci теперь используется по умолчанию, см. monolune.com/what-is-the-utf8mb4_0900_ai_ci-collation
Джулиус
Мне пришлось использовать «SET foreign_key_checks = 0;», затем применить изменения, а затем «SET foreign_key_checks = 1;».
dfrankow
Спасибо, мужик. Это было РЕШЕНИЕ в Redmin, чтобы изменить все на utf8mb4.
Лучано Фантуцци
5

Я использовал следующий сценарий оболочки. Он принимает имя базы данных в качестве параметра и преобразует все таблицы в другую кодировку и сопоставление (заданное другими параметрами или значением по умолчанию, определенным в сценарии).

#!/bin/bash

# mycollate.sh <database> [<charset> <collation>]
# changes MySQL/MariaDB charset and collation for one database - all tables and
# all columns in all tables

DB="$1"
CHARSET="$2"
COLL="$3"

[ -n "$DB" ] || exit 1
[ -n "$CHARSET" ] || CHARSET="utf8mb4"
[ -n "$COLL" ] || COLL="utf8mb4_general_ci"

echo $DB
echo "ALTER DATABASE \`$DB\` CHARACTER SET $CHARSET COLLATE $COLL;" | mysql

echo "USE \`$DB\`; SHOW TABLES;" | mysql -s | (
    while read TABLE; do
        echo $DB.$TABLE
        echo "ALTER TABLE \`$TABLE\` CONVERT TO CHARACTER SET $CHARSET COLLATE $COLL;" | mysql $DB
    done
)
Петр Стастный
источник
3

Я написал бы скрипт (на Perl или что-то еще), чтобы использовать information_schema (TABLES и COLUMNS) для обхода всех таблиц и выполнять MODIFY COLUMN для каждого поля CHAR / VARCHAR / TEXT. Я бы собрал все MODIFY в один ALTER для каждой таблицы; это будет более эффективным.

Я думаю (но не уверен), что предложение Райхана только меняет значение по умолчанию для таблицы.

Рик Джеймс
источник
3

Столкнулся с этой ситуацией; вот подход, который я использовал для преобразования моей базы данных:

  1. Во-первых, вам нужно отредактировать, my.cnfчтобы сделать соединение с базой данных по умолчанию (между приложениями и MYSQL) utf8mb4_unicode_ci совместимым. Без этих символов, таких как emojis и аналогичные, представленные вашими приложениями, вы не попадете в ваши таблицы в правильных байтах / кодировке (если в параметрах CNN БД вашего приложения не указано соединение utf8mb4).

    Инструкции приведены здесь .

  2. Выполните следующий SQL (не нужно готовить SQL для изменения отдельных столбцов, ALTER TABLEоператоры сделают это).

    Перед тем, как выполнить приведенный ниже код, замените «DbName» на свое фактическое имя БД.

    USE information_schema;
    
    SELECT concat("ALTER DATABASE `",table_schema,
                  "` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;") as _sql
      FROM `TABLES`
     WHERE table_schema like "DbName"
     GROUP BY table_schema;
    
    SELECT concat("ALTER TABLE `",table_schema,"`.`",table_name,
                  "` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;") as _sql
      FROM `TABLES`
     WHERE table_schema like "DbName"
     GROUP BY table_schema, table_name;
    
  3. Соберите и сохраните вывод вышеупомянутого SQL в точечный файл sql и выполните его.

  4. Если вы получаете сообщение об ошибке #1071 - Specified key was too long; max key length is 1000 bytes.вместе с проблемным именем таблицы, это означает, что индексный ключ для некоторого столбца этой таблицы (который должен был быть преобразован в charstring MB4) будет очень большим, следовательно, столбец Varchar должен быть <= 250, так что его Ключ индекса будет максимум 1000 байтов. Проверьте столбцы, по которым у вас есть индексы, и если один из них является varchar> 250 (скорее всего, 255), то

    • Шаг 1: проверьте данные в этом столбце, чтобы убедиться, что максимальный размер строки в этом столбце <= 250.

      Пример запроса:

      select `id`,`username`, `email`,
             length(`username`) as l1,
             char_length(`username`) as l2,
             length(`email`) as l3,
             char_length(`email`) as l4
        from jos_users
       order by l4 Desc;
      
    • Шаг 2: если максимальная длина строки данных индексированного столбца <= 250, измените длину столбца на 250. Если это невозможно, удалите индекс для этого столбца.

    • Шаг 3: затем снова запустите запрос на изменение таблицы для этой таблицы, и теперь таблица должна быть успешно преобразована в utf8mb4.

Ура!

Nav44
источник
Существует способ использовать индекс для длинных VARCHAR длиной более 191 символа. Вы должны иметь привилегию DBA / SUPER USER для: установки параметров базы данных: innodb_large_prefix: ON; innodb_file_format: Барракуда; innodb_file_format_max: Барракуда;
Châu Hồng
2

Я написал это руководство: http://hanoian.com/content/index.php/24-automate-the-converting-a-mysql-database-character-set-to-utf8mb4

Из моей работы я увидел, что ALTER базы данных и таблиц недостаточно. Я должен был войти в каждую таблицу и изменить каждый из столбцов text / mediumtext / varchar.

К счастью, мне удалось написать скрипт для обнаружения метаданных баз данных MySQL, чтобы он мог циклически проходить по таблицам и столбцам и автоматически изменять их.

Длинный индекс для MySQL 5.6:

Есть одна вещь, которую вы должны иметь привилегию DBA / SUPER USER: установка параметров базы данных:

innodb_large_prefix: ON
innodb_file_format: Барракуда 
innodb_file_format_max: Барракуда

В ответах на этот вопрос есть инструкция, как установить эти параметры выше: https://stackoverflow.com/questions/35847015/mysql-change-innodb-large-prefix

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

Для MySQL версии 5.7 или новее , innodb_large_prefix включен по умолчанию, а innodb_file_format также является Barracuda по умолчанию.

Châu Hồng Lhnh
источник
2

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

  1. CHAR => Двоичный
  2. ТЕКСТ => BLOB
  3. TINYTEXT => TINYBLOB
  4. MEDIUMTEXT => MEDIUMBLOB
  5. LONGTEXT => LONGBLOB
  6. VARCHAR => VARBINARY

И после этого измените столбец до его прежнего типа и с вашим желаемым набором символов.

Например.:

ALTER TABLE [TABLE_SCHEMA].[TABLE_NAME] MODIFY [COLUMN_NAME] LONGBLOB;
ALTER TABLE [TABLE_SCHEMA].[TABLE_NAME] MODIFY [COLUMN_NAME] VARCHAR(140) CHARACTER SET utf8mb4;

Я попробовал в нескольких латинских таблицах, и это сохранило все диакритические знаки.

Вы можете извлечь этот запрос для всех столбцов, которые делают это:

SELECT
CONCAT('ALTER TABLE ', TABLE_SCHEMA,'.', TABLE_NAME,' MODIFY ', COLUMN_NAME,' VARBINARY;'),
CONCAT('ALTER TABLE ', TABLE_SCHEMA,'.', TABLE_NAME,' MODIFY ', COLUMN_NAME,' ', COLUMN_TYPE,' CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;')
FROM information_schema.columns
WHERE TABLE_SCHEMA IN ('[TABLE_SCHEMA]')
AND COLUMN_TYPE LIKE 'varchar%'
AND (COLLATION_NAME IS NOT NULL AND COLLATION_NAME NOT LIKE 'utf%');
MalachiteBR
источник
0

Я сделал скрипт, который делает это более или менее автоматически:

<?php
/**
 * Requires php >= 5.5
 * 
 * Use this script to convert utf-8 data in utf-8 mysql tables stored via latin1 connection
 * This is a PHP port from: https://gist.github.com/njvack/6113127
 *
 * BACKUP YOUR DATABASE BEFORE YOU RUN THIS SCRIPT!
 *
 * Once the script ran over your databases, change your database connection charset to utf8:
 *
 * $dsn = 'mysql:host=localhost;port=3306;charset=utf8';
 * 
 * DON'T RUN THIS SCRIPT MORE THAN ONCE!
 *
 * @author hollodotme
 *
 * @author derclops since 2019-07-01
 *
 *         I have taken the liberty to adapt this script to also do the following:
 *
 *         - convert the database to utf8mb4
 *         - convert all tables to utf8mb4
 *         - actually then also convert the data to utf8mb4
 *
 */

header('Content-Type: text/plain; charset=utf-8');

$dsn      = 'mysql:host=localhost;port=3306;charset=utf8';
$user     = 'root';
$password = 'root';
$options  = [
    \PDO::ATTR_CURSOR                   => \PDO::CURSOR_FWDONLY,
    \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
    \PDO::MYSQL_ATTR_INIT_COMMAND       => "SET CHARACTER SET latin1",
];


$dbManager = new \PDO( $dsn, $user, $password, $options );

$databasesToConvert = [ 'database1',/** database3, ... */ ];
$typesToConvert     = [ 'char', 'varchar', 'tinytext', 'mediumtext', 'text', 'longtext' ];

foreach ( $databasesToConvert as $database )
{
    echo $database, ":\n";
    echo str_repeat( '=', strlen( $database ) + 1 ), "\n";

    $dbManager->exec( "USE `{$database}`" );

    echo "converting database to correct locale too ... \n";

    $dbManager->exec("ALTER DATABASE `{$database}` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci");


    $tablesStatement = $dbManager->query( "SHOW TABLES" );
    while ( ($table = $tablesStatement->fetchColumn()) )
    {
        echo "Table: {$table}:\n";
        echo str_repeat( '-', strlen( $table ) + 8 ), "\n";

        $columnsToConvert = [ ];

        $columsStatement = $dbManager->query( "DESCRIBE `{$table}`" );

        while ( ($tableInfo = $columsStatement->fetch( \PDO::FETCH_ASSOC )) )
        {
            $column = $tableInfo['Field'];
            echo ' * ' . $column . ': ' . $tableInfo['Type'];

            $type = preg_replace( "#\(\d+\)#", '', $tableInfo['Type'] );

            if ( in_array( $type, $typesToConvert ) )
            {
                echo " => must be converted\n";

                $columnsToConvert[] = $column;
            }
            else
            {
                echo " => not relevant\n";
            }
        }


        //convert table also!!!
        $convert = "ALTER TABLE `{$table}` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci";

        echo "\n", $convert, "\n";
        $dbManager->exec( $convert );
        $databaseErrors = $dbManager->errorInfo();
        if( !empty($databaseErrors[1]) ){
            echo "\n !!!!!!!!!!!!!!!!! ERROR OCCURED ".print_r($databaseErrors, true)." \n";
            exit;
        }


        if ( !empty($columnsToConvert) )
        {
            $converts = array_map(
                function ( $column )
                {
                    //return "`{$column}` = IFNULL(CONVERT(CAST(CONVERT(`{$column}` USING latin1) AS binary) USING utf8mb4),`{$column}`)";
                    return "`{$column}` = CONVERT(BINARY(CONVERT(`{$column}` USING latin1)) USING utf8mb4)";
                },
                $columnsToConvert
            );

            $query = "UPDATE IGNORE `{$table}` SET " . join( ', ', $converts );

            //alternative
            // UPDATE feedback SET reply = CONVERT(BINARY(CONVERT(reply USING latin1)) USING utf8mb4) WHERE feedback_id = 15015;


            echo "\n", $query, "\n";


            $dbManager->exec( $query );

            $databaseErrors = $dbManager->errorInfo();
            if( !empty($databaseErrors[1]) ){
                echo "\n !!!!!!!!!!!!!!!!! ERROR OCCURED ".print_r($databaseErrors, true)." \n";
                exit;
            }
        }

        echo "\n--\n";
    }

    echo "\n";
}
clops
источник