暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

技术干货 | Oracle迁移到GreatSQL 因排序规则改变引发的乱码问题怎么破?

万里数据库 2025-06-19
76



项目背景

某老业务系统项目,数据库从Oracle迁移至GreatSQL过程中,首批迁移(存储过程、表结构、基础数据)顺利完成。然而,第二批数据在迁移时出现主键冲突问题:原Oracle数据库中存在主键字段A与a(忽略大小写后视为相同值),但GreatSQL默认排序规则 utf8mb4_0900_ai_ci不区分大小写,导致主键冲突。


为解决此问题,将排序规则调整为 utf8mb4_0900_bin,以区分大小写。但调整后,Java程序读取中文字段时出现乱码(如“好”显示为“好”),直接影响业务功能。基于上述问题,本文将从环境兼容性、驱动版本、字符编解码机制等角度深入分析问题根源,并提供三种解决方案。



01

环境说明与问题背景


关键组件版本:

组件

版本号

备注

数据库

GreatSQL 8.0.32-26

默认字符集utf8mb4

jdk

1.7.0_80

旧版本,升级成本高

驱动版本

mysql-connector-java 5.1.46

官方已停止维护

字符集

utf8mb4

未变动

排序规则

utf8mb4_0900_ai_ci->utf8mb4_0900_bin

变更后引发乱码


核心矛盾点:

  • 业务需求:需使用utf8mb4_0900_bin排序规则解决主键冲突。

  • 环境限制:旧版JDK 1.7与低版本驱动(5.1.46)存在兼容性问题,无法正确解析新排序规则。



02

复现过程


1. 创建测试表并插入数据


    greatsql> CREATE TABLE test.t1(id int PRIMARY KEY, cname varchar(10)) DEFAULTcharset=utf8mb4 collate=utf8mb4_0900_ai_ci;
    Query OK, 0 rows affected (0.02 sec)


    greatsql> INSERT INTO test.t1 VALUES(1'好');
    Query OK, 1 row affected (0.00 sec)


    确认Java版本

      $ javac -version
      javac 1.7.0_80
      $ java -version
      java version 
      "1.7.0_80"
      Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
      Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)


      编写SimpleDBQuery.java,其内容如下:

        import java.sql.Connection;
        import java.sql.DriverManager;
        import java.sql.ResultSet;
        import java.sql.Statement;


        publicclass SimpleDBQuery{
            public static void main(String[] args){
                String url = "jdbc:mysql://172.17.134.66:3301/test?characterEncoding=UTF-8&useSSL=false";
                String username = "bing";
                String password = "abc123";
                Connection conn = null;
                Statement stmt = null;
                ResultSet rs = null
        try {
                    Class.forName("com.mysql.jdbc.Driver");
                    conn = DriverManager.getConnection(url,  username, password);
                    String sql = "SELECT cname FROM t1 LIMIT 1";
                    stmt = conn.createStatement();
                    rs = stmt.executeQuery(sql);
                    if (rs.next())  {
                        String value = rs.getString("cname");
                        System.out.println(" 查询结果: "+ value);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {               
                         if (rs!= null) rs.close();
                        if (stmt!= null) stmt.close();
                        if (conn!= null) conn.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }


        2.Java程序读取数据(正常)


          javac -cp .:mysql-connector-java-5.1.46.jar SimpleDBQuery.java
          java -cp .:mysql-connector-java-5.1.46.jar SimpleDBQuery
            查询结果: 好


          3.修改排序规则后复现乱码


            greatsql> ALTERTABLE test.t1 CONVERTTOcharset utf8mb4 COLLATE utf8mb4_0900_bin;
            Query OK, 0 rows affected (0.04 sec)
            Records0  Duplicates: 0  Warnings: 0


            再次通过Java程序访问数据库中的汉字,则出现乱码:

              java -cp .:mysql-connector-java-5.1.46.jar SimpleDBQuery
                查询结果: 好



              03

              关键排查过程


              1. 数据库端验证 

              确认表中数据无乱码,且字符集未变动,仅排序规则修改。

                greatsql> SHOWCREATETABLE test.t1 \G
                *************************** 1.row ***************************
                       Table: t1
                CreateTable: CREATETABLE`t1` (
                `id`intNOTNULL,
                `cname`varchar(10) COLLATE utf8mb4_0900_bin DEFAULTNULL,
                  PRIMARY KEY (`id`)
                ) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
                1rowinset (0.00 sec)


                greatsql> SELECT * FROM test.t1;
                +----+-------+
                | id | cname |
                +----+-------+
                |  1 | 好    |
                +----+-------+
                1 row in set (0.01 sec)


                2. 驱动源码分析 

                查看驱动5.1.46中仅支持 utf8mb4_0900_ai_ci,未定义 utf8mb4_0900_bin。

                  $ grep -inr 'utf8mb4_0900_ai_ci' *
                  com/mysql/jdbc/CharsetMapping.java:489:        collation[255] = new Collation(255, "utf8mb4_0900_ai_ci", 0, MYSQL_CHARSET_NAME_utf8mb4);
                  $ grep -inr 'utf8mb4_0900_bin' *
                  pwd
                  /opt/software/jdbc_test/mysql-connector-java-5.1.46/src


                  3. 解码逻辑

                  当驱动无法识别排序规则时,默认使用latin1解码,导致UTF-8字节流被错误解析。


                  4. 网络抓包验证

                  通过抓包,对比确认不论是utf8mb4_0900_ai_ci,还是utf8mb4_0900_bin,返回的十六进制数据均为e5 a5 bd。


                  5. 解析抓包内容验证

                  如果用默认的latin1作为字符集进行解码,那么将e5 a5 bd按照latin1进行解码,发现返回结果集和查询乱码一致。


                  通过在线工具

                  https://qr9.net/string-encoding将十六进制内容按latin1解码发现和乱码内容一致:


                  通过在线工具 https://lzltool.cn/Tools/HexToUtf8 将十六进制内容按utf8解码,确认能够解析正确的返回结果“好”:



                  04

                  根因分析


                  乱码本质:低版本驱动(5.1.46)未适配GreatSQL 8.0.32的utf8mb4_0900_bin排序规则,触发默认的latin1解码机制,导致UTF-8字节流被错误转换。



                  05

                  解决方法

                  方案1:强制指定JDBC字符集参数(推荐)

                  在连接字符串中显式声明编解码规则:

                    String url = "jdbc:mysql://10.191.81.31:3307/test?useUnicode=true&characterSetResults=utf8&characterEncoding=utf8&useSSL=false";


                    参数作用:

                    • characterSetResults=utf8:强制服务端返回UTF-8编码。

                    • characterEncoding=utf8:客户端使用UTF-8编码发送请求。


                    优点:无需升级,调整简单,兼容性强。


                    方案2:使用兼容的排序规则

                    将排序规则改为utf8mb4_bin(非utf8mb4_0900_bin),该规则在驱动5.1.46中已支持,且同样区分大小写。

                      ALTERTABLE test.t1 CONVERTTOCHARSET utf8mb4 COLLATE utf8mb4_bin;  


                      方案3:升级驱动至8.0.x版本

                      使用mysql-connector-java-8.0.32,完全支持utf8mb4_0900_bin。

                        <!-- Maven依赖示例 -->  
                        <dependency>  
                            <groupId>mysql</groupId>  
                            <artifactId>mysql-connector-java</artifactId>  
                            <version>8.0.32</version>  
                        </dependency>  


                        注意事项:需验证JDK 1.7与新版驱动的兼容性,部分API可能需要调整。



                        06

                        总  结


                        本文通过复现、排查、分析三步定位乱码问题,发现根本原因在于驱动版本与数据库排序规则的兼容性。以下三种解决方案,各有其适用场景:

                        • 快速修复场景:调整JDBC连接参数,强制UTF-8编解码;

                        • 保守场景:使用兼容的utf8mb4_bin排序规则;

                        • 技术升级场景:升级驱动至8.0.x版本。


                        建议:需要根据实际环境选择最优方案,并在变更后进行全面测试,确保数据一致性与业务功能正常。


                        Enjoy GreatSQL





                        技术干货 | GreatSQL连接数被打满的3种紧急解决方案

                        技术干货 | dbops 助力GreatSQL安装部署

                        技术干货 | 图文结合带你搞懂GreatSQL体系架构


                        关于万里数据库


                        北京万里开源软件有限公司(简称“万里数据库”)成立于2000年,是专注于国产自主可控数据库产品研发的国家高新技术企业、国家级专精特新“小巨人”企业,拥有发明专利、软件著作权百余项。


                        万里数据库的技术底蕴源自对底层核心代码的掌控,产品始终坚持以“极致稳定、极致性能、极致易用”为目标,经过20余年的研发经验积累,产品在功能、性能、稳定、易用等方面均处于行业领先水平,广泛应用于金融、运营商、能源、政府、交通等行业重要业务系统中的超2000个业务场景,得到了用户和市场的认可与肯定。


                        2021年,公司创立GreatSQL开源社区,通过对MySQL技术的优化,目前已成长为国内活跃的自主开源数据库社区


                        MySQL国产替代第一品牌


                        “在看”点一下,万里早知道

                        文章转载自万里数据库,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                        评论