数据迁移中的字符集
数据迁移中的字符集
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>
因此在做数据迁移的时候,源目标端的配置一定要相同,否则就会导致写入异常或者失败的情况
- 点赞
- 收藏
- 关注作者
评论(0)