返回

MySQL迁移报错: Unknown collation utf8mb4_0900_ai_ci 终极解决

mysql

MySQL 数据库迁移后 "Unknown collation: 'utf8mb4_0900_ai_ci'" 报错,但数据库中并不存在该排序规则

遇到过这种问题?程序连不上 MySQL 数据库,报 “1273 (HY000): Unknown collation: 'utf8mb4_0900_ai_ci'”,说是不认识这个叫 'utf8mb4_0900_ai_ci' 的排序规则,可数据库里压根就没用它啊! 这就很让人头大了。别急,我们一起看看咋回事,再把它搞定。

一、 啥原因造成的?

一般这种情况,都是从高版本 MySQL 数据库(比如 8.0)往低版本(比如 5.7)迁移数据后出现的。因为 'utf8mb4_0900_ai_ci' 是 MySQL 8.0 引入的,5.7 版本根本不认。 虽然你仔细检查过,表结构里都是 'utf8mb4_general_ci',没问题啊,但问题可能出在别的地方:

  1. 连接字符串: 有些客户端(或驱动程序)会默认用 'utf8mb4_0900_ai_ci' 去连接数据库,即使数据库本身设置不是这个。
  2. 数据库或服务器的默认配置: MySQL 服务器、数据库,甚至是连接的客户端, 都可能有自己的默认字符集和排序规则设置。即使数据表明确指定了,也可能被全局的设置覆盖掉。
  3. 存储过程、函数、触发器等: 数据表的定义是干净的, 但数据库里的存储过程、函数或触发器,可能创建的时候用到了 'utf8mb4_0900_ai_ci', 它们躲在暗处,也可能导致报错。
  4. 旧的连接池: 有时候,程序用的数据库连接池还保留着老的连接信息。那些老连接可能带着错误的排序规则。

二、 怎么解决?逐个排查!

下面我们分几种情况, 提供不同的解决方法。 记得做任何修改前备份数据库 !

1. 检查并修改连接字符串

看看你的连接字符串,是不是漏掉了什么? Python SQLAlchemy 的连接字符串长这样:

engine = create_engine('mysql+mysqlconnector://user:**** **** ***@** **** **** :3306/amatdb?charset=utf8mb4')

重点看 charset=utf8mb4 这里,只指定了字符集,没指定排序规则! MySQL 会按自己的默认规则来。我们给它加上排序规则:

engine = create_engine('mysql+mysqlconnector://user:**** **** ***@** **** **** :3306/amatdb?charset=utf8mb4&collation=utf8mb4_general_ci')

加了个 &collation=utf8mb4_general_ci,明确告诉它,用这个排序规则!

2. 修改 MySQL 服务器和数据库的默认配置

如果你能改动 MySQL 服务器配置,直接把默认的排序规则也改掉,就省事了。

  • 查看当前配置:

    SHOW VARIABLES LIKE 'character\_set\_%';
    SHOW VARIABLES LIKE 'collation%';
    

    看看 character_set_server, collation_server, character_set_database, collation_database 这几个变量的值。

  • 修改配置文件 (my.cnf 或 my.ini): 找到 MySQL 的配置文件 (位置可能不一样, 搜一下),在 [mysqld] 下面加上或修改:

    [mysqld]
    character_set_server = utf8mb4
    collation_server = utf8mb4_general_ci
    

    改完记得重启 MySQL 服务 !

  • 在数据库启动时进行设置:
    如果在Docker中运行MySQL,您可以使用环境变量进行设置:

    docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag -character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    

    如果是通过systemd,可以通过在服务设置中设定 command_line_args的方式来实现:

     [Service]
     #Command line arguments to be passed to mysqld_safe
     #Number of processes. Safe to set up to 2 * number of cores available.
     #The software takes advantage of SMP systems as appropriate.
     ExecStart=
     ExecStartPre=
     ExecStartPost=
     command_line_args=--character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    
  • 只修改数据库的配置(不推荐): 也可以单独修改数据库的默认值,但通常不建议这样,因为新建的表还是会按服务器的默认设置走:

    ALTER DATABASE amatdb CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
    

    这样改完, 理论上对新建表, 设置会生效.

3. 揪出并修改存储过程、函数、触发器

把那些可能藏着 'utf8mb4_0900_ai_ci' 的家伙找出来,改掉!

  • 查询存储过程和函数:

    SELECT ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, COLLATION_CONNECTION
    FROM information_schema.ROUTINES
    WHERE ROUTINE_SCHEMA = 'your_database_name'; -- 换成你的数据库名
    

    COLLATION_CONNECTION 列,如果有 'utf8mb4_0900_ai_ci',记下来!

  • 查询触发器:

    SELECT TRIGGER_SCHEMA, TRIGGER_NAME, COLLATION_CONNECTION
    FROM information_schema.TRIGGERS
    WHERE TRIGGER_SCHEMA = 'your_database_name';  -- 换成你的数据库名
    

    同样看 COLLATION_CONNECTION 列。

  • 修改有问题的对象: 找到后,可以用 ALTER PROCEDURE, ALTER FUNCTION, ALTER TRIGGER 语句修改它们的排序规则,或者直接删除重建。修改前,一定先用 SHOW CREATE PROCEDURE/FUNCTION/TRIGGER 看清楚定义!

    举个例子, 假如有个存储过程叫 my_proc,要修改它:

    -- 先查看定义,确认修改位置
    SHOW CREATE PROCEDURE my_proc;
    
    -- 修改排序规则
    ALTER PROCEDURE my_proc SQL SECURITY INVOKER COLLATION 'utf8mb4_general_ci';
    

    进阶操作: 批量导出、修改、再导入

    如果有很多存储过程、函数要改,手动太麻烦了。可以写个脚本,批量导出所有定义,用文本替换把 'utf8mb4_0900_ai_ci' 换成 'utf8mb4_general_ci',再批量导入回去。要注意 SQL 语句的分隔符可能也需要修改,免得导入出错。

    或者使用如下查询语句一次性查看全部受影响对象,进行统一导出:

      SELECT CONCAT('ALTER ',
                  ROUTINE_TYPE, ' `',
                  ROUTINE_SCHEMA, '`.`',
                  ROUTINE_NAME,
                  '` COLLATION ''utf8mb4_general_ci'';')
       FROM information_schema.ROUTINES
       WHERE COLLATION_CONNECTION = 'utf8mb4_0900_ai_ci'
        AND ROUTINE_SCHEMA = 'your_database_name'
      UNION ALL
      SELECT CONCAT('ALTER TRIGGER `',
                 TRIGGER_SCHEMA, '`.`',
                 TRIGGER_NAME,
                '` COLLATION ''utf8mb4_general_ci'';')
      FROM   information_schema.TRIGGERS
      WHERE COLLATION_CONNECTION = 'utf8mb4_0900_ai_ci'
         AND TRIGGER_SCHEMA = 'your_database_name';
    

这个命令会将所有的alter命令组合生成出来,但是可能不包括修改触发器以及event等部分的内容。

4. 清理/重启连接池

如果你用了数据库连接池,试试重启连接池,或者清理一下旧连接,让它重新建立连接。不同连接池操作方式不一样,具体看你用的连接池的文档。

5.终极大招:重新导数据(如果上面的都不行)

上面几个方法都试过了, 还不行?那只能放大招了:

  1. 从 MySQL 8.0 的数据库,用 mysqldump 导出数据。 导出的时候,加上参数,强制指定字符集和排序规则:

    mysqldump -u your_user -p --default-character-set=utf8mb4 --set-gtid-purged=OFF your_database_name > your_dump_file.sql
    

    set-gtid-purged=OFF, 可以解决一些 reset master 会发生的问题.

  2. 修改导出的 .sql 文件, 替换 utf8mb4_0900_ai_ciutf8mb4_general_ci。这一步可以用文本编辑器或者 sed 命令:

    sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' your_dump_file.sql
    
  3. 把修改后的 .sql 文件导入到 MySQL 5.7 的数据库。

特别提醒: 如果你的数据量很大,导出会很久。导入前最好把目标数据库的事务日志关掉(SET GLOBAL innodb_flush_log_at_trx_commit = 0;),导入完再打开,能快很多。 但这样做有风险, 如果导入过程中断电, 可能会丢数据。请务必谨慎权衡。

这几个方法, 一个个排查下来,应该能解决你的问题。问题原因不一定是单一的, 多个原因混合导致的可能性也不是没有。所以多试试不同的组合拳!