So konvertieren Sie einfach utf8-Tabellen in utf8mb4 in MySQL 5.5

71

Ich habe eine Datenbank, die jetzt 4-Byte-Zeichen (Chinesisch) unterstützen muss. Zum Glück habe ich MySQL 5.5 bereits in der Produktion.

Ich möchte also nur alle Kollatierungen machen, die utf8_bin bis utf8mb4_bin sind.

Ich glaube, dass es mit dieser Änderung keinen Leistungsverlust / -gewinn gibt, außer ein wenig Speicheraufwand.

Geoaxis
quelle

Antworten:

93

Aus meinem Handbuch So unterstützen Sie vollständigen Unicode in MySQL-Datenbanken : Hier sind die Abfragen, die Sie ausführen können, um den Zeichensatz und die Sortierung einer Datenbank, einer Tabelle oder einer Spalte zu aktualisieren:

Für jede Datenbank:

ALTER DATABASE
    database_name
    CHARACTER SET = utf8mb4
    COLLATE = utf8mb4_unicode_ci;

Für jeden Tisch:

ALTER TABLE
    table_name
    CONVERT TO CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

Für jede Spalte:

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

(Kopieren Sie dies nicht blind, sondern fügen Sie es ein. Die genaue Angabe hängt vom Spaltentyp, der maximalen Länge und anderen Eigenschaften ab. Die obige Zeile ist nur ein Beispiel für eine VARCHARSpalte.)

Beachten Sie jedoch, dass Sie die Konvertierung von utf8nach nicht vollständig automatisieren können utf8mb4. Wie in Schritt 4 des oben genannten Handbuchs beschrieben , müssen Sie die maximale Länge von Spalten und Indexschlüsseln überprüfen, da die von Ihnen angegebene Nummer eine andere Bedeutung hat, wenn utf8mb4sie anstelle von verwendet wird utf8.

In Abschnitt 10.1.11 des MySQL 5.5-Referenzhandbuchs finden Sie hierzu weitere Informationen.

Mathias Bynens
quelle
31

Ich habe eine Lösung, die Datenbanken und Tabellen durch Ausführen einiger Befehle konvertiert. Es wandelt auch alle Spalten des Typs varchar, text, tinytext, mediumtext, longtext, char. Sie sollten auch Ihre Datenbank sichern, falls etwas kaputt geht.

Kopieren Sie den folgenden Code in eine Datei mit dem Namen 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');

Ersetzen Sie alle Vorkommen von "yourDbName" durch die Datenbank, die Sie konvertieren möchten. Dann renne:

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

Dadurch wird eine neue Datei alterTables.sql mit allen Abfragen generiert, die Sie zum Konvertieren der Datenbank benötigen. Führen Sie den folgenden Befehl aus, um die Konvertierung zu starten:

mysql -uroot < alterTables.sql

Sie können dies auch anpassen, um mehrere Datenbanken zu durchlaufen, indem Sie die Bedingung für table_schema ändern. Zum Beispiel table_schema like "wiki_%"werden alle Datenbanken mit dem Namenspräfix konvertiert wiki_. Um alle Datenbanken zu konvertieren, ersetzen Sie die Bedingung durch table_type!='SYSTEM VIEW'.

Ein Problem, das auftreten könnte. Ich hatte einige varchar (255) Spalten in MySQL-Schlüsseln. Dies verursacht einen Fehler:

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

In diesem Fall können Sie einfach die Spalte wie varchar (150) verkleinern und den Befehl erneut ausführen.

Bitte beachten Sie : Diese Antwort konvertiert die Datenbank in, utf8mb4_unicode_cianstatt utf8mb4_bin, in der Frage gestellt. Aber Sie können dies einfach ersetzen.

MrJingles87
quelle
Großartiges Scripting, nur ein paar Notizen; Bei aktuellen MiariaDb-Installationen muss das Kennwort angegeben werden mysql -uroot -pThatrootPassWord < alterTables.sql. Und wie Sie bereits bemerkt haben, ist utf8mb4_bin unter anderem das, was nextcloud empfiehlt.
Julius
aber utf8mb4_0900_ai_ci ist der Standard jetzt finden monolune.com/what-is-the-utf8mb4_0900_ai_ci-collation
Julius
Ich musste "SET foreign_key_checks = 0;" verwenden, dann die Änderungen anwenden, dann "SET foreign_key_checks = 1;".
15.
Danke mann. Dies war DIE Lösung in Redmin, um alle auf utf8mb4 umzustellen.
Luciano Fantuzzi
5

Ich habe das folgende Shell-Skript verwendet. Es verwendet den Datenbanknamen als Parameter und konvertiert alle Tabellen in einen anderen Zeichensatz und eine andere Sortierung (angegeben durch einen anderen Parameter oder einen im Skript definierten Standardwert).

#!/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
quelle
3

Ich würde ein Skript (in Perl oder was auch immer) schreiben, um information_schema (TABLES und COLUMNS) zu verwenden, um durch alle Tabellen zu gehen, und MODIFY COLUMN für jedes CHAR / VARCHAR / TEXT-Feld ausführen. Ich würde alle MODIFYs in einem einzigen ALTER für jede Tabelle sammeln; Dies wird effizienter sein.

Ich denke (bin mir aber nicht sicher), dass Raihans Vorschlag nur die Standardeinstellung für die Tabelle ändert .

Rick James
quelle
3

Ist in diese Situation geraten; Hier ist der Ansatz, mit dem ich meine Datenbank konvertiert habe:

  1. Zuerst müssen Sie bearbeiten my.cnf, um die Standarddatenbankverbindung (zwischen Anwendungen und MYSQL) utf8mb4_unicode_ci kompatibel zu machen. Ohne diese Zeichen wie Emojis und ähnliche, die von Ihren Apps gesendet werden, gelangen nicht in richtigen Bytes / Codierung in Ihre Tabellen (es sei denn, die DB CNN-Parameter Ihrer Anwendung geben eine utf8mb4-Verbindung an).

    Anweisungen hier gegeben .

  2. Führen Sie die folgenden SQL- ALTER TABLEAnweisungen aus (SQL- Anweisungen müssen nicht vorbereitet werden, um einzelne Spalten zu ändern .)

    Bevor Sie den folgenden Code ausführen, ersetzen Sie "DbName" durch Ihren tatsächlichen DB-Namen.

    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. Sammle und speichere die Ausgabe von oben genanntem SQL in einer Dot-SQL-Datei und führe sie aus.

  4. Wenn Sie einen Fehler wie #1071 - Specified key was too long; max key length is 1000 bytes.zusammen mit dem problematischen Tabellennamen erhalten, bedeutet dies, dass der Indexschlüssel für eine Spalte dieser Tabelle (die in MB4-Zeichenfolge konvertiert werden sollte) sehr groß ist, daher sollte die Varchar-Spalte <= 250 sein Der Indexschlüssel ist maximal 1000 Byte groß. Überprüfen Sie die Spalten, für die Sie Indizes haben, und wenn eine von ihnen ein varchar> 250 (höchstwahrscheinlich 255) ist, dann

    • Schritt 1: Überprüfen Sie die Daten in dieser Spalte, um sicherzustellen, dass die maximale Zeichenfolgengröße in dieser Spalte <= 250 ist.

      Beispielabfrage:

      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;
    • Schritt 2: Wenn die maximale Länge der indizierten Spaltendaten <= 250 ist, ändern Sie die Spaltenlänge in 250. Wenn dies nicht möglich ist, entfernen Sie den Index für diese Spalte

    • Schritt 3: Führen Sie anschließend die Abfrage alter table für diese Tabelle erneut aus. Die Tabelle sollte nun erfolgreich in utf8mb4 konvertiert werden.

Prost!

Nav44
quelle
Es gibt eine Möglichkeit, den Index für lange VARCHAR-Werte von mehr als 191 Zeichen zu verwenden. Sie müssen über die Berechtigung DBA / SUPER USER verfügen, um Folgendes auszuführen: Festlegen der Datenbankparameter: innodb_large_prefix: ON; innodb_file_format: Barracuda; innodb_file_format_max: Barracuda;
Châu Hồng Lĩnh
2

Ich habe dieses Handbuch geschrieben: http://hanoian.com/content/index.php/24-automate-the-converting-a-mysql-database-character-set-to-utf8mb4

Bei meiner Arbeit habe ich gesehen, dass ALTER die Datenbank und die Tabellen nicht ausreichen. Ich musste in jede Tabelle gehen und auch jede der Spalten text / mediumtext / varchar ÄNDERN.

Zum Glück konnte ich ein Skript schreiben, um die Metadaten von MySQL-Datenbanken zu erkennen, sodass die Tabellen und Spalten durchlaufen und automatisch geändert werden konnten.

Langer Index für MySQL 5.6:

Sie müssen über die Berechtigung DBA / SUPER USER verfügen, um Folgendes zu tun: Festlegen der Datenbankparameter:

innodb_large_prefix: ON
innodb_file_format: Barracuda 
innodb_file_format_max: Barracuda

In den Antworten auf diese Frage finden Sie Anweisungen zum Festlegen dieser Parameter: https://stackoverflow.com/questions/35847015/mysql-change-innodb-large-prefix

Natürlich gibt es in meinem Artikel auch Anweisungen dazu.

Ab MySQL-Version 5.7 ist innodb_large_prefix standardmäßig aktiviert, und innodb_file_format ist ebenfalls standardmäßig Barracuda.

Châu Hồng Lĩnh
quelle
2

Für Leute, die dieses Problem haben könnten, ist die beste Lösung, zuerst die Spalten in einen binären Typ zu ändern, entsprechend dieser Tabelle:

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

Anschließend ändern Sie die Spalte wieder auf den vorherigen Typ und geben den gewünschten Zeichensatz ein.

Z.B.:

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

Ich habe es in mehreren lateinischen Tischen versucht und es hat alle diakritischen Punkte bewahrt.

Sie können diese Abfrage für alle Spalten extrahieren, die dies tun:

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

Ich habe ein Skript erstellt, das dies mehr oder weniger automatisch ausführt:

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