วิธีการแปลงตาราง utf8 เป็น utf8mb4 ใน MySQL 5.5 ได้อย่างง่ายดาย


71

ฉันมีฐานข้อมูลซึ่งตอนนี้ต้องรองรับอักขระ 4 ไบต์ (ภาษาจีน) โชคดีที่ฉันมี MySQL 5.5 ในการผลิตอยู่แล้ว

ดังนั้นฉันอยากจะทำการเปรียบเทียบทั้งหมดซึ่งเป็น utf8_bin ถึง utf8mb4_bin

ฉันเชื่อว่าไม่มีการสูญเสีย / กำไรจากการเปลี่ยนแปลงนี้นอกจากการใช้พื้นที่จัดเก็บเล็กน้อย

คำตอบ:


93

จากคำแนะนำของฉันวิธีสนับสนุน Unicode แบบเต็มในฐานข้อมูล MySQL ต่อไปนี้เป็นแบบสอบถามที่คุณสามารถเรียกใช้เพื่ออัปเดตชุดอักขระและการเรียงของฐานข้อมูลตารางหรือคอลัมน์:

สำหรับแต่ละฐานข้อมูล:

ALTER DATABASE
    database_name
    CHARACTER SET = utf8mb4
    COLLATE = utf8mb4_unicode_ci;

สำหรับแต่ละตาราง:

ALTER TABLE
    table_name
    CONVERT TO CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

สำหรับแต่ละคอลัมน์:

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

(อย่าคัดลอกวางอย่างเด็ดขาดคำสั่งนี้ขึ้นอยู่กับประเภทคอลัมน์ความยาวสูงสุดและคุณสมบัติอื่น ๆ บรรทัดด้านบนเป็นเพียงตัวอย่างสำหรับVARCHARคอลัมน์)

แต่โปรดทราบว่าคุณไม่สามารถได้อย่างเต็มที่โดยอัตโนมัติแปลงจากไปutf8 utf8mb4ตามที่อธิบายไว้ในขั้นตอนที่ 4 ของคู่มือดังกล่าวข้างต้นคุณจะต้องตรวจสอบความยาวสูงสุดของคอลัมน์และดัชนีคีย์เป็นหมายเลขที่คุณระบุมีความหมายที่แตกต่างกันเมื่อใช้แทนutf8mb4utf8

ส่วนที่ 10.1.11 ของคู่มืออ้างอิง MySQL 5.5มีข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้


31

ฉันมีทางออกที่จะแปลงฐานข้อมูลและตารางโดยใช้คำสั่งไม่กี่คำ นอกจากนี้ยังแปลงคอลัมน์ทุกชนิดvarchar, text, tinytext, mediumtext, ,longtext charคุณควรสำรองฐานข้อมูลของคุณในกรณีที่มีข้อผิดพลาดเกิดขึ้น

คัดลอกรหัสต่อไปนี้ลงในไฟล์ชื่อ 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');

แทนที่ "yourDbName" ที่เกิดขึ้นทั้งหมดด้วยฐานข้อมูลที่คุณต้องการแปลง จากนั้นเรียกใช้:

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

สิ่งนี้จะสร้างไฟล์ใหม่ alterTables.sql พร้อมกับเคียวรีทั้งหมดที่คุณต้องการแปลงฐานข้อมูล เรียกใช้คำสั่งต่อไปนี้เพื่อเริ่มการแปลง:

mysql -uroot < alterTables.sql

คุณยังสามารถปรับใช้สิ่งนี้เพื่อทำงานผ่านหลายฐานข้อมูลโดยการเปลี่ยนเงื่อนไขสำหรับ table_schema ยกตัวอย่างเช่นจะแปลงฐานข้อมูลทั้งหมดที่มีคำนำหน้าชื่อtable_schema like "wiki_%" การแปลงฐานข้อมูลทั้งหมดเปลี่ยนสภาพด้วยwiki_table_type!='SYSTEM VIEW'

ปัญหาที่อาจเกิดขึ้น ฉันมี varchar (255) คอลัมน์ในคีย์ mysql สิ่งนี้ทำให้เกิดข้อผิดพลาด:

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

หากสิ่งนั้นเกิดขึ้นคุณสามารถเปลี่ยนคอลัมน์ให้เล็กลงเช่น varchar (150) และรันคำสั่งอีกครั้ง

โปรดทราบ : คำตอบนี้แปลงฐานข้อมูลเป็นutf8mb4_unicode_ciแทนที่จะutf8mb4_binถามในคำถาม แต่คุณสามารถแทนที่สิ่งนี้ได้


การเขียนสคริปต์ที่ยอดเยี่ยมมีเพียงไม่กี่ข้อเท่านั้น ปัจจุบัน MiariaDb ติดตั้งต้องใช้รหัสผ่านที่จะได้รับเพื่อให้mysql -uroot -pThatrootPassWord < alterTables.sqlงาน และตามที่คุณได้กล่าวมาแล้ว utf8mb4_bin คือสิ่งที่กลุ่มคนอื่น ๆ แนะนำต่อไปขอแนะนำ
Julius

แต่ utf8mb4_0900_ai_ci เป็นค่าเริ่มต้นตอนนี้ดูmonolune.com/what-is-the-utf8mb4_0900_ai_ci-collation
Julius

ฉันต้องใช้ "SET foreign_key_checks = 0;" จากนั้นใช้การเปลี่ยนแปลงแล้ว "SET foreign_key_checks = 1;"
dfrankow

ขอบคุณชาย นี่คือโซลูชันใน Redmin เพื่อเปลี่ยนเป็น utf8mb4 ทั้งหมด
Luciano Fantuzzi

5

ฉันใช้เชลล์สคริปต์ต่อไปนี้ ใช้ชื่อฐานข้อมูลเป็นพารามิเตอร์และแปลงตารางทั้งหมดเป็นชุดอักขระและการเปรียบเทียบอื่น (กำหนดโดยพารามิเตอร์อื่นหรือค่าเริ่มต้นที่กำหนดไว้ในสคริปต์)

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

ฉันจะเขียนสคริปต์ (ใน Perl หรืออะไรก็ตาม) เพื่อใช้ information_schema (TABLES และคอลัมน์) เพื่อเดินผ่านตารางทั้งหมดและทำคอลัมน์แก้ไขในทุก ๆ CHAR / VARCHAR / TEXT ฉันจะรวบรวม MODIFY ทั้งหมดเป็น ALTER เดียวสำหรับแต่ละตาราง สิ่งนี้จะมีประสิทธิภาพมากขึ้น

ฉันคิดว่า (แต่ไม่แน่ใจ) ว่าคำแนะนำของ Raihan เปลี่ยนค่าเริ่มต้นสำหรับตารางเท่านั้น


3

วิ่งเข้าไปในสถานการณ์นี้; นี่คือวิธีที่ฉันใช้ในการแปลงฐานข้อมูลของฉัน:

  1. ก่อนอื่นคุณต้องแก้ไขmy.cnfเพื่อให้การเชื่อมต่อฐานข้อมูลเริ่มต้น (ระหว่างแอปพลิเคชันและ MYSQL) utf8mb4_unicode_ci เป็นไปตาม หากไม่มีตัวอักษรนี้เช่นอิโมจิและแอพที่คล้ายกันที่ส่งมาจากแอปของคุณจะไม่ทำให้ตารางของคุณเป็นไบต์ / เข้ารหัส (ยกเว้นว่าพารามิเตอร์ CN CNN ของแอปพลิเคชันของคุณระบุการเชื่อมต่อ utf8mb4)

    คำแนะนำที่นี่

  2. ดำเนินการ SQL ต่อไปนี้ (ไม่จำเป็นต้องเตรียม SQL ให้พร้อมเพื่อเปลี่ยนคอลัมน์แต่ละอันALTER TABLEงบจะทำเช่นนั้น)

    ก่อนที่คุณจะเรียกใช้รหัสด้านล่างให้แทนที่ "DbName" ด้วยชื่อฐานข้อมูลจริงของคุณ

    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. รวบรวมและบันทึกผลลัพธ์ของ SQL ข้างต้นในไฟล์ dot sql และเรียกใช้งาน

  4. หากคุณได้รับข้อผิดพลาดเช่นเดียว#1071 - Specified key was too long; max key length is 1000 bytes.กับชื่อตารางที่มีปัญหานี่หมายถึงคีย์ดัชนีในคอลัมน์ของตารางนั้น (ซึ่งควรจะถูกแปลงเป็น MB4 charstring) จะมีขนาดใหญ่มากดังนั้นคอลัมน์ Varchar ควรเป็น <= 250 เพื่อให้มัน รหัสดัชนีจะสูงสุด 1,000 ไบต์ ตรวจสอบคอลัมน์ที่คุณมีดัชนีและถ้าหนึ่งในนั้นคือ varchar> 250 (เป็นไปได้มากที่สุด 255)

    • ขั้นตอนที่ 1: ตรวจสอบข้อมูลในคอลัมน์นั้นเพื่อให้แน่ใจว่าขนาดสตริงสูงสุดในคอลัมน์นั้นคือ <= 250

      ตัวอย่างแบบสอบถาม:

      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;
    • ขั้นตอนที่ 2: ถ้าความยาวสูงสุดของข้อมูลคอลัมน์ที่มีการจัดทำดัชนี <= 250 จากนั้นเปลี่ยนความยาวคอลัมน์เป็น 250 หากไม่สามารถทำได้ให้ลบดัชนีในคอลัมน์นั้น

    • ขั้นตอนที่ 3: จากนั้นเรียกใช้คิวรีตารางแก้ไขสำหรับตารางนั้นอีกครั้งและตอนนี้ตารางควรจะถูกแปลงเป็น utf8mb4 สำเร็จแล้ว

ไชโย!


มีวิธีใช้ดัชนีสำหรับ VARCHAR ที่ยาวกว่า 191 ตัวอักษร คุณต้องมีสิทธิ์ DBA / SUPER USER ที่ต้องทำ: การตั้งค่าพารามิเตอร์ฐานข้อมูล: innodb_large_prefix: ON; innodb_file_format: Barracuda; innodb_file_format_max: Barracuda;
ChâuHồngLĩnh

2

ฉันเขียนคู่มือนี้: http://hanoian.com/content/index.php/24-automate-the-converting-a-mysql-database-character-set-to-utf8mb4

จากงานของฉันฉันเห็นว่าแก้ไขฐานข้อมูลและตารางไม่เพียงพอ ฉันต้องเข้าไปในแต่ละตารางและแก้ไขแต่ละคอลัมน์ text / Mediumtext / varchar เช่นกัน

โชคดีที่ฉันสามารถเขียนสคริปต์เพื่อตรวจสอบเมตาดาต้าของฐานข้อมูล MySQL ดังนั้นมันสามารถวนซ้ำผ่านตารางและคอลัมน์และแก้ไขโดยอัตโนมัติ

ดัชนีแบบยาวสำหรับ MySQL 5.6:

มีสิ่งหนึ่งที่คุณต้องมีสิทธิ์ DBA / SUPER USER ที่ต้องทำ: การตั้งค่าพารามิเตอร์ฐานข้อมูล:

innodb_large_prefix: ON
innodb_file_format: Barracuda 
innodb_file_format_max: Barracuda

ในคำตอบสำหรับคำถามนี้มีคำแนะนำวิธีตั้งค่าพารามิเตอร์ข้างต้น: https://stackoverflow.com/questions/35847015/mysql-change-innodb-large-prefix

แน่นอนในบทความของฉันมีคำแนะนำให้ทำเช่นกัน

สำหรับ MySQL เวอร์ชัน 5.7 หรือใหม่กว่านั้น innodb_large_prefix จะเป็น ON โดยค่าเริ่มต้นและ innodb_file_format ก็เป็น Barracuda เช่นกัน


2

สำหรับผู้ที่อาจมีปัญหานี้ทางออกที่ดีที่สุดคือการแก้ไขคอลัมน์ในประเภทไบนารีเป็นอันดับแรกตามตารางนี้:

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

และหลังจากนั้นก็ปรับเปลี่ยนคอลัมน์กลับไปเป็นประเภทเดิมและมีชุดอักขระที่คุณต้องการ

เช่น.:

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

ฉันลองในตาราง latin1 หลายอันและเก็บเครื่องหมายกำกับไว้ทั้งหมด

คุณสามารถแยกแบบสอบถามนี้สำหรับคอลัมน์ทั้งหมดที่ทำสิ่งนี้:

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

ฉันสร้างสคริปต์ซึ่งทำสิ่งนี้โดยอัตโนมัติไม่มากก็น้อย:

<?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";
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.