Cách dễ dàng chuyển đổi bảng utf8 thành utf8mb4 trong MySQL 5.5


71

Tôi có một cơ sở dữ liệu hiện cần hỗ trợ 4 ký tự (tiếng Trung). May mắn thay, tôi đã có MySQL 5.5 trong sản xuất.

Vì vậy, tôi chỉ muốn thực hiện tất cả các collations là utf8_bin thành utf8mb4_bin.

Tôi tin rằng không có sự mất / tăng hiệu suất với thay đổi này ngoài một chút chi phí lưu trữ.

Câu trả lời:


93

Từ hướng dẫn của tôi Cách hỗ trợ Unicode đầy đủ trong cơ sở dữ liệu MySQL , đây là các truy vấn bạn có thể chạy để cập nhật bộ ký tự và đối chiếu cơ sở dữ liệu, bảng hoặc cột:

Đối với mỗi cơ sở dữ liệu:

ALTER DATABASE
    database_name
    CHARACTER SET = utf8mb4
    COLLATE = utf8mb4_unicode_ci;

Đối với mỗi bảng:

ALTER TABLE
    table_name
    CONVERT TO CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

Đối với mỗi cột:

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

(Đừng sao chép một cách mù quáng điều này! Câu lệnh chính xác phụ thuộc vào loại cột, độ dài tối đa và các thuộc tính khác. Dòng trên chỉ là một ví dụ cho một VARCHARcột.)

Tuy nhiên, lưu ý rằng bạn không thể tự động hóa hoàn toàn việc chuyển đổi từ utf8sang utf8mb4. Như được mô tả trong bước 4 của hướng dẫn nêu trên , bạn sẽ cần kiểm tra độ dài tối đa của các cột và khóa chỉ mục, vì số bạn chỉ định có ý nghĩa khác khi utf8mb4được sử dụng thay vì utf8.

Mục 10.1.11 của Tài liệu tham khảo MySQL 5.5 có thêm một số thông tin về điều này.


31

Tôi có một giải pháp sẽ chuyển đổi cơ sở dữ liệu và bảng bằng cách chạy một vài lệnh. Nó cũng chuyển đổi tất cả các cột của các loại varchar, text, tinytext, mediumtext, longtext, char. Bạn cũng nên sao lưu cơ sở dữ liệu của mình trong trường hợp có gì đó bị hỏng.

Sao chép đoạn mã sau vào một tệp có tên là 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');

Thay thế tất cả các lần xuất hiện của "yourDbName" bằng cơ sở dữ liệu bạn muốn chuyển đổi. Sau đó chạy:

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

Điều này sẽ tạo ra một tập tin mới thay đổi, với tất cả các truy vấn bạn cần để chuyển đổi cơ sở dữ liệu. Chạy lệnh sau để bắt đầu chuyển đổi:

mysql -uroot < alterTables.sql

Bạn cũng có thể điều chỉnh điều này để chạy qua nhiều cơ sở dữ liệu, bằng cách thay đổi điều kiện cho bảng_schema. Ví dụ table_schema like "wiki_%"sẽ chuyển đổi tất cả các cơ sở dữ liệu với tiền tố tên wiki_. Để chuyển đổi tất cả các cơ sở dữ liệu thay thế điều kiện với table_type!='SYSTEM VIEW'.

Một vấn đề có thể phát sinh. Tôi đã có một số cột varchar (255) trong các khóa mysql. Điều này gây ra lỗi:

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

Nếu điều đó xảy ra, bạn có thể chỉ cần thay đổi cột thành nhỏ hơn, như varchar (150) và chạy lại lệnh.

Xin lưu ý : Câu trả lời này chuyển đổi cơ sở dữ liệu thành utf8mb4_unicode_cithay vì utf8mb4_bin, được hỏi trong câu hỏi. Nhưng bạn chỉ có thể thay thế điều này.


Kịch bản tuyệt vời, chỉ cần một vài ghi chú; Các cài đặt MiariaDb hiện tại yêu cầu mật khẩu được cung cấp, do đó mysql -uroot -pThatrootPassWord < alterTables.sqlhoạt động. Và như bạn đã lưu ý, utf8mb4_bin là những gì, trong số những người khác, nextcloud khuyến nghị.
Julius

nhưng utf8mb4_0900_ai_ci là mặc định ngay bây giờ, xem monolune.com/what-is-the-utf8mb4_0900_ai_ci-collation
Julius

Tôi đã phải sử dụng "SET Foreign_key_checks = 0;", sau đó áp dụng các thay đổi, sau đó "SET Foreign_key_checks = 1;".
dfrankow

Cảm ơn bạn. Đây là giải pháp trong Redmin để thay đổi tất cả thành utf8mb4.
Luciano Fantuzzi

5

Tôi đã sử dụng kịch bản shell sau đây. Nó lấy tên cơ sở dữ liệu làm tham số và chuyển đổi tất cả các bảng thành bộ ký tự và đối chiếu khác (được cung cấp bởi một tham số khác hoặc giá trị mặc định được xác định trong tập lệnh).

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

Tôi sẽ viết một tập lệnh (bằng Perl, hoặc bất cứ điều gì) để sử dụng information_schema (TABLES và COLUMNS) để đi qua tất cả các bảng và thực hiện MODIFY COLUMN trên mỗi trường CHAR / VARCHAR / TEXT. Tôi sẽ thu thập tất cả các MODIFY thành một ALTER duy nhất cho mỗi bảng; Điều này sẽ hiệu quả hơn.

Tôi nghĩ (nhưng không chắc chắn) rằng đề xuất của Raihan chỉ thay đổi mặc định cho bảng.


3

Chạy vào tình huống này; Đây là cách tiếp cận tôi đã sử dụng để chuyển đổi cơ sở dữ liệu của mình:

  1. Trước tiên, bạn cần chỉnh sửa my.cnfđể thực hiện kết nối cơ sở dữ liệu mặc định (giữa các ứng dụng và MYSQL) tuân thủ utf8mb4_unicode_ci. Nếu không có các ký tự này như biểu tượng cảm xúc và các ứng dụng tương tự được gửi bởi các ứng dụng của bạn sẽ không đưa nó vào bảng của bạn theo byte / mã hóa đúng (trừ khi thông số DB CNN của ứng dụng chỉ định kết nối utf8mb4).

    Hướng dẫn đưa ra ở đây .

  2. Thực thi SQL sau (không cần phải chuẩn bị SQL để thay đổi các cột riêng lẻ, các ALTER TABLEcâu lệnh sẽ làm điều đó).

    Trước khi bạn thực thi mã bên dưới, thay thế "DbName" bằng tên DB thực tế của bạn.

    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. Thu thập và lưu kết quả đầu ra của SQL ở trên trong một tệp sql dot và thực hiện nó.

  4. Nếu bạn gặp lỗi như #1071 - Specified key was too long; max key length is 1000 bytes.cùng với tên bảng có vấn đề, điều này có nghĩa là khóa chỉ mục trên một số cột của bảng đó (được cho là đã được chuyển đổi thành chuỗi MB4) sẽ rất lớn do đó cột Varchar phải <= 250 sao cho cột của nó khóa chỉ mục sẽ tối đa 1000 byte. Kiểm tra các cột mà bạn có các chỉ mục và nếu một trong số chúng là varchar> 250 (rất có thể là 255) thì

    • Bước 1: kiểm tra dữ liệu trong cột đó để đảm bảo rằng kích thước chuỗi tối đa trong cột đó là <= 250.

      Ví dụ truy vấn:

      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;
      
    • Bước 2: nếu tối đa hóa dữ liệu cột được lập chỉ mục <= 250 thì thay đổi độ dài col thành 250. nếu không thể, hãy xóa chỉ mục trên cột đó

    • Bước 3: sau đó chạy lại truy vấn bảng thay đổi cho bảng đó và bây giờ bảng sẽ được chuyển đổi thành utf8mb4 thành công.

Chúc mừng!


Có một cách để sử dụng chỉ mục cho VARCHAR dài trên 191 ký tự. Bạn phải có đặc quyền DBA / SUPER USER để thực hiện: Đặt tham số cơ sở dữ liệu: innodb_large_prefix: ON; innodb_file_format: Barracuda; innodb_file_format_max: Barracuda;
Châu Hồng Lĩnh

2

Tôi đã viết hướng dẫn này: http://hanoian.com/content/index.php/24-automate-the-converting-a-mysql-database-character-set-to-utf8mb4

Từ công việc của tôi, tôi thấy rằng THAY ĐỔI cơ sở dữ liệu và các bảng là không đủ. Tôi đã phải đi vào từng bảng và THAY ĐỔI từng cột văn bản / trung bình / varchar.

May mắn thay, tôi đã có thể viết một tập lệnh để phát hiện siêu dữ liệu của cơ sở dữ liệu MySQL, vì vậy nó có thể lặp qua các bảng và cột và thay đổi chúng một cách tự động.

Chỉ mục dài cho MySQL 5.6:

Có một điều bạn phải có đặc quyền DBA / SUPER USER để làm: Đặt tham số cơ sở dữ liệu:

innodb_large_prefix: BẬT
innodb_file_format: Barracuda 
innodb_file_format_max: Barracuda

Trong các câu trả lời cho câu hỏi này, có hướng dẫn cách đặt các tham số trên: https://stackoverflow.com/questions353847015/mysql-change-innodb-large-prefix

Tất nhiên, trong bài viết của tôi, có hướng dẫn để làm điều đó quá.

Đối với phiên bản MySQL 5.7 hoặc mới hơn , innodb_large_prefix được BẬT theo mặc định và innodb_file_format cũng là Barracuda theo mặc định.


2

Đối với những người có thể gặp vấn đề này, giải pháp tốt nhất là sửa đổi các cột đầu tiên thành loại nhị phân, theo bảng này:

  1. CHAR => BINary
  2. VĂN => BLOB
  3. TINYTEXT => TINYBLOB
  4. TRUNG TÂM => MEDIUMBLOB
  5. LONGTEXT => LONGBLOB
  6. VARCHAR => BIỂU TƯỢNG

Và sau đó sửa đổi cột trở lại kiểu cũ và với bộ ký tự mong muốn của bạn.

Ví dụ.:

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

Tôi đã thử trong một số bảng latin1 và nó giữ tất cả các dấu phụ.

Bạn có thể trích xuất truy vấn này cho tất cả các cột thực hiện việc này:

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

Tôi đã tạo một kịch bản thực hiện điều này ít nhiều tự động:

<?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";
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.