it-swarm.it

Come convertire facilmente le tabelle utf8 in utf8mb4 in MySQL 5.5

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.

91
geoaxis

Dalla mia guida Come supportare Unicode completo nei database MySQL , ecco le query che puoi 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 colonna VARCHAR)

Tieni presente, tuttavia, che non puoi automatizzare completamente la conversione da utf8 per utf8mb4. Come descritto in passaggio 4 della guida di cui sopra , dovrai controllare la lunghezza massima di colonne e chiavi di indice, poiché il numero specificato ha un significato diverso quando utf8mb4 viene utilizzato anziché utf8.

Sezione 10.1.11 del Manuale di riferimento di MySQL 5.5 contiene alcune informazioni in più.

106
Mathias Bynens

Ho una soluzione che convertirà database e tabelle eseguendo alcuni comandi. Converte anche tutte le colonne del tipo varchar, text, tinytext, mediumtext, longtext, char. Dovresti anche eseguire il backup del tuo 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. Per 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_ci invece di utf8mb4_bin, posto nella domanda. Ma puoi semplicemente sostituirlo.

39
MrJingles87

Ho usato il seguente script Shell. Prende il nome del database come parametro e converte tutte le tabelle in un altro set di caratteri e regole di confronto (fornite 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
)
8
Petr Stastny

Scriverei una sceneggiatura (in Perl, o qualsiasi altra cosa) per usare information_schema (TAVOLI e COLONNE) per percorrere tutte le tabelle, e 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 impostazione predefinita per la tabella.

3
Rick James

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

  1. Innanzitutto, devi modificare my.cnf per rendere la connessione al database predefinita (tra applicazioni e MYSQL) conforme a 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. Esegui il seguente SQL (non è necessario prepararsi SQL per modificare le singole colonne, ALTER TABLE dichiarazioni 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 sq dot ed eseguirlo.

  4. Se ricevi un errore come #1071 - Specified key was too long; max key length is 1000 bytes. insieme al nome della tabella problematica, questo significa che la chiave di indice su alcune colonne di quella tabella (che doveva essere convertita in charstring MB4) sarà molto grande, quindi la colonna Varchar dovrebbe essere <= 250 in modo che la sua chiave di indice sia max 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 di modifica tabella per quella tabella e la tabella ora dovrebbe essere convertita correttamente in utf8mb4.

Saluti!

3
Nav44

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

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 innodb_file_format è anche Barracuda per impostazione predefinita.

2

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