在Oracle到GreatSQL迁移中排序规则改变引发的乱码问题分析及解决
<h2 id="一引言">一、引言</h2><p>某老系统数据库从 Oracle 迁移至 GreatSQL 过程中,首批迁移(存储过程、表结构、基础数据)顺利完成。然而,第二批数据迁移时出现主键冲突问题:原Oracle数据库中存在主键字段A与a(忽略大小写后视为相同值),但 GreatSQL 默认排序规则 utf8mb4_0900_ai_ci 不区分大小写,导致主键冲突。</p>
<p>为解决此问题,将排序规则调整为 utf8mb4_0900_bin 以区分大小写。但调整后,Java程序读取中文字段时出现乱码(如“好”显示为“好”),直接影响业务功能。本文从环境兼容性、驱动版本、字符编解码机制等角度深入分析问题根源,并提供三种解决方案。</p>
<h2 id="二环境说明与问题背景">二、环境说明与问题背景</h2>
<p>关键组件版本:</p>
<table>
<thead>
<tr>
<th>组件</th>
<th>版本号</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td>数据库</td>
<td>GreatSQL 8.0.32-26</td>
<td>默认字符集utf8mb4</td>
</tr>
<tr>
<td>jdk</td>
<td>1.7.0_80</td>
<td>旧版本,升级成本高</td>
</tr>
<tr>
<td>驱动版本</td>
<td>mysql-connector-java 5.1.46</td>
<td>官方已停止维护</td>
</tr>
<tr>
<td>字符集</td>
<td>utf8mb4</td>
<td>未变动</td>
</tr>
<tr>
<td>排序规则</td>
<td>utf8mb4_0900_ai_ci->utf8mb4_0900_bin</td>
<td>变更后引发乱码</td>
</tr>
</tbody>
</table>
<p><strong>核心矛盾点</strong></p>
<ul>
<li>业务需求:需使用 utf8mb4_0900_bin 排序规则解决主键冲突。</li>
<li>环境限制:旧版 JDK 1.7 与低版本驱动(5.1.46)存在兼容性问题,无法正确解析新排序规则。</li>
</ul>
<h2 id="三复现过程">三、复现过程</h2>
<h3 id="1-创建测试表并插入数据">1. 创建测试表并插入数据</h3>
<pre><code class="language-sql">greatsql> CREATE TABLE test.t1(id int PRIMARY KEY, cname varchar(10)) DEFAULT charset=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)
</code></pre>
<p>确认 Java 版本</p>
<pre><code class="language-bash">$ 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)
</code></pre>
<p>编写 SimpleDBQuery.java,其内容如下:</p>
<pre><code class="language-java">import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class 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();
}
}
}
}
</code></pre>
<h3 id="2java程序读取数据正常">2.Java程序读取数据(正常)</h3>
<pre><code class="language-java">$ javac -cp .:mysql-connector-java-5.1.46.jar SimpleDBQuery.java
$ java -cp .:mysql-connector-java-5.1.46.jar SimpleDBQuery
查询结果: 好
</code></pre>
<h3 id="3修改排序规则后复现乱码">3.修改排序规则后复现乱码</h3>
<pre><code class="language-sql">greatsql> ALTER TABLE test.t1 CONVERT TO charset utf8mb4 COLLATE utf8mb4_0900_bin;
Query OK, 0 rows affected (0.04 sec)
Records: 0Duplicates: 0Warnings: 0
</code></pre>
<p>再次通过 Java 程序访问数据库中的汉字,则出现乱码:</p>
<pre><code class="language-JAVA">$ java -cp .:mysql-connector-java-5.1.46.jar SimpleDBQuery
查询结果: 好
</code></pre>
<h2 id="四关键排查过程">四、关键排查过程</h2>
<h3 id="1-数据库端验证">1. 数据库端验证</h3>
<p>确认表中数据无乱码,且字符集未变动,仅排序规则修改。</p>
<pre><code class="language-sql">greatsql> SHOW CREATE TABLE test.t1 \G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int NOT NULL,
`cname` varchar(10) COLLATE utf8mb4_0900_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin
1 row in set (0.00 sec)
greatsql> SELECT * FROM test.t1;
+----+-------+
| id | cname |
+----+-------+
|1 | 好 |
+----+-------+
1 row in set (0.01 sec)
</code></pre>
<h3 id="2-驱动源码分析">2. 驱动源码分析</h3>
<p>查看驱动5.1.46中仅支持 utf8mb4_0900_ai_ci,未定义 utf8mb4_0900_bin。</p>
<pre><code class="language-bash">$ grep -inr 'utf8mb4_0900_ai_ci' *
com/mysql/jdbc/CharsetMapping.java:489: collation = 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
</code></pre>
<h3 id="3-解码逻辑">3. 解码逻辑</h3>
<p>当驱动无法识别排序规则时,默认使用latin1解码,导致UTF-8字节流被错误解析</p>
<p><img src="https://img2024.cnblogs.com/other/2630741/202505/2630741-20250514092950763-1766395902.png"></p>
<h3 id="4-网络抓包验证">4. 网络抓包验证</h3>
<p>通过抓包,对比确认不论是 utf8mb4_0900_ai_ci,还是 utf8mb4_0900_bin,返回的十六进制数据均为 <strong>e5 a5 bd</strong></p>
<p><img src="https://img2024.cnblogs.com/blog/2630741/202505/2630741-20250514102742335-1586606180.png"></p>
<h3 id="5-解析抓包内容验证">5. 解析抓包内容验证</h3>
<p>如果是用默认的 latin1 做为字符集进行解码,那么把 e5 a5 bd 按照 latin1 进行解码,发现返回结果集和查询乱码一致。</p>
<p>通过在线工具 https://qr9.net/string-encoding 将十六进制内容按latin1解码发现和乱码内容一致:</p>
<p><img src="https://img2024.cnblogs.com/blog/2630741/202505/2630741-20250514102751490-63787686.png"></p>
<p>通过在线工具 https://lzltool.cn/Tools/HexToUtf8 将十六进制内容按 utf8 解码,确认能够解析正确的返回结果“好”:</p>
<p><img src="https://img2024.cnblogs.com/blog/2630741/202505/2630741-20250514102757975-987435977.png"></p>
<h2 id="五根因分析">五、根因分析</h2>
<p><strong>乱码本质</strong>:低版本驱动(5.1.46)未适配 GreatSQL 8.0.32 的 utf8mb4_0900_bin 排序规则,触发默认的 latin1 解码机制,导致 UTF-8 字节流被错误转换。</p>
<h2 id="六解决方法">六、解决方法</h2>
<p><strong>方案1</strong>:强制指定JDBC字符集参数(推荐)</p>
<p>在连接字符串中显式声明编解码规则:</p>
<pre><code class="language-java">String url = "jdbc:mysql://10.191.81.31:3307/test?useUnicode=true&characterSetResults=utf8&characterEncoding=utf8&useSSL=false";
</code></pre>
<p>参数作用:</p>
<ul>
<li><code>characterSetResults=utf8</code>:强制服务端返回UTF-8编码。</li>
<li><code>characterEncoding=utf8</code>:客户端使用UTF-8编码发送请求。</li>
</ul>
<blockquote>
<p>优点:无需升级,调整简单,兼容性强。</p>
</blockquote>
<p><strong>方案2</strong>:使用兼容的排序规则</p>
<p>将排序规则改为<code>utf8mb4_bin</code>(非<code>utf8mb4_0900_bin</code>),该规则在驱动5.1.46中已支持,且同样区分大小写。</p>
<pre><code class="language-sql">ALTER TABLE test.t1 CONVERT TO CHARSET utf8mb4 COLLATE utf8mb4_bin;
</code></pre>
<p><strong>方案3</strong>:升级驱动至8.0.x版本</p>
<p>使用<code>mysql-connector-java-8.0.32</code>,完全支持<code>utf8mb4_0900_bin</code>。</p>
<pre><code class="language-java"><!-- Maven依赖示例 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
</code></pre>
<blockquote>
<p>注意事项:需验证 JDK 1.7 与新版驱动的兼容性,部分API可能需调整。</p>
</blockquote>
<h2 id="七总结">七、总结</h2>
<p>本文通过复现、排查、分析三步定位乱码问题,根本原因在于驱动版本与数据库排序规则的兼容性。三种解决方案各有适用场景:</p>
<ol>
<li>
<p>快速修复场景:调整JDBC连接参数,强制UTF-8编解码。</p>
</li>
<li>
<p>保守场景:使用兼容的utf8mb4_bin排序规则。</p>
</li>
<li>
<p>技术升级场景:升级驱动至8.0.x版本。</p>
</li>
</ol>
<p>建议根据实际环境选择最优方案,并在变更后进行全面测试,确保数据一致性与业务功能正常。</p>
<hr>
<p>Enjoy GreatSQL 😃</p>
<h2 id="关于-greatsql">关于 GreatSQL</h2>
<p>GreatSQL是适用于金融级应用的国内自主开源数据库,具备高性能、高可靠、高易用性、高安全等多个核心特性,可以作为MySQL或Percona Server的可选替换,用于线上生产环境,且完全免费并兼容MySQL或Percona Server。</p>
<p>相关链接: GreatSQL社区 Gitee GitHub Bilibili</p>
<h2 id="greatsql社区">GreatSQL社区:</h2>
<blockquote>
<p>社区博客有奖征稿详情:https://greatsql.cn/thread-100-1-1.html</p>
</blockquote>
<p><img src="https://img2024.cnblogs.com/other/2630741/202505/2630741-20250514092951874-650410899.png"></p>
<h2 id="技术交流群">技术交流群:</h2>
<blockquote>
<p>微信:扫码添加<code>GreatSQL社区助手</code>微信好友,发送验证信息<code>加群</code>。</p>
</blockquote>
<p><img src="https://img2024.cnblogs.com/other/2630741/202505/2630741-20250514092952136-196388197.png"></p><br><br>
来源:https://www.cnblogs.com/greatsql/p/18875372
頁:
[1]