it-swarm-eu.dev

Jak snadno převést tabulky utf8 na utf8mb4 v MySQL 5.5

Mám databázi, která nyní potřebuje podporovat 4 bajtové znaky (čínské). Naštěstí již mám MySQL 5.5 ve výrobě.

Chtěl bych jen provést všechny kolace, které jsou utf8_bin až utf8mb4_bin.

Věřím, že s touto změnou nedojde k žádné ztrátě/zisku výkonu, než k částečné režii úložiště.

91
geoaxis

Z mého průvodce Jak podporovat plný Unicode v MySQL databázích , jsou zde dotazy, které můžete spustit pro aktualizaci znakové sady a řazení databáze, tabulky nebo sloupce:

Pro každou databázi:

ALTER DATABASE
    database_name
    CHARACTER SET = utf8mb4
    COLLATE = utf8mb4_unicode_ci;

Pro každou tabulku:

ALTER TABLE
    table_name
    CONVERT TO CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

Pro každý sloupec:

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

(Nepoužívejte slepě zkopírujte a vložte! Přesný příkaz závisí na typu sloupce, maximální délce a dalších vlastnostech. Výše ​​uvedený řádek je pouze příkladem VARCHAR sloupec.)

Mějte však na paměti, že nelze plně automatizovat převod z utf8 to utf8mb4. Jak je popsáno v krok 4 výše uvedeného průvodce , musíte zkontrolovat maximální délku sloupců a indexových klíčů, protože číslo, které zadáte, má jiný význam, když utf8mb4 se použije místo utf8.

Oddíl 10.1.11 MySQL 5.5 Reference Manual má o tom nějaké další informace.

106
Mathias Bynens

Mám řešení, které převede databáze a tabulky spuštěním několika příkazů. Převede také všechny sloupce typu varchar, text, tinytext, mediumtext, longtext, char. Měli byste také zálohovat databázi pro případ, že se něco pokazí.

Zkopírujte následující kód do souboru s názvem 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');

Nahraďte všechny výskyty "yourDbName" databází, kterou chcete převést. Pak spusťte:

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

Tím vygenerujete nový soubor alterTables.sql se všemi dotazy, které potřebujete k převodu databáze. Spusťte následující příkaz ke spuštění převodu:

mysql -uroot < alterTables.sql

Můžete to také upravit tak, aby běžel přes více databází změnou podmínky pro tabulku_schema. Například table_schema like "wiki_%" převede všechny databáze s předponou názvu wiki_. Chcete-li převést všechny databáze, nahraďte podmínku znakem table_type!='SYSTEM VIEW'.

Může nastat problém. Měl jsem několik varchar (255) sloupců v mysql klíčích. To způsobí chybu:

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

Pokud k tomu dojde, můžete jednoduše změnit sloupec tak, aby byl menší, například varchar (150), a příkaz znovu spustit.

Poznámka: Tato odpověď převede databázi na utf8mb4_unicode_ci namísto utf8mb4_bin, zeptal se na otázku. Ale můžete to jednoduše nahradit.

39
MrJingles87

Použil jsem následující skript Shell. Jako parametr převezme název databáze a převede všechny tabulky na jinou znakovou sadu a řazení (dané jinými parametry nebo výchozí hodnotou definovanou ve skriptu).

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

Napsal bych skript (v Perlu nebo cokoli), abych použil information_schema (TABLES and COLUMNS) k procházení všemi tabulkami a do MODIFY COLUMN do každého pole CHAR/VARCHAR/TEXT. Shromáždil bych všechny MODIFY do jediné ALTER pro každou tabulku; to bude efektivnější.

Myslím, že (ale nejsem si jistý), že Raihanův návrh změní pouze tabulku výchozí.

3
Rick James

Do této situace narazil; Zde je přístup, který jsem použil k převodu své databáze:

  1. Nejprve musíte upravit my.cnf pro vytvoření výchozího databázového připojení (mezi aplikacemi a MYSQL) utf8mb4_unicode_ci kompatibilním. Bez těchto znaků, jako jsou emodži a podobné, odeslané vašimi aplikacemi, se nedostanou do tabulek v správných bajtech/kódování (pokud parametry aplikace CNN DB vaší aplikace neurčují připojení utf8mb4).

    Pokyny jsou uvedeny zde .

  2. Proveďte následující SQL (není třeba připravovat SQL na změnu jednotlivých sloupců, ALTER TABLE to udělá).

    Než spustíte níže uvedený kód, nahraďte „DbName“ skutečným jménem DB.

    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. Shromážděte a uložte výstup nad SQL do souboru dot sql a spusťte jej.

  4. Pokud se zobrazí chyba jako #1071 - Specified key was too long; max key length is 1000 bytes. spolu s problematickým názvem tabulky to znamená, že indexový klíč na některém sloupci této tabulky (který měl být převeden na MB4 charstring) bude velmi velký, a proto by měl být sloupec Varchar <= 250, takže jeho indexový klíč bude max. 1 000 bytů. Zkontrolujte sloupce, ve kterých máte indexy, a pokud je jeden z nich varchar> 250 (pravděpodobně 255), pak

    • Krok 1: zkontrolujte data v tomto sloupci a ujistěte se, že maximální velikost řetězce v tomto sloupci je <= 250.

      Příklad dotazu:

      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;
      
    • Krok 2: Pokud maximální délka dat indexovaného sloupce <= 250, změňte délku sloupce na 250. Pokud to není možné, odeberte index z tohoto sloupce

    • Krok 3: poté znovu spusťte dotaz na alternativní tabulku pro tuto tabulku a tabulka by nyní měla být úspěšně převedena na utf8mb4.

Na zdraví!

3
Nav44

Pro lidi, kteří by mohli mít tento problém, je nejlepším řešením upravit nejprve sloupce na binární typ, podle této tabulky:

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

A poté upravte sloupec zpět na jeho dřívější typ a pomocí požadované znakové sady.

Např.:

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

Zkoušel jsem v několika tabulkách latin1 a zachoval jsem všechny diakritiky.

Tento dotaz můžete extrahovat pro všechny tyto sloupce:

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

Tuto příručku jsem napsal: http://hanoian.com/content/index.php/24-automate-the-convertting-a-mysql-database-character-set-to-utf8mb4

Z mé práce jsem viděl, že ALTER databáze a tabulky nestačí. Musel jsem jít do každé tabulky a také ALTER každý ze sloupců text/mediumtext/varchar.

Naštěstí jsem byl schopen napsat skript pro detekci metadat databází MySQL, takže mohl procházet tabulkami a sloupci a automaticky je ALTERED.

Dlouhý index pro MySQL 5.6:

Musíte mít jednu věc, kterou musíte mít oprávnění DBA/SUPER USER: Nastavení parametrů databáze:

 innodb_large_prefix: ON 
 innodb_file_format: Barracuda 
 innodb_file_format_max: Barracuda 

V odpovědích na tuto otázku je návod, jak výše uvedené parametry nastavit: https://stackoverflow.com/questions/35847015/mysql-change-innodb-large-prefix

V mém článku jsou samozřejmě pokyny, jak to udělat.

Pro MySQL verze 5.7 nebo novější je ve výchozím nastavení zapnuta funkce innodb_large_prefix a ve výchozím nastavení je také formát innodb_file_format Barracuda.

2

I vytvořil skript který to dělá víceméně automaticky:

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