Comment convertir facilement les tables utf8 en utf8mb4 dans MySQL 5.5

71

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

Donc, je voudrais juste faire toutes les collations qui sont utf8_bin à utf8mb4_bin.

Je pense qu’il n’ya pas de perte / gain de performance avec ce changement, si ce n’est un peu de surcharge de stockage.

géoaxie
la source

Réponses:

93

De mon guide Comment prendre en charge l'intégralité des bases de données Unicode dans 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 pas copier-coller aveuglément cela! L'instruction exacte dépend du type de colonne, de sa longueur maximale et d'autres propriétés. La ligne ci-dessus n'est qu'un exemple pour une VARCHARcolonne.)

Notez toutefois que vous ne pouvez pas automatiser entièrement la conversion de utf8vers utf8mb4. Comme décrit à l' étape 4 du guide susmentionné , vous devez vérifier la longueur maximale des colonnes et des clés d'indexation, car le nombre que vous spécifiez a une signification différente lorsqu'il utf8mb4est utilisé à la place de utf8.

La section 10.1.11 du manuel de référence MySQL 5.5 contient des informations complémentaires à ce sujet.

Mathias Bynens
la source
31

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 du type varchar, text, tinytext, mediumtext, longtext, char. Vous devriez également sauvegarder votre base de données au cas où quelque chose se briserait.

Copiez le code suivant dans un fichier appelé 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. Puis lancez:

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

Cela générera 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 exécuter plusieurs bases de données en modifiant la condition pour la table_schema. Par exemple table_schema like "wiki_%", toutes les bases de données portant le préfixe seront converties 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 varchar (255) colonnes 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 changer la colonne pour qu'elle soit plus petite, comme varchar (150), et réexécuter la commande.

S'il vous plaît noter : Cette réponse convertit la base de données au utf8mb4_unicode_cilieu de utf8mb4_bin, a demandé à la question. Mais vous pouvez simplement remplacer ceci.

MrJingles87
la source
Super scripting, juste quelques notes; Les installations actuelles de MiariaDb nécessitent la saisie du mot de passe, donc cela mysql -uroot -pThatrootPassWord < alterTables.sqlfonctionne. Et comme vous l'avez déjà noté, utf8mb4_bin est ce que recommande, notamment, nextcloud.
Julius
mais utf8mb4_0900_ai_ci est la valeur par défaut maintenant, voir monolune.com/what-is-the-utf8mb4_0900_ai_ci-collation
Julius
Je devais utiliser "SET foreign_key_checks = 0;", puis appliquer les modifications, puis "SET foreign_key_checks = 1;".
dfrankow
Merci mec. C’était LA solution de Redmin de tout changer en utf8mb4.
Luciano Fantuzzi
5

J'ai utilisé le script shell suivant. Il prend le nom de base de données en tant que paramètre et convertit toutes les tables en un autre jeu de caractères et en un autre classement (donné par un autre paramètre ou la 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
)
Petr Stastny
la source
3

J'écrirais un script (en Perl, ou autre) qui utiliserait information_schema (TABLES et COLUMNS) pour parcourir toutes les tables et modifier MODIFY COLUMN sur chaque champ CHAR / VARCHAR / TEXT. Je voudrais rassembler toutes les MODIFY dans 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 la valeur par défaut pour la table.

Rick James
la source
3

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

  1. Commencez par éditer my.cnfpour rendre la connexion utf8mb4_unicode_ci conforme à la base de données par défaut (entre les applications et MYSQL). Sans cela, les caractères tels que emojis et similaires soumis par vos applications ne parviendront pas à vos tables en octets / encodages droits (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 (inutile de préparer le SQL pour modifier des colonnes individuelles, les ALTER TABLEinstructions le feront).

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

    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. Rassemblez et enregistrez la sortie de SQL ci-dessus dans un fichier point SQL 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 supposée être convertie en chaîne de caractères MB4) sera très grosse, de sorte que la colonne Varchar doit être <= 250, de sorte que sa clé d'index sera max 1000 octets. Vérifiez les colonnes sur lesquelles vous avez des index et si l’un d’eux est un varchar> 250 (probablement 255), puis

    • Étape 1: vérifiez les données dans cette colonne pour vous assurer que la taille maximale de la chaîne 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;
      
    • Etape 2: si max charlength des données de colonne indexée <= 250, remplacez la longueur de la colonne par 250. Si cela n’est pas possible, supprimez l’index de cette colonne.

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

À votre santé!

Nav44
la source
Il existe un moyen d’utiliser index pour VARCHAR long sur 191 caractères. Vous devez disposer du privilège DBA / SUPER USER pour effectuer les tâches suivantes: définition des paramètres de la base de données: innodb_large_prefix: ON; innodb_file_format: Barracuda; innodb_file_format_max: Barracuda;
Châu Hồng Lĩnh
2

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

D'après mon travail, j'ai vu qu'ALTER la base de données et les tables ne suffisait pas. Je devais aller dans chaque table et modifier chaque colonne 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:

Vous devez disposer du privilège DBA / SUPER USER: Définissez les paramètres de la base de données:

innodb_large_prefix: ON
innodb_file_format: Barracuda 
innodb_file_format_max: Barracuda

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

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

Pour MySQL version 5.7 ou ultérieure, innodb_large_prefix est activé par défaut et innodb_file_format est également Barracuda par défaut.

Châu Hồng Lĩnh
la source
2

Pour les personnes qui pourraient avoir ce problème, la meilleure solution consiste à modifier d'abord les colonnes en un type binaire, en fonction de ce tableau:

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

Et après cela, modifiez la colonne à son type précédent 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 tables latin1 et cela a gardé tous les signes 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%');
MalachiteBR
la source
0

J'ai 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";
}
clops
la source