Як легко конвертувати utf8 таблиці в utf8mb4 в MySQL 5.5


71

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

Тому я просто хотів би зробити всі порівняння, які є utf8_bin до utf8mb4_bin.

Я вважаю, що з цією зміною немає жодних втрат / виграшів продуктивності, окрім невеликих витрат на зберігання.

Відповіді:


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

Ви також можете пристосувати це для запуску через декілька баз даних, змінивши умову таблиці_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, задану у запитанні. Але ви можете просто замінити це.


Чудовий сценарій, лише кілька записок; Поточні встановлення 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 чи будь-який інший), щоб використовувати інформацію_схеми (ТАБЛИЦІ та КОЛИНИ) для проходу по всіх таблицях, а також МОДИФІКУВАТИ КОЛІНЮ в кожному полі CHAR / VARCHAR / TEXT. Я зібрав би всі МОДИФІЇ в один ALTER для кожної таблиці; це буде ефективніше.

Я думаю (але не впевнений), що пропозиція Райхана лише змінює стандартні для таблиці.


3

Натрапив на цю ситуацію; ось підхід, який я використовував для перетворення моєї бази даних:

  1. По-перше, вам потрібно відредагувати, my.cnfщоб зробити з'єднання бази даних за замовчуванням (між додатками та MYSQL) utf8mb4_unicode_ci сумісним. Без таких символів, як емоджи та подібні файли, що надсилаються вашими програмами, вони не перетворять їх у ваші таблиці в правильних байтах / кодування (якщо тільки параметри DB 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.поряд із назвою проблемної таблиці, це означає, що індексний ключ у деякому стовпці цієї таблиці (який повинен був бути перетворений в MB4 charstring) буде дуже великим, отже, стовпець Varchar повинен бути <= 250, так що його індексний ключ становитиме максимум 1000 байт. Перевірте стовпці, на яких у вас є індекси, і якщо один з них варчар> 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: потім знову запустіть запит таблиці alter для цієї таблиці, і тепер таблицю слід успішно перетворити на utf8mb4.

Ура!


Існує спосіб використання індексу для довгого VARCHAR понад 191 символу. Ви повинні мати привілей DBA / SUPER USER: Налаштування параметрів бази даних: innodb_large_prefix: ON; innodb_file_format: Barracuda; innodb_file_format_max: Barracuda;
Châu Hồng Lĩnh

2

Я написав цей посібник: http://hanoian.com/content/index.php/24-automate-the-converting-a-mysql-database-character-set-to-utf8mb4

З моєї роботи я побачив, що АЛЬТЕР баз даних і таблиць недостатньо. Мені довелося зайти в кожну таблицю і ВСІМО кожне зі стовпців текст / середній текст / вархар.

На щастя, мені вдалося написати скрипт для виявлення метаданих баз даних MySQL, тому він міг переглядати таблиці та стовпці та змінювати їх автоматично.

Довгий індекс для MySQL 5.6:

Існує одна річ, для якої потрібно мати привілей DBA / SUPER USER: Встановлення параметрів бази даних:

innodb_large_prefix: УВІМКНЕНО
innodb_file_format: Barracuda 
innodb_file_format_max: Barracuda

У відповідях на це питання є інструкція, як встановити ці параметри вище: https://stackoverflow.com/questions/35847015/mysql-change-innodb-large-prefix

Звичайно, в моїй статті є вказівки зробити це теж.

Для MySQL версії 5.7 або новішої версії , innodb_large_prefix за замовчуванням увімкнено, а innodb_file_format також Barracuda за замовчуванням.


2

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

  1. ЧАР = = БІНАРІЯ
  2. ТЕКСТ => BLOB
  3. TINYTEXT => TINYBLOB
  4. MEDIUMTEXT => MEDIUMBLOB
  5. LONGTEXT => LONGBLOB
  6. ВАРХАР => ВАРБІНАР

А після цього змініть стовпчик назад до його колишнього типу та за допомогою потрібного коду.

Напр .:

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

Я спробував у кількох таблицях latin1, і це зберігало всю діакритику.

Ви можете отримати цей запит для всіх стовпців, які роблять це:

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%');

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";
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.