Come convertire facilmente le tabelle utf8 in utf8mb4 in MySQL 5.5


71

Ho un database che ora deve supportare caratteri a 4 byte (cinese). Fortunatamente ho già MySQL 5.5 in produzione.

Quindi vorrei solo fare tutte le regole di confronto che sono utf8_bin in utf8mb4_bin.

Credo che non ci siano perdite / guadagni in termini di prestazioni con questa modifica se non un sovraccarico di memoria.

Risposte:


93

Dalla mia guida Come supportare Unicode completo nei database MySQL , ecco le query che è possibile eseguire per aggiornare il set di caratteri e le regole di confronto di un database, una tabella o una colonna:

Per ogni database:

ALTER DATABASE
    database_name
    CHARACTER SET = utf8mb4
    COLLATE = utf8mb4_unicode_ci;

Per ogni tavolo:

ALTER TABLE
    table_name
    CONVERT TO CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

Per ogni colonna:

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

(Non copiare e incollare ciecamente questo! L'istruzione esatta dipende dal tipo di colonna, dalla lunghezza massima e da altre proprietà. La riga sopra è solo un esempio per una VARCHARcolonna.)

Si noti, tuttavia, che non è possibile automatizzare completamente la conversione da utf8a utf8mb4. Come descritto nel passaggio 4 della guida di cui sopra , è necessario verificare la lunghezza massima di colonne e chiavi di indice, poiché il numero specificato ha un significato diverso quando utf8mb4viene utilizzato anziché utf8.

La Sezione 10.1.11 del Manuale di riferimento di MySQL 5.5 contiene ulteriori informazioni al riguardo.


31

Ho una soluzione che convertirà database e tabelle eseguendo alcuni comandi. Inoltre converte tutte le colonne di tipo varchar, text, tinytext, mediumtext, longtext, char. È inoltre necessario eseguire il backup del database in caso di problemi.

Copia il seguente codice in un file chiamato 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');

Sostituisci tutte le occorrenze di "yourDbName" con il database che desideri convertire. Quindi eseguire:

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

Ciò genererà un nuovo file alterTables.sql, con tutte le query necessarie per convertire il database. Eseguire il comando seguente per avviare la conversione:

mysql -uroot < alterTables.sql

È inoltre possibile adattarlo per l'esecuzione su più database, modificando la condizione per table_schema. Ad esempio table_schema like "wiki_%", convertirà tutti i database con il prefisso del nome wiki_. Per convertire tutti i database, sostituire la condizione con table_type!='SYSTEM VIEW'.

Un problema che potrebbe sorgere. Avevo alcune colonne varchar (255) nelle chiavi mysql. Ciò provoca un errore:

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

Se ciò accade, puoi semplicemente modificare la colonna in modo che sia più piccola, come varchar (150), ed eseguire nuovamente il comando.

Nota : questa risposta converte il database in utf8mb4_unicode_ciinvece di utf8mb4_bin, posto nella domanda. Ma puoi semplicemente sostituirlo.


Ottima sceneggiatura, solo alcune note; Le installazioni MiariaDb attuali richiedono la password, quindi mysql -uroot -pThatrootPassWord < alterTables.sqlfunziona. E come hai già notato, utf8mb4_bin è ciò che, tra l'altro, nextcloud consiglia.
Giulio

ma utf8mb4_0900_ai_ci è l'impostazione predefinita ora, vedi monolune.com/what-is-the-utf8mb4_0900_ai_ci-collation
Julius

Ho dovuto usare "SET foreign_key_checks = 0;", quindi applicare le modifiche, quindi "SET foreign_key_checks = 1;".
domenica

Grazie amico. Questa era LA soluzione in Redmin per cambiare tutto in utf8mb4.
Luciano Fantuzzi,

5

Ho usato il seguente script di shell. Prende il nome del database come parametro e converte tutte le tabelle in un altro set di caratteri e regole di confronto (dato da altri parametri o valore predefinito definito nello 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
)

3

Scriverei una sceneggiatura (in Perl, o qualsiasi altra cosa) per usare information_schema (TAVOLI e COLONNE) per percorrere tutte le tabelle, e fare MODIFICARE COLONNA su ogni campo CHAR / VARCHAR / TEXT. Raccoglierei tutti i MODIFICATI in un unico ALTER per ogni tavolo; questo sarà più efficiente.

Penso (ma non sono sicuro) che il suggerimento di Raihan cambi solo il valore predefinito per la tabella.


3

Sono corso in questa situazione; ecco l'approccio che ho usato per convertire il mio database:

  1. Innanzitutto, è necessario modificare my.cnfper rendere conforme la connessione al database predefinita (tra applicazioni e MYSQL) utf8mb4_unicode_ci. Senza questi caratteri come emoji e simili inviati dalle tue app non arriverai alle tue tabelle con byte / codifica corretti (a meno che i parametri DB CNN dell'applicazione non specifichino una connessione utf8mb4).

    Istruzioni fornite qui .

  2. Eseguire il seguente SQL (non è necessario prepararsi all'SQL per modificare le singole colonne, le ALTER TABLEistruzioni lo faranno).

    Prima di eseguire il codice seguente, sostituire "DbName" con il nome del DB effettivo.

    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. Raccogliere e salvare l'output di SQL sopra in un file dot sql ed eseguirlo.

  4. Se viene visualizzato un errore simile #1071 - Specified key was too long; max key length is 1000 bytes.al nome della tabella problematica, ciò significa che la chiave di indice su alcune colonne di quella tabella (che avrebbe dovuto essere convertita in charstring MB4) sarà molto grande, quindi che la colonna Varchar dovrebbe essere <= 250 in modo che la sua la chiave di indice sarà di massimo 1000 byte. Controlla le colonne su cui hai gli indici e se uno di questi è un varchar> 250 (molto probabilmente 255), allora

    • Passaggio 1: controllare i dati in quella colonna per assicurarsi che la dimensione massima della stringa in quella colonna sia <= 250.

      Quesito di esempio:

      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;
    • Passaggio 2: se la lunghezza massima dei dati della colonna indicizzata <= 250, modificare la lunghezza del col su 250. Se ciò non è possibile, rimuovere l'indice su quella colonna

    • Passaggio 3: quindi eseguire nuovamente la query alter table per quella tabella e la tabella ora dovrebbe essere convertita correttamente in utf8mb4.

Saluti!


Esiste un modo per utilizzare l'indice per VARCHAR lungo oltre 191 caratteri. È necessario disporre del privilegio DBA / SUPER USER per eseguire: Impostazione dei parametri del database: innodb_large_prefix: ON; innodb_file_format: Barracuda; innodb_file_format_max: Barracuda;
Châu Hồng Lĩnh,

2

Ho scritto questa guida: http://hanoian.com/content/index.php/24-automate-the-converting-a-mysql-database-character-set-to-utf8mb4

Dal mio lavoro, ho visto che ALTER il database e le tabelle non sono sufficienti. Ho dovuto andare in ogni tabella e ALTER anche ognuna delle colonne text / mediumtext / varchar.

Fortunatamente sono stato in grado di scrivere uno script per rilevare i metadati dei database MySQL, in modo da poter scorrere ciclicamente le tabelle e le colonne e modificarli automaticamente.

Indice lungo per MySQL 5.6:

È necessario disporre del privilegio DBA / SUPER USER: Impostazione dei parametri del database:

innodb_large_prefix: ON
innodb_file_format: Barracuda 
innodb_file_format_max: Barracuda

Nelle risposte a questa domanda, ci sono istruzioni su come impostare questi parametri sopra: https://stackoverflow.com/questions/35847015/mysql-change-innodb-large-prefix

Naturalmente, nel mio articolo, ci sono anche istruzioni per farlo.

Per MySQL versione 5.7 o successive , innodb_large_prefix è ON per impostazione predefinita e anche innodb_file_format è Barracuda per impostazione predefinita.


2

Per le persone che potrebbero avere questo problema, la soluzione migliore è modificare prima le colonne in un tipo binario, secondo questa tabella:

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

E successivamente modifica la colonna al suo precedente tipo e con il set di caratteri desiderato.

Per esempio.:

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

Ho provato in diverse tabelle latin1 e ha mantenuto tutti i segni diacritici.

È possibile estrarre questa query per tutte le colonne in questo modo:

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

Ho realizzato uno script che lo fa più o meno automaticamente:

<?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";
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.