Como converter facilmente tabelas utf8 para utf8mb4 no MySQL 5.5

71

Eu tenho um banco de dados que agora precisa suportar caracteres de 4 bytes (chinês). Felizmente, eu já tenho o MySQL 5.5 em produção.

Então, eu gostaria de fazer todos os agrupamentos que são utf8_bin em utf8mb4_bin.

Acredito que não haja perda / ganho de desempenho com essa alteração além de um pouco de sobrecarga de armazenamento.

geoaxia
fonte

Respostas:

93

Do meu guia Como dar suporte a Unicode completo nos bancos de dados MySQL , aqui estão as consultas que você pode executar para atualizar o conjunto de caracteres e agrupamento de um banco de dados, uma tabela ou coluna:

Para cada banco de dados:

ALTER DATABASE
    database_name
    CHARACTER SET = utf8mb4
    COLLATE = utf8mb4_unicode_ci;

Para cada tabela:

ALTER TABLE
    table_name
    CONVERT TO CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

Para cada coluna:

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

(Não copie e cole cegamente isso! A declaração exata depende do tipo de coluna, comprimento máximo e outras propriedades. A linha acima é apenas um exemplo para uma VARCHARcoluna.)

Observe, no entanto, que você não pode automatizar completamente a conversão de utf8para utf8mb4. Conforme descrito na etapa 4 do guia acima , você precisará verificar o comprimento máximo de colunas e chaves de índice, pois o número especificado tem um significado diferente quando utf8mb4usado em vez de utf8.

A Seção 10.1.11 do Manual de Referência do MySQL 5.5 tem mais algumas informações sobre isso.

Mathias Bynens
fonte
31

Eu tenho uma solução que irá converter bancos de dados e tabelas executando alguns comandos. Ele também converte todas as colunas do tipo varchar, text, tinytext, mediumtext, longtext, char. Você também deve fazer backup do seu banco de dados , caso algo ocorra.

Copie o seguinte código em um arquivo chamado 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');

Substitua todas as ocorrências de "yourDbName" pelo banco de dados que você deseja converter. Então corra:

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

Isso irá gerar um novo arquivo alterTables.sql, com todas as consultas necessárias para converter o banco de dados. Execute o seguinte comando para iniciar a conversão:

mysql -uroot < alterTables.sql

Você também pode adaptar isso para executar vários bancos de dados, alterando a condição do table_schema. Por exemplo table_schema like "wiki_%", converterá todos os bancos de dados com o prefixo do nome wiki_. Para converter todos os bancos de dados, substitua a condição por table_type!='SYSTEM VIEW'.

Um problema que pode surgir. Eu tinha algumas colunas varchar (255) nas chaves do mysql. Isso causa um erro:

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

Se isso acontecer, você pode simplesmente mudar a coluna para ser menor, como varchar (150), e executar novamente o comando.

Observação : esta resposta converte o banco de dados em utf8mb4_unicode_civez de utf8mb4_bin, na pergunta. Mas você pode simplesmente substituir isso.

MrJingles87
fonte
Ótimos scripts, apenas algumas notas; As instalações atuais do MiariaDb exigem que a senha seja fornecida, assim mysql -uroot -pThatrootPassWord < alterTables.sqlfunciona. E como você já observou, utf8mb4_bin é o que, entre outros, o nextcloud recomenda.
Julius
mas utf8mb4_0900_ai_ci é o padrão agora, consulte monolune.com/what-is-the-utf8mb4_0900_ai_ci-collation
Julius
Eu tive que usar "SET Foreign_key_checks = 0;", aplicar as alterações e, em seguida, "SET Foreign_key_checks = 1;".
dfrankow 15/08
Obrigado cara. Esta foi a solução no Redmin para mudar tudo para utf8mb4.
Luciano Fantuzzi
5

Eu usei o seguinte script de shell. Ele pega o nome do banco de dados como parâmetro e converte todas as tabelas em outro conjunto de caracteres e agrupamento (fornecido por outros parâmetros ou valor padrão definido no script).

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

Eu escreveria um script (em Perl, ou qualquer outra coisa) para usar o information_schema (TABLES e COLUMNS) para percorrer todas as tabelas e MODIFY COLUMN em todos os campos CHAR / VARCHAR / TEXT. Eu coletaria todos os MODIFYs em um único ALTER para cada tabela; isso será mais eficiente.

Penso (mas não tenho certeza) que a sugestão de Raihan altera apenas o padrão da tabela.

Rick James
fonte
3

Corri para esta situação; Aqui está a abordagem que eu usei para converter meu banco de dados:

  1. Primeiro, você precisa editar my.cnfpara tornar a conexão com o banco de dados padrão (entre aplicativos e MYSQL) compatível com utf8mb4_unicode_ci. Sem esses caracteres, como emojis e similares enviados por seus aplicativos, eles não serão exibidos em suas tabelas em bytes / codificação corretos (a menos que os parâmetros de CN CN do banco de dados do seu aplicativo especifiquem uma conexão utf8mb4).

    Instruções dadas aqui .

  2. Execute o SQL a seguir (não é necessário preparar o SQL para alterar colunas individuais, as ALTER TABLEinstruções farão isso).

    Antes de executar o código abaixo, substitua "DbName" pelo seu nome de banco de dados real.

    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. Colete e salve a saída do SQL acima em um arquivo dot sql e execute-o.

  4. Se você receber um erro como #1071 - Specified key was too long; max key length is 1000 bytes.o nome da tabela problemática, isso significa que a chave de índice em alguma coluna dessa tabela (que deveria ser convertida em string MB4) será muito grande, portanto a coluna Varchar deve ser <= 250 para que sua A chave de índice terá no máximo 1000 bytes. Verifique as colunas nas quais você possui índices e, se um deles for um varchar> 250 (provavelmente 255),

    • Etapa 1: verifique os dados nessa coluna para garantir que o tamanho máximo da string nessa coluna seja <= 250.

      Consulta de exemplo:

      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;
      
    • Etapa 2: se o comprimento máximo dos dados da coluna indexada for <= 250, altere o comprimento da coluna para 250. se isso não for possível, remova o índice nessa coluna

    • Etapa 3: execute a consulta alter table para essa tabela novamente e a tabela agora deve ser convertida em utf8mb4 com êxito.

Felicidades!

Nav44
fonte
Existe uma maneira de usar o índice para VARCHAR longo com mais de 191 caracteres. É necessário ter o privilégio DBA / SUPER USER para: Definir os parâmetros do banco de dados: innodb_large_prefix: ON; innodb_file_format: Barracuda; innodb_file_format_max: Barracuda;
Châu Hồng Lĩnh 4/10
2

Eu escrevi este guia: http://hanoian.com/content/index.php/24-automate-the-converting-a-mysql-database-character-set-to-utf8mb4

No meu trabalho, vi que ALTER o banco de dados e as tabelas não são suficientes. Eu tive que entrar em cada tabela e ALTERAR cada uma das colunas text / mediumtext / varchar também.

Felizmente, consegui escrever um script para detectar os metadados dos bancos de dados MySQL, para que ele pudesse percorrer as tabelas e colunas e alterá-los automaticamente.

Índice longo para MySQL 5.6:

Há uma coisa que você deve ter o privilégio DBA / SUPER USER para fazer: Definir os parâmetros do banco de dados:

innodb_large_prefix: ON
innodb_file_format: Barracuda 
innodb_file_format_max: Barracuda

Nas respostas para esta pergunta, há instruções sobre como definir esses parâmetros acima: https://stackoverflow.com/questions/35847015/mysql-change-innodb-large-prefix

Obviamente, no meu artigo, há instruções para fazer isso também.

Para o MySQL versão 5.7 ou mais recente , o innodb_large_prefix está ativado por padrão e o innodb_file_format também é o Barracuda por padrão.

Châu Hồng Lĩnh
fonte
2

Para pessoas que podem ter esse problema, a melhor solução é modificar primeiro as colunas para um tipo binário, de acordo com esta tabela:

  1. CHAR => BINÁRIO
  2. TEXTO => BLOB
  3. TINYTEXT => TINYBLOB
  4. MEDIUMTEXT => MEDIUMBLOB
  5. LONGTEXT => LONGBLOB
  6. VARCHAR => VARBINÁRIO

Depois disso, modifique a coluna de volta ao seu tipo anterior e com o conjunto de caracteres desejado.

Por exemplo.:

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

Eu tentei em várias tabelas latin1 e manteve todos os diacríticos.

Você pode extrair esta consulta para todas as colunas fazendo isso:

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

Eu criei um script que faz isso mais ou menos automaticamente:

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