某次信创环境Oceanbase数据库偶发乱码问题

资料迁移,整理分享。 问题发生在2023年


一、环境介绍及问题简述

数据库oceanbase
操作系统Linux (麒麟)
WEB中间件Tongweb
数据库表编码GBK
中间件默认使用的HTTP通道编码GBK
Java应用UTF8
在客户方搭建的信创环境中,使用麒麟Linux作为操作系统,Oceanbase作为应用数据库,东方通TongWeb作为Web应用容器,在此环境下 部署我司JAVA应用(后续表述中将使用简称WebA),在WebA使用过程中,偶发的出现中文字符入库后乱码的问题,导致业务数据无法正常查看。

其中,oceanbase数据库编码使用GBK,TongWeb中HTTP通道也使用了默认的GBK编码,WebA则采用UTF8编码,应用与数据库的连接,交由TongWeb中的数据源配置进行池化管理。

该乱码问题随机出现,无规律,时间随机,针对同一个功能,即便是相同的数据,也不会重复触发。

二、结论声明

  • 排查后发现,现场存在定制化功能,对外公布接口,该接口在收到请求后,会调用set names gbk这个数据库命令以防止后续SP执行返回中文乱码。
  • 该命令会修改数据库session级的字符编码,而且WebA的数据库连接是由中间件池化管理的,进而导致连接池里的连接被 set names gbk 命令污染。
  • WebA采用的字符集又是UTF8,后续使用过程中,使用被污染的连接操作中文字符时就会出现乱码问题。

三、原因解释

1、作为前提,需要知道,每一种编码方式会有自己的字符映射表。同样的字节序列在不同的编码方式中代表的是不同的字符。

2、在向数据库服务器发送一条SQL指令时,指令及指令中的数据是以字节序列的形式发送到后台的。

3、在数据库中,可以通过 show variables like '%char%' 命令获取当前连接下的数据库编码,如下图

database.png

其中,character_set_client 是数据库服务器认为的请求端使用的字符编码方式,如上图,即便客户端在发送sql语句时,使用的是GBK编码,服务器也会将其认为是UTF8编码进行处理。

character_set_connection 数据库服务器会把按照 character_set_client 编码处理的字符再转换为 character_set_connection 对应的编码方案。此时,如果两者的配置值相同,就可以省略一个转换的操作。

服务器的后续操作中,还会发生 character_set_connection到数据库表或数据表列的字符转换。

character_set_results 是服务器向请求端返回数据时使用的字符集,服务器会采用该变量指定的字符集对返回给客户端的数据进行编码。

针对入库乱码的问题,此处重点关注 character_set_client 与 character_set_connection

4、set names gbk的作用

该 语句将三个会话系统变 量 character_set_client , character_set_connection 和 character_set_results 设置为给定的GBK字符集。 该命令仅作用于当前数据库会话,SESSION级别,详情见SET NAMES_用户指南_云数据库OceanBase_敏捷版通用版本 (aliyun.com)
dg

另外,在OB数据中,支持在租户级、Database 级、表级、字段级、session 级设置字符集,详见字符集规范-OceanBase 数据库 -OceanBase文档中心-分布式数据库使用文档

5、测试(MySQL环境,OceanBase环境)。

MySQL测试用例一 :

  在WebA中通过代码修改 当前数据库连接 的字符编码设置,修改前后重复执行  SELECT '中文字段啊四川省' as col FROM dual  语句,查看输出结果是否乱码。
character_set_clientcharacter_set_connection是否乱码结果
utf8mb4utf8mb4 否uu.png
utf8mb4gbkug.png
    
gbkgbkgg.png
gbkutf8mb4gu.png
MySQL测试用例二:

定义如下数据库表

CREATE TABLE  encoding_test (  
  zzz  varchar(100) DEFAULT NULL,  
  xxx  varchar(100) DEFAULT NULL,  
  ccc  varchar(100) DEFAULT NULL,  
  vvv  varchar(100) DEFAULT NULL  
) ENGINE=InnoDB DEFAULT CHARSET=gbk

修改 当前数据库连接 的字符编码设置,执行insert插入语句,查看插入的数据是否乱码。

insert.png

character_set_client character_set_connection 是否乱码结果
utf8mb4 utf8mb4 iuu.png
utf8mb4 gbk iug.png
gbkgbk igg.png
gbk utf8mb4 igu.png

由上结果可证: 在mysql的innodb引擎下,当 character_set_client 的编码与请求端的实际编码方式不匹配时,会出现数据的乱码情况。

OceanBase测试用例一 :

  通过代码修改 当前数据库连接 的字符编码设置,修改前后重复执行  SELECT '中文字段啊四川省' as col FROM dual  语句,查看输出结果是否乱码。

character_se

t_client

character_se

t_connection

character_se

t_results

是否

乱码

结果
utf8mb4 utf8mb4 utf8mb4  否 ouuu.png
utf8mb4 utf8mb4 gbk ouug.png
utf8mb4 gbk utf8mb4 ougu.png
utf8mb4 gbk gbk ougg.png
         
gbk utf8mb4 utf8mb4 oguu.png
gbk utf8mb4 gbk ogug.png
gbk gbk utf8mb4 oggu.png
gbk gbk gbk oggg.png

根据上方表格及输出截图发现,oceanbase中,character_set_connection与请求端编码一致时不会出现乱码的情况。

Oceanbase测试用例二:

字段,测试代码沿用mysql测试示例二中的内容,下方继续以表格形式展示:

character_se

t_client

character_se

t_connection

character_se

t_results

是否乱码 结果
utf8mb4 utf8mb4 utf8mb4  否 oiuuu.png
utf8mb4 utf8mb4 gbk  
utf8mb4 gbk utf8mb4

无法确定。

默认配置插入一条数据后,切换为当前组合,第一或者第二条数据会乱码,后面出现较少

oiugu.png
utf8mb4 gbk gbk 无法确定,得不到确切的规律,多次执行时甚至会间接出现乱码与不乱码的情况。 oiugg.png
         
gbk utf8mb4 utf8mb4 oiguu.png
gbk utf8mb4 gbk oigug.png
gbk gbk utf8mb4 无法确定,得不到确切的规律,多次执行时甚至会间接出现乱码与不乱码的情况 oiggu.png
gbk gbk gbk 无法确定,得不到确切的规律,多次执行时甚至会间接出现乱码与不乱码的情况 请看下面的对比图

这里有一个很奇怪的地方是:

直 接运行 set character_set_client = gbk, character_set_connection = gbk, character_set_results = gbk, 然后执行插入,有时可以得到正常数据,有时候又是乱码的数据。

oiggg

oiggg_luan


运行 set names gbk后执行插入,则可以稳定触发乱码现象。下方为对比图:o_compare


根据以上测试结果发现,在oceanbase中当 character_set_connection 的编码与请求端的实际编码方式不匹配时,会出现数据的乱码情况,且该乱码情况也是随机出现,无法确认规律。

四、使用建议

1、 建议将 character_set_client , character_set_connection 和 character_set_results 都设置为应用使用的编码方式,在WebA应用的环境下使用时,应该将参数默认设置为 utf8mb4。具体设置方式请参考:

部署在oceanbase上需要注意字符集规范,oceanbase默认字符集为utf-8, 无特殊需求无需修改。

oceanbase中指定字符集方式:

• 在创建租户时设置字符集

º 可以在 create tenant 语句添加 charset 设置,添加 "charset=utf8"

º 可以在 OCP 中创建租户时,选择字符集为utf8

• 设置客户端(链路)字符集

º 客户端(链路)字符集是配置 client 和 server 之间交互使用的字符集设置

set global character_set_client = utf8;

set global character_set_connection = utf8;

set global character_set_results = utf8;

• 设置客户端字符集

º 使用jdbc 连接 OceanBase 数据库,utf8 链路一般在 url 里修改参数设置,添加 characterEncoding=utf8

String url = "jdbc:oceanbase://xxx.xxx.xxx.xxx:xxxx?useSSL=false&useUnicode=true&characterEncoding=utf8&connectTimeout=30000&rewriteBatchedStatements=true";

客户端和服务端字符集编码要保持一致。

五、当前采用的修复方案

1、使用jdbc初始化征信接口使用的数据库连接,避免与框架使用的数据库连接池冲突。

posted @ 2025-01-23 16:53  化云随风去  阅读(600)  评论(0)    收藏  举报