web-dev-qa-db-fra.com

Comment convertir facilement des tables utf8 en utf8mb4 dans MySQL 5.5

J'ai une base de données qui doit maintenant prendre en charge les caractères de 4 octets (chinois). Heureusement, j'ai déjà MySQL 5.5 en production.

Je voudrais donc simplement faire tous les classements qui sont utf8_bin à utf8mb4_bin.

Je crois qu'il n'y a aucune perte/gain de performances avec ce changement autre qu'un peu de surcharge de stockage.

91
geoaxis

Dans mon guide Comment prendre en charge Unicode complet dans les bases de données MySQL , voici les requêtes que vous pouvez exécuter pour mettre à jour le jeu de caractères et le classement d'une base de données, d'une table ou d'une colonne:

Pour chaque base de données:

ALTER DATABASE
    database_name
    CHARACTER SET = utf8mb4
    COLLATE = utf8mb4_unicode_ci;

Pour chaque table:

ALTER TABLE
    table_name
    CONVERT TO CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

Pour chaque colonne:

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

(Ne copiez-collez pas aveuglément! L'instruction exacte dépend du type de colonne, de la longueur maximale et d'autres propriétés. La ligne ci-dessus n'est qu'un exemple pour un VARCHAR colonne.)

Notez cependant que vous ne pouvez pas automatiser complètement la conversion à partir de utf8 à utf8mb4. Comme décrit dans étape 4 du guide susmentionné , vous devrez vérifier la longueur maximale des colonnes et des clés d'index, car le nombre que vous spécifiez a une signification différente lorsque utf8mb4 est utilisé à la place de utf8.

Section 10.1.11 du Manuel de référence MySQL 5.5 a plus d'informations à ce sujet.

106
Mathias Bynens

J'ai une solution qui convertira les bases de données et les tables en exécutant quelques commandes. Il convertit également toutes les colonnes de type varchar, text, tinytext, mediumtext, longtext, char. Vous devriez également sauvegarder votre base de données au cas où quelque chose se casserait.

Copiez le code suivant dans un fichier appelé it 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');

Remplacez toutes les occurrences de "yourDbName" par la base de données que vous souhaitez convertir. Exécutez ensuite:

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

Cela va générer un nouveau fichier alterTables.sql, avec toutes les requêtes dont vous avez besoin pour convertir la base de données. Exécutez la commande suivante pour démarrer la conversion:

mysql -uroot < alterTables.sql

Vous pouvez également l'adapter pour qu'il s'exécute sur plusieurs bases de données, en modifiant la condition du schéma_table. Par exemple table_schema like "wiki_%" convertira toutes les bases de données avec le préfixe de nom wiki_. Pour convertir toutes les bases de données, remplacez la condition par table_type!='SYSTEM VIEW'.

Un problème qui pourrait survenir. J'ai eu quelques colonnes varchar (255) dans les clés mysql. Cela provoque une erreur:

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

Si cela se produit, vous pouvez simplement modifier la colonne pour qu'elle soit plus petite, comme varchar (150), et réexécuter la commande.

Veuillez noter: Cette réponse convertit la base de données en utf8mb4_unicode_ci au lieu de utf8mb4_bin, demandé dans la question. Mais vous pouvez simplement remplacer cela.

39
MrJingles87

J'ai utilisé le script Shell suivant. Il prend le nom de la base de données comme paramètre et convertit toutes les tables en un autre jeu de caractères et classement (donné par un autre paramètre ou une valeur par défaut définie dans le script).

#!/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
)
8
Petr Stastny

J'écrirais un script (en Perl ou autre) pour utiliser information_schema (TABLES et COLUMNS) pour parcourir toutes les tables et faire MODIFY COLUMN sur chaque champ CHAR/VARCHAR/TEXT. Je rassemblerais tous les MODIFYs en un seul ALTER pour chaque table; ce sera plus efficace.

Je pense (mais je ne suis pas sûr) que la suggestion de Raihan ne change que le par défaut pour la table.

3
Rick James

Ran dans cette situation; voici l'approche que j'ai utilisée pour convertir ma base de données:

  1. Tout d'abord, vous devez modifier my.cnf pour établir la connexion par défaut à la base de données (entre les applications et MYSQL) conforme à utf8mb4_unicode_ci. Sans ces caractères, comme les emojis et les éléments similaires soumis par vos applications, ils ne parviendront pas à vos tables dans les bons octets/encodage (sauf si les paramètres DB CNN de votre application spécifient une connexion utf8mb4).

    Instructions données ici .

  2. Exécutez le SQL suivant (pas besoin de préparer du SQL pour changer des colonnes individuelles, ALTER TABLE les instructions le feront).

    Avant d'exécuter le code ci-dessous, remplacez "DbName" par votre nom de base de données réel.

    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. Collectez et enregistrez la sortie de SQL ci-dessus dans un fichier sql dot et exécutez-le.

  4. Si vous obtenez une erreur comme #1071 - Specified key was too long; max key length is 1000 bytes. avec le nom de la table problématique, cela signifie que la clé d'index sur une colonne de cette table (qui était censée être convertie en chaîne de caractères MB4) sera très grande, de sorte que la colonne Varchar devrait être <= 250 afin que sa clé d'index soit max 1000 octets. Vérifiez les colonnes sur lesquelles vous avez des index et si l'un d'eux est un varchar> 250 (très probablement 255), puis

    • Étape 1: vérifiez les données de cette colonne pour vous assurer que la taille de chaîne maximale dans cette colonne est <= 250.

      Exemple de requête:

      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;
      
    • Étape 2: si la longueur maximale des données de colonne indexées <= 250, changez la longueur de col en 250. si ce n'est pas possible, supprimez l'index sur cette colonne

    • Étape 3: exécutez à nouveau la requête alter table pour cette table et la table doit maintenant être convertie en utf8mb4 avec succès.

À votre santé!

3
Nav44

Pour les personnes susceptibles d'avoir ce problème, la meilleure solution consiste à modifier d'abord les colonnes en type binaire, selon ce tableau:

  1. CHAR => BINAIRE
  2. TEXTE => BLOB
  3. TINYTEXT => TINYBLOB
  4. MEDIUMTEXT => MEDIUMBLOB
  5. LONGTEXT => LONGBLOB
  6. VARCHAR => VARBINAIRE

Et après cela, modifiez la colonne à son ancien type et avec votre jeu de caractères souhaité.

Par exemple.:

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

J'ai essayé dans plusieurs tableaux latin1 et il a gardé tous les diacritiques.

Vous pouvez extraire cette requête pour toutes les colonnes en procédant comme suit:

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

J'ai écrit ce guide: http://hanoian.com/content/index.php/24-automate-the-converting-a-mysql-database-character-set-to-utf8mb4

De mon travail, j'ai vu qu'ALTER la base de données et les tables ne suffisait pas. J'ai dû aller dans chaque table et ALTER aussi chacune des colonnes text/mediumtext/varchar.

Heureusement, j'ai pu écrire un script pour détecter les métadonnées des bases de données MySQL, afin qu'il puisse parcourir les tables et les colonnes et les modifier automatiquement.

Index long pour MySQL 5.6:

Il y a une chose que vous devez avoir le privilège DBA/SUPER USER pour faire: Définition des paramètres de la base de données:

 innodb_large_prefix: ON 
 innodb_file_format: Barracuda 
 innodb_file_format_max: Barracuda 

Dans les réponses à cette question, des instructions expliquent comment définir ces paramètres ci-dessus: https://stackoverflow.com/questions/35847015/mysql-change-innodb-large-prefix

Bien sûr, dans mon article, il y a des instructions pour le faire aussi.

Pour MySQL version 5.7 ou plus récente , innodb_large_prefix est ON par défaut, et innodb_file_format est également Barracuda par défaut.

2
Châu Hồng Lĩnh

I fait un script qui le fait plus ou moins automatiquement:

<?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";
}
0
clops