Cómo convertir fácilmente tablas utf8 a utf8mb4 en MySQL 5.5

71

Tengo una base de datos que ahora necesita admitir caracteres de 4 bytes (chino). Afortunadamente ya tengo MySQL 5.5 en producción.

Así que me gustaría hacer todas las colaciones que son utf8_bin a utf8mb4_bin.

Creo que no hay pérdida / ganancia de rendimiento con este cambio que no sea un poco de sobrecarga de almacenamiento.

geoaxis
fuente

Respuestas:

93

De mi guía Cómo admitir Unicode completo en bases de datos MySQL , aquí están las consultas que puede ejecutar para actualizar el conjunto de caracteres y la recopilación de una base de datos, una tabla o una columna:

Para cada base de datos:

ALTER DATABASE
    database_name
    CHARACTER SET = utf8mb4
    COLLATE = utf8mb4_unicode_ci;

Para cada mesa:

ALTER TABLE
    table_name
    CONVERT TO CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

Para cada columna:

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

(¡No copie y pegue ciegamente esto! La declaración exacta depende del tipo de columna, la longitud máxima y otras propiedades. La línea anterior es solo un ejemplo para una VARCHARcolumna).

Sin embargo, tenga en cuenta que no puede automatizar completamente la conversión de utf8a utf8mb4. Como se describe en el paso 4 de la guía mencionada anteriormente , deberá verificar la longitud máxima de las columnas y las claves de índice, ya que el número que especifique tiene un significado diferente cuando utf8mb4se usa en lugar de utf8.

La Sección 10.1.11 del Manual de referencia de MySQL 5.5 tiene más información al respecto.

Mathias Bynens
fuente
31

Tengo una solución que convertirá bases de datos y tablas ejecutando algunos comandos. También convierte todas las columnas del tipo varchar, text, tinytext, mediumtext, longtext, char. También debe hacer una copia de seguridad de su base de datos en caso de que algo se rompa.

Copie el siguiente código en un archivo llamado 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');

Reemplace todas las ocurrencias de "yourDbName" con la base de datos que desea convertir. Entonces corre:

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

Esto generará un nuevo archivo alterTables.sql, con todas las consultas que necesita para convertir la base de datos. Ejecute el siguiente comando para iniciar la conversión:

mysql -uroot < alterTables.sql

También puede adaptar esto para que se ejecute a través de múltiples bases de datos, cambiando la condición para el esquema_tabla. Por ejemplo table_schema like "wiki_%", convertirá todas las bases de datos con el prefijo de nombre wiki_. Para convertir todas las bases de datos, reemplace la condición con table_type!='SYSTEM VIEW'.

Un problema que podría surgir. Tenía algunas columnas varchar (255) en las claves mysql. Esto causa un error:

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

Si eso sucede, simplemente puede cambiar la columna para que sea más pequeña, como varchar (150), y volver a ejecutar el comando.

Tenga en cuenta : esta respuesta convierte la base de datos en utf8mb4_unicode_cilugar de la que se utf8mb4_binhace en la pregunta. Pero simplemente puedes reemplazar esto.

MrJingles87
fuente
Gran secuencia de comandos, solo unas pocas notas; Las instalaciones actuales de MiariaDb requieren que se proporcione la contraseña, por lo que mysql -uroot -pThatrootPassWord < alterTables.sqlfunciona. Y como ya notó, utf8mb4_bin es lo que, entre otros, recomienda nextcloud.
Julius
pero utf8mb4_0900_ai_ci es el valor predeterminado ahora, vea monolune.com/what-is-the-utf8mb4_0900_ai_ci-collation
Julio
Tuve que usar "SET foreign_key_checks = 0;", luego aplicar los cambios, luego "SET foreign_key_checks = 1;".
dfrankow
Gracias hombre. Esta fue LA solución en Redmin para cambiar todo a utf8mb4.
Luciano Fantuzzi
5

Usé el siguiente script de shell. Toma el nombre de la base de datos como parámetro y convierte todas las tablas a otro conjunto de caracteres y clasificación (dado por otros parámetros o valores predeterminados definidos en el 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
fuente
3

Escribiría un script (en Perl, o lo que sea) para usar información_esquema (TABLAS y COLUMNAS) para recorrer todas las tablas y MODIFICAR COLUMNA en cada campo CHAR / VARCHAR / TEXT. Recogería todos los MODIFYs en un solo ALTER para cada tabla; Esto será más eficiente.

Creo (pero no estoy seguro) que la sugerencia de Raihan solo cambia el valor predeterminado para la tabla.

Rick James
fuente
3

Me encontré con esta situación; Aquí está el enfoque que usé para convertir mi base de datos:

  1. Primero, debe editar my.cnfpara establecer una conexión de base de datos predeterminada (entre aplicaciones y MYSQL) compatible con utf8mb4_unicode_ci. Sin estos caracteres como emojis y similares enviados por sus aplicaciones, no llegarán a sus tablas en bytes / codificación correctos (a menos que los parámetros de DB CNN de su aplicación especifiquen una conexión utf8mb4).

    Instrucciones dadas aquí .

  2. Ejecute el siguiente SQL (no es necesario prepararse para cambiar columnas individuales, las ALTER TABLEdeclaraciones lo harán).

    Antes de ejecutar el siguiente código, reemplace "DbName" con su nombre de base de datos real.

    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. Recopile y guarde la salida del SQL anterior en un archivo dot sql y ejecútelo.

  4. Si obtiene un error como #1071 - Specified key was too long; max key length is 1000 bytes.el nombre de la tabla problemática, esto significa que la clave de índice en alguna columna de esa tabla (que se suponía que se convertiría en MB4) sería muy grande, por lo tanto, la columna Varchar debería ser <= 250 para que su la clave de índice tendrá un máximo de 1000 bytes. Verifique las columnas en las que tiene índices y si una de ellas es un varchar> 250 (muy probablemente 255), entonces

    • Paso 1: verifique los datos en esa columna para asegurarse de que el tamaño máximo de la cadena en esa columna sea <= 250.

      Consulta de ejemplo:

      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;
    • Paso 2: si la longitud máxima de los datos de la columna indexada <= 250, cambie la longitud de la columna a 250. Si eso no es posible, elimine el índice en esa columna

    • Paso 3: luego ejecute la consulta alter table para esa tabla nuevamente y la tabla ahora debe convertirse a utf8mb4 con éxito.

¡Salud!

Nav44
fuente
Hay una forma de usar el índice para VARCHAR largo de más de 191 caracteres. Debe tener el privilegio DBA / SUPER USUARIO para hacer: Establecer los parámetros de la base de datos: innodb_large_prefix: ON; innodb_file_format: Barracuda; innodb_file_format_max: Barracuda;
Châu Hồng Lĩnh
2

Escribí esta guía: http://hanoian.com/content/index.php/24-automate-the-converting-a-mysql-database-character-set-to-utf8mb4

Desde mi trabajo, vi que ALTERAR la base de datos y las tablas no es suficiente. Tuve que ir a cada tabla y ALTERAR cada una de las columnas text / mediumtext / varchar también.

Afortunadamente, pude escribir un script para detectar los metadatos de las bases de datos MySQL, por lo que podría recorrer las tablas y columnas y ALTERARLAS automáticamente.

Índice largo para MySQL 5.6:

Hay una cosa que debe tener el privilegio DBA / SUPER USUARIO: establecer los parámetros de la base de datos:

innodb_large_prefix: ON
innodb_file_format: Barracuda 
innodb_file_format_max: Barracuda

En las respuestas a esta pregunta, hay instrucciones sobre cómo establecer esos parámetros arriba: https://stackoverflow.com/questions/35847015/mysql-change-innodb-large-prefix

Por supuesto, en mi artículo, hay instrucciones para hacerlo también.

Para MySQL versión 5.7 o posterior , innodb_large_prefix está activado de forma predeterminada, y innodb_file_format también es Barracuda de forma predeterminada.

Châu Hồng Lĩnh
fuente
2

Para las personas que podrían tener este problema, la mejor solución es modificar primero las columnas a un tipo binario, de acuerdo con esta tabla:

  1. CHAR => BINARIO
  2. TEXTO => BLOB
  3. TINYTEXT => TINYBLOB
  4. MEDIUMTEXT => MEDIUMBLOB
  5. LONGTEXT => LONGBLOB
  6. VARCHAR => VARBINARIO

Y después de eso, modifique la columna de nuevo a su tipo anterior y con el juego de caracteres deseado.

P.ej.:

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

Lo intenté en varias tablas latin1 y mantuvo todos los diacríticos.

Puede extraer esta consulta para todas las columnas haciendo esto:

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
fuente
0

Hice un script que hace esto más o menos automáticamente:

<?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
fuente