Cara mudah mengkonversi tabel utf8 ke utf8mb4 di MySQL 5.5

71

Saya memiliki database yang sekarang perlu mendukung 4 byte karakter (Cina). Untungnya saya sudah memiliki MySQL 5.5 dalam produksi.

Jadi saya hanya ingin membuat semua koleksi yang utf8_bin menjadi utf8mb4_bin.

Saya percaya tidak ada kehilangan / perolehan kinerja dengan perubahan ini selain sedikit overhead penyimpanan.

geoaksis
sumber

Jawaban:

93

Dari panduan saya Cara mendukung Unicode penuh dalam basis data MySQL , berikut adalah kueri yang dapat Anda jalankan untuk memperbarui rangkaian karakter dan susunan basis data, tabel, atau kolom:

Untuk setiap basis data:

ALTER DATABASE
    database_name
    CHARACTER SET = utf8mb4
    COLLATE = utf8mb4_unicode_ci;

Untuk setiap tabel:

ALTER TABLE
    table_name
    CONVERT TO CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

Untuk setiap kolom:

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

(Jangan salin-tempel ini secara membabi buta! Pernyataan yang tepat tergantung pada jenis kolom, panjang maksimum, dan properti lainnya. Baris di atas hanyalah contoh untuk sebuah VARCHARkolom.)

Namun, perhatikan bahwa Anda tidak dapat mengotomatisasi sepenuhnya konversi dari utf8menjadi utf8mb4. Seperti dijelaskan pada langkah 4 dari panduan di atas , Anda harus memeriksa panjang maksimum kolom dan kunci indeks, karena angka yang Anda tentukan memiliki arti yang berbeda ketika utf8mb4digunakan sebagai gantinya utf8.

Bagian 10.1.11 dari Manual Referensi MySQL 5.5 memiliki beberapa informasi lebih lanjut tentang ini.

Mathias Bynens
sumber
31

Saya punya solusi yang akan mengonversi basis data dan tabel dengan menjalankan beberapa perintah. Hal ini juga mengkonversi semua kolom jenis varchar, text, tinytext, mediumtext, longtext, char. Anda juga harus mencadangkan basis data jika terjadi kerusakan.

Salin kode berikut ke file yang bernama 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');

Ganti semua kemunculan "yourDbName" dengan database yang ingin Anda konversi. Lalu lari:

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

Ini akan menghasilkan file baru alterTables.sql, dengan semua pertanyaan yang Anda butuhkan untuk mengonversi database. Jalankan perintah berikut untuk memulai konversi:

mysql -uroot < alterTables.sql

Anda juga dapat mengadaptasi ini untuk dijalankan melalui banyak basis data, dengan mengubah kondisi untuk table_schema. Misalnya table_schema like "wiki_%"akan mengonversi semua basis data dengan nama awalan wiki_. Untuk mengonversi semua basis data, ganti kondisinya dengan table_type!='SYSTEM VIEW'.

Suatu masalah yang mungkin muncul. Saya memiliki beberapa kolom varchar (255) di kunci mysql. Ini menyebabkan kesalahan:

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

Jika itu terjadi, Anda cukup mengubah kolom menjadi lebih kecil, seperti varchar (150), dan jalankan kembali perintah.

Harap dicatat : Jawaban ini mengubah database menjadi utf8mb4_unicode_cialih-alih utf8mb4_bin, ditanyakan dalam pertanyaan. Tetapi Anda bisa langsung mengganti ini.

MrJingles87
sumber
Skrip hebat, hanya beberapa catatan; Pemasangan MiariaDb saat ini membutuhkan kata sandi yang harus diberikan, agar mysql -uroot -pThatrootPassWord < alterTables.sqlberfungsi. Dan seperti yang sudah Anda catat, utf8mb4_bin adalah apa yang direkomendasikan oleh nextcloud.
Julius
tetapi utf8mb4_0900_ai_ci adalah default sekarang, lihat monolune.com/what-is-the-utf8mb4_0900_ai_ci-collation
Julius
Saya harus menggunakan "SET foreign_key_checks = 0;", lalu menerapkan perubahan, lalu "SET foreign_key_checks = 1;".
dfrankow
Terima kasih kawan Ini adalah solusi di Redmin untuk mengubah semua menjadi utf8mb4.
Luciano Fantuzzi
5

Saya menggunakan skrip shell berikut. Dibutuhkan nama database sebagai parameter dan mengubah semua tabel ke charset dan collation lain (diberikan oleh parameter lain atau nilai default yang didefinisikan dalam skrip).

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

Saya akan menulis skrip (dalam Perl, atau apa pun) untuk menggunakan information_schema (TABLES dan COLUMNS) untuk menelusuri semua tabel, dan melakukan MODIFY COLUMN pada setiap bidang CHAR / VARCHAR / TEXT. Saya akan mengumpulkan semua MODIFY menjadi ALTER tunggal untuk setiap tabel; ini akan lebih efisien.

Saya pikir (tapi tidak yakin) bahwa saran Raihan hanya mengubah default untuk tabel.

Rick James
sumber
3

Berlari ke dalam situasi ini; inilah pendekatan yang saya gunakan untuk mengonversi basis data saya:

  1. Pertama, Anda perlu mengedit my.cnfuntuk membuat koneksi database default (antara aplikasi dan MYSQL) sesuai utf8mb4_unicode_ci. Tanpa karakter ini seperti emoji dan yang serupa yang dikirimkan oleh aplikasi Anda tidak akan bisa masuk ke tabel Anda dalam byte / encoding yang tepat (kecuali params DB CNN aplikasi Anda menentukan koneksi utf8mb4).

    Instruksi diberikan di sini .

  2. Jalankan SQL berikut (tidak perlu bersiap-siap SQL untuk mengubah kolom individual, ALTER TABLEpernyataan akan melakukannya).

    Sebelum Anda menjalankan kode di bawah ini, ganti "DbName" dengan nama DB Anda yang sebenarnya.

    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. Kumpulkan dan simpan output dari SQL di atas dalam file dot sql dan jalankan.

  4. Jika Anda mendapatkan kesalahan seperti #1071 - Specified key was too long; max key length is 1000 bytes.bersama dengan nama tabel yang bermasalah, ini berarti kunci indeks pada beberapa kolom dari tabel itu (yang seharusnya dikonversi ke MB4 charstring) akan sangat besar sehingga kolom Varchar harus <= 250 sehingga itu kunci indeks akan menjadi maksimal 1000 byte. Periksa kolom di mana Anda memiliki indeks dan jika salah satunya adalah varchar> 250 (kemungkinan besar 255)

    • Langkah 1: periksa data di kolom itu untuk memastikan bahwa ukuran string maksimal dalam kolom itu adalah <= 250.

      Contoh permintaan:

      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;
    • Langkah 2: jika panjang karakter maksimum dari data kolom yang diindeks <= 250 kemudian ubah panjang col menjadi 250. jika itu tidak memungkinkan, hapus indeks pada kolom itu

    • Langkah 3: kemudian jalankan query tabel alter untuk tabel itu lagi dan tabel sekarang harus dikonversi menjadi utf8mb4 berhasil.

Tepuk tangan!

Nav44
sumber
Ada cara untuk menggunakan indeks untuk VARCHAR panjang lebih dari 191 karakter. Anda harus memiliki hak istimewa DBA / SUPER USER untuk melakukan: Mengatur parameter basis data: innodb_large_prefix: ON; innodb_file_format: Barracuda; innodb_file_format_max: Barracuda;
Châu Hồng Lĩnh
2

Saya menulis panduan ini: http://hanoian.com/content/index.php/24-automate-the-converting-a-mysql-database-character-set-to-utf8mb4

Dari pekerjaan saya, saya melihat bahwa ALTER database dan tabel tidak cukup. Saya harus masuk ke setiap tabel dan ALTER masing-masing kolom teks / mediumteks / varchar juga.

Untungnya saya bisa menulis skrip untuk mendeteksi metadata dari database MySQL, sehingga bisa loop melalui tabel dan kolom dan MENGUBAH mereka secara otomatis.

Indeks panjang untuk MySQL 5.6:

Ada satu hal yang harus Anda lakukan sebagai hak istimewa DBA / SUPER USER: Mengatur parameter basis data:

innodb_large_prefix: ON
innodb_file_format: Barracuda 
innodb_file_format_max: Barracuda

Dalam jawaban untuk pertanyaan ini, ada instruksi cara mengatur parameter di atas: https://stackoverflow.com/questions/35847015/mysql-change-innodb-large-prefix

Tentu saja, dalam artikel saya, ada instruksi untuk melakukannya juga.

Untuk MySQL versi 5.7 atau lebih baru , innodb_large_prefix AKTIF secara default, dan innodb_file_format juga merupakan Barracuda secara default.

Châu Hồng Lĩnh
sumber
2

Untuk orang-orang yang mungkin memiliki masalah ini solusi terbaik adalah memodifikasi terlebih dahulu kolom ke tipe biner, menurut tabel ini:

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

Dan setelah itu memodifikasi kolom kembali ke tipe sebelumnya dan dengan rangkaian karakter yang Anda inginkan.

Misalnya.:

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

Saya mencoba beberapa tabel latin1 dan menyimpan semua diakritik.

Anda dapat mengekstrak kueri ini untuk semua kolom yang melakukan ini:

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

Saya membuat skrip yang melakukan ini kurang lebih secara otomatis:

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