数据迁移中的字符集

举报
DRS技术快客 发表于 2021/04/30 16:33:56 2021/04/30
【摘要】 数据迁移中的字符集DRS数据迁移要求源库和目标库的字符集一致,否则会导致数据迁移异常或者数据不一致,原因得从字符集及其原理说起 字符与字节在计算机中,数据都是以二进制进行存储的,也就是在物理上,最小的物理存储单元是字节byte(B), 也就是8个bit位,物理上基于字节可以扩展出KB, MB, GB等在逻辑上,把一个或多个byte进行组合,来代表更多逻辑世界的东西比如:在java中用4个字...

数据迁移中的字符集

DRS数据迁移要求源库和目标库的字符集一致,否则会导致数据迁移异常或者数据不一致,原因得从字符集及其原理说起

字符与字节

在计算机中,数据都是以二进制进行存储的,也就是
在物理上,最小的物理存储单元是字节byte(B), 也就是8个bit位,物理上基于字节可以扩展出KB, MB, GB等
在逻辑上,把一个或多个byte进行组合,来代表更多逻辑世界的东西
比如:在java中用4个字节为一个单元来表示范围为2^31~2^31-1的整数(int),用8个字节为一个单元表示一个双精度浮点数(double),用2个字节为一个单元,表示一个字符char

字符集

为了用字节来表示各种文字,各个组织制定了多套字节到字符的映射关系,这些映射关系就是字符集
例如:在ascii表中用固定1个字节表示1个字符,但是ascci码表只能表示256个字符;在gbk字符集中用2个字节表示1个字符,但是gbk只能表示65535个字符,有许多汉字不一定包括进去了,其它外国文字就更不用说了;在UNICODE字符集中用2~4个字节表示一个字符,基本实现了所有语言文字使用同一套映射的功能(unicode.org)
java的字符是基于unicode的

查看字符的unicode值
System.out.println("我的uniocode编码 hex:" + Integer.toHexString('我') + " bin:" + Integer.toBinaryString('我'));
//我的uniocode编码 hex:6211 bin:110001000010001
字符编码

有些字符集只有一种字符编码,比如ascii, gbk等,byte值在什么地方都可以直接映射成对应的字符,但是UNICODE的字符集却有多种编码,因为在UNICODE中规定,用2~4个字节表示一个字符,如果文件内容全是英文的,那么每个字符用2~4个字节相对于ascii来说在存储或者传输中就会占用浪费太多的空间,为了解决这个问题,对于UNICODE出现了多种使用变长字符编码,其中最常使用的UTF8编码,分别用1-4个字节来表示一个字符。UTF8是基于UNICODE的一种编码,UTF8编码格式如下:
首字节:0xxxxxxx(单字节), 110xxxxx(2字节), 1110xxxx(3字节), 11110xxx(4字节)
剩余字节:10xxxxxx

查看一个字符的编码
for (byte b : new String(new char[]{'我'}).getBytes("UTF8")) {
    System.out.print(Integer.toHexString(b & 0xFF) + ":" + Integer.toBinaryString(b & 0xFF) + "  ");
}
//e6:11100110  88:10001000  91:10010001
for (byte b : new String(new char[]{'我'}).getBytes("GBK")) {
    System.out.print(Integer.toHexString(b & 0xFF) + ":" + Integer.toBinaryString(b & 0xFF) + "  ");
}
//ce:11001110  d2:11010010
UNICODE与UTF8的转换
Unicode符号范围      | UTF-8编码方式
(十六进制)           | (二进制)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

其中二进制中xxx部分即为unicode的内容
“我” unicode值为:110 001000 010001 utf8值为:11100110 10001000 10010001

字符排序规则

数据库中除了字符集外,还有字符排序规则,

java中的字符集

根据不同的编码,一串字节流可以翻译成不同的字符,那么想正确翻译出文件或者数据流的内容,就需要知道对应的字符集或字符编码才行

读写数据
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", 8080));
InputStreamReader netIn = new InputStreamReader(socket.getInputStream());
char[] charsNet = new char[10];
netIn.read(charsNet);

OutputStreamWriter netOut = new OutputStreamWriter(socket.getOutputStream());
netOut.write(new char[]{'我', '是', 'D', 'R', 'S'});

InputStreamReader fileIn = new InputStreamReader(new FileInputStream("/tmp/t.txt"));
char[] fileChars = new char[10];
fileIn.read(fileChars);

OutputStreamWriter fileOut = new OutputStreamWriter(new FileOutputStream("/tmp/t.txt"));
fileOut.write(new char[]{'我', '是', 'D', 'R', 'S'});

[![](file:///C:/Users/h00547913/Desktop/%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0/java%E4%B8%AD%E7%9A%84%E5%AD%97%E7%AC%A6%E9%9B%86.png)](file:///C:/Users/h00547913/Desktop/%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0/java%E4%B8%AD%E7%9A%84%E5%AD%97%E7%AC%A6%E9%9B%86.png)
写入的时候,通过CharsetDecoder把CharBuffer转换成ByteBuffer
读取的时候,通过CharsetEncoder把ByteBuffer转换成CharBuffer

错误的字符集

当我们用一个GBK的字符集去读取UTF8的byte的时候,程序不会报错,而且对于异常字符会由其它字符填充,不会保持byte的原样
注:这是因为java的String在处理字符集格式异常的时候,默认用的CharsetDecoder策略的替换(REPLACE), 另外两个是忽略(IGNORE)和报错(REPORT),如果是replace的情况下,会用一些特殊字符替换,于是byte内容就会失真

char[] chars = new char[]{'我'};
System.out.print("UTF8:\t\t\t我\t\t");
for (byte b : new String(chars).getBytes("UTF8")) {
    System.out.print(Integer.toHexString(b & 0xFF) + ":" + Integer.toBinaryString(b & 0xFF) + "\t");
}
System.out.println();
System.out.print("UTF8=>GBK:\t\t" + new String(new String(chars).getBytes("UTF8"), Charset.forName("GBK")) + "\t");
for (byte b : new String(new String(chars).getBytes("UTF8"), Charset.forName("GBK")).getBytes("GBK")) {
    System.out.print(Integer.toHexString(b & 0xFF) + ":" + Integer.toBinaryString(b & 0xFF) + "\t");
}
System.out.println();
System.out.print("GBK:\t\t\t我\t\t");
for (byte b : new String(chars).getBytes("GBK")) {
    System.out.print(Integer.toHexString(b & 0xFF) + ":" + Integer.toBinaryString(b & 0xFF) + "\t");
}
System.out.println();
System.out.print("GBK=>UTF8:\t\t" + new String(new String(chars).getBytes("GBK"), Charset.forName("UTF8")) + "\t");
for (byte b : new String(new String(chars).getBytes("GBK"), Charset.forName("UTF8")).getBytes("GBK")) {
    System.out.print(Integer.toHexString(b & 0xFF) + ":" + Integer.toBinaryString(b & 0xFF) + "\t");
}
//UTF8:			我		e6:11100110	88:10001000	91:10010001	
//UTF8=>GBK:		鎴�	e6:11100110	88:10001000	3f:111111	
//GBK:			我		ce:11001110	d2:11010010	
//GBK=>UTF8:		��	3f:111111	3f:111111	

MySQL中的字符集

不同字符集的表,对于相同的字符(char),存储的内容(byte)不同
 CREATE TABLE `t_charset_gbk` (
  `val` varchar(32)
) CHARSET=gbk;
insert into t_charset_gbk values ('我'),('是');
select val,hex(val) from t_charset_gbk;
+------+----------+
| val  | hex(val) |
+------+----------+
|| CED2     |
|| CAC7     |
+------+----------+

 CREATE TABLE `t_charset_utf8` (
  `val` varchar(32)
) DEFAULT CHARSET=utf8;
insert into t_charset_utf8 select * from t_charset_gbk;
select val,hex(val) from t_charset_utf8;
+------+----------+
| val  | hex(val) |
+------+----------+
|| E68891   |
|| E698AF   |
+------+----------+
客户端选用不同显示字符集,数据存储内容也不同
>mysql --default-character-set=utf8
mysql>  select hex('我'), '我', convert(unhex('E68891') using utf8);
+------------+-----+-------------------------------------+
| hex('我')  || convert(unhex('E68891') using utf8) |
+------------+-----+-------------------------------------+
| E68891     |||
+------------+-----+-------------------------------------+

>mysql --default-character-set=gbk
mysql> select hex('我'), '我', convert(unhex('CED2') using gbk);
+-----------+----+----------------------------------+
| hex('我') || convert(unhex('CED2') using gbk) |
+-----------+----+----------------------------------+
| CED2      |||
+-----------+----+----------------------------------+
字符集的转换

可以用convert函数来转换不同字符集的字符

mysql>  select convert(unhex('CED2') using gbk),convert(unhex('E68891') using utf8),hex(convert(convert(unhex('CED2') using gbk) using utf8));
+----------------------------------+-------------------------------------+-----------------------------------------------------------+
| convert(unhex('CED2') using gbk) | convert(unhex('E68891') using utf8) | hex(convert(convert(unhex('CED2') using gbk) using utf8)) |
+----------------------------------+-------------------------------------+-----------------------------------------------------------+
||| E68891                                                    |
+----------------------------------+-------------------------------------+-----------------------------------------------------------+
错误的字符集的时候

跟java不同,MySQL错误的字符集转换会变为空值,类似于java的报错策略,而如果格式符合要求,它会存储成乱码但会保持byte原样存储

> select convert(unhex('E68891') using gbk), convert(unhex('E68891E68891') using gbk), convert(unhex('E68891') using utf8);
+------------------------------------+------------------------------------------+-------------------------------------+
| convert(unhex('E68891') using gbk) | convert(unhex('E68891E68891') using gbk) | convert(unhex('E68891') using utf8) |
+------------------------------------+------------------------------------------+-------------------------------------+
| NULL                               | 鎴戞垜                                   ||
+------------------------------------+------------------------------------------+-------------------------------------+
字符集校验规则

当collate不同的时候,有可能会把不同的字符认为是相同的字符

mysql> select 'A'='a';
+---------+
| 'A'='a' |
+---------+
|       1 |
+---------+
1 row in set (0.00 sec)

mysql> select convert('a',CHAR CHARACTER SET utf8) COLLATE utf8_bin=convert('A', CHAR CHARACTER SET utf8) COLLATE utf8_bin;
+--------------------------------------------------------------------------------------------------------------+
| convert('a',CHAR CHARACTER SET utf8) COLLATE utf8_bin=convert('A', CHAR CHARACTER SET utf8) COLLATE utf8_bin |
+--------------------------------------------------------------------------------------------------------------+
|                                                                                                            0 |
+--------------------------------------------------------------------------------------------------------------+

一些简单的名称规则

后缀名称    | 含义
_ai        | 口音不敏感
_as        | 口音敏感
_ci        | 不区分大小写
_cs        | 区分大小写
_ks        | 假名敏感
_bin       | 二进制
默认字符集

1、创建库的时候,如果不指定库的字符集,默认用character_set_server,默认排序规则用infomation_schema.collations中对应的default值
2、创建表的时候,如是不指定表的字符集,默认用库的字符集和排序规则

mysql> set character_set_server=gbk;
Query OK, 0 rows affected (0.00 sec)

mysql> create database test1;
Query OK, 1 row affected (0.01 sec)

mysql> show create database test1;
+----------+---------------------------------------------------------------+
| Database | Create Database                                               |
+----------+---------------------------------------------------------------+
| test1    | CREATE DATABASE `test1` /*!40100 DEFAULT CHARACTER SET gbk */ |
+----------+---------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> create table t (name varchar(32));
Query OK, 0 rows affected (0.02 sec)

mysql> show create table t;
+-------+------------------------------------------------------------------------------------------+
| Table | Create Table                                                                             |
+-------+------------------------------------------------------------------------------------------+
| t     | CREATE TABLE `t` (
  `name` varchar(32) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=gbk |
+-------+------------------------------------------------------------------------------------------+

mysql> set character_set_server=utf8mb4;
Query OK, 0 rows affected (0.00 sec)

mysql> create database test3;
Query OK, 1 row affected (0.01 sec)

mysql> show create database test3;
+----------+-------------------------------------------------------------------+
| Database | Create Database                                                   |
+----------+-------------------------------------------------------------------+
| test3    | CREATE DATABASE `test3` /*!40100 DEFAULT CHARACTER SET utf8mb4 */ |
+----------+-------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> create table t (name varchar(32));
Query OK, 0 rows affected (0.03 sec)

mysql> show create table t;
+-------+----------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                 |
+-------+----------------------------------------------------------------------------------------------+
| t     | CREATE TABLE `t` (
  `name` varchar(32) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |
+-------+----------------------------------------------------------------------------------------------+

DRS数据迁移中的字符集

在DRS做数据迁移的时候,我们要求
1、源和目标库的字符集保持一致,如果字符集不一致,源表字符集在目标表字符集中没有,就会出现异常或者乱码
例如韩文“조선글”不在gbk的字符集里面,但是在unicode字符集里面

mysql> create table t_charset_gbk (val varchar(32) character set gbk);
Query OK, 0 rows affected (0.03 sec)

mysql> create table t_charset_utf8 (val varchar(32) character set utf8);
Query OK, 0 rows affected (0.02 sec)

mysql>
mysql> insert into t_charset_utf8 value('汉字'), ('조선글');
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> insert into t_charset_gbk select * from t_charset_utf8;
ERROR 1366 (HY000): Incorrect string value: '\xEC\xA1\xB0\xEC\x84\xA0...' for column 'val' at row 2

2、源和目标库的字符集校验规则一致,如果校验规则不一致,有可能会出现数据惟一约束失败的问题

mysql> create table t_collate_ci (val varchar(32) character set gbk collate gbk_chinese_ci,unique key uk_v(val));
Query OK, 0 rows affected (0.02 sec)

mysql> create table t_collate_bin (val varchar(32) character set gbk collate gbk_bin,unique key uk_v(val));
Query OK, 0 rows affected (0.03 sec)

mysql> insert into t_collate_bin values ('a'), ('A');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> insert into t_collate_ci select * from t_collate_bin;
ERROR 1062 (23000): Duplicate entry 'a' for key 'uk_v'
mysql>

因此在做数据迁移的时候,源目标端的配置一定要相同,否则就会导致写入异常或者失败的情况

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。