字符集转换引发插入乱码问题

根据http://www.cnblogs.com/cchust/p/4601536.html进行验证测试

问题背景

在mysql上面执行一条普通的insert语句,结果报错:

Incorrect string value: ‘\x91;offl...‘ for column ‘c‘ at row 1

重现:

1)连接MySQL字符集是UTF8

 mysql --default-character-set=utf8 test

2)表结构

CREATE TABLE `abc` (
  `id` int(11) DEFAULT NULL,
  `c` varchar(100) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=gbk

3)SQL

jinyizhou@localhost : test 05:58:25>insert into abc values(1,‘我们‘);
Query OK, 1 row affected (0.01 sec)

jinyizhou@localhost : test 05:58:32>insert into abc values(2,concat(‘cardName:校园网‘,char(59),‘offlineCardType:campus‘));
Query OK, 1 row affected, 1 warning (0.00 sec)    #报错

jinyizhou@localhost : test 05:58:51>show warnings;
+---------+------+----------------------------------------------------------------+
| Level   | Code | Message                                                        |
+---------+------+----------------------------------------------------------------+
| Warning | 1366 | Incorrect string value: ‘\x91;offl...‘ for column ‘c‘ at row 1 |
+---------+------+----------------------------------------------------------------+
1 row in set (0.00 sec)

jinyizhou@localhost : test 05:58:55>select * from abc;
+------+-----------------------+
| id   | c                     |
+------+-----------------------+
|    1 | 我们                  |
|    2 | cardName:鏍″洯缃      |
+------+-----------------------+
2 rows in set (0.01 sec)

分析:

上面看到第2个插入乱码了,八九不离十是字符集的问题了,看warnings就知道。这里有疑问,为什么第一条插入正常,第二条就报错?2个插入的字符集都是一样的(客户端字符集),要是字符集出错,第一个也会报错。即使表的字符集是gbk,连接字符集是utf8,大家都知道utf8字符集范围要大于gbk,难道是”校内网“这3个中文超出了gbk范围?测试下:

jinyizhou@localhost : test 05:59:02>insert into abc values(1,‘校园网‘);
Query OK, 1 row affected (0.00 sec)

jinyizhou@localhost : test 06:04:12>select * from abc;
+------+-----------------------+
| id   | c                     |
+------+-----------------------+
|    1 | 我们                  |
|    2 | cardName:鏍″洯缃      |
|    1 | 校园网                |
+------+-----------------------+
3 rows in set (0.00 sec)

插入成功,上面说明和中文没有关系,其实MySQL内部会进行转换,所以问题不在这里。既然问题在第2个insert,那直接查看下是否乱码:

jinyizhou@localhost : test 06:04:14>select concat(‘cardName:校园网‘,char(59),‘offlineCardType:campus‘);
+----------------------------------------------------------------+
| concat(‘cardName:校园网‘,char(59),‘offlineCardType:campus‘)    |
+----------------------------------------------------------------+
| cardName:校园网;offlineCardType:campus                         |
+----------------------------------------------------------------+

显示没有问题,没有乱码,但是写入却出现乱码。这里注意到了一个函数:char(56),我们把这个函数转成字符串看看:

jinyizhou@localhost : test 06:10:09>insert into abc values(2,concat(‘cardName:校园网‘,‘;‘,‘offlineCardType:campus‘));
Query OK, 1 row affected (0.00 sec)

jinyizhou@localhost : test 06:14:49>select * from abc;
+------+-------------------------------------------+
| id   | c                                         |
+------+-------------------------------------------+
|    1 | 我们                                      |
|    2 | cardName:鏍″洯缃                          |
|    1 | 校园网                                    |
|    2 | cardName:校园网;offlineCardType:campus    |
+------+-------------------------------------------+

到此问题找到了,原来是char()函数的问题。那就在手册里看看char函数的相关知识:

CHAR() returns a binary string. To produce a string in a given character set, use the optional USING clause:#char()返回的是一个二进制字符串,可选择使用USING语句产生一个给出的字符集中的字符串:
mysql> SELECT CHARSET(CHAR(0x65)), CHARSET(CHAR(0x65 USING utf8));
+---------------------+--------------------------------+
| CHARSET(CHAR(0x65)) | CHARSET(CHAR(0x65 USING utf8)) |
+---------------------+--------------------------------+
| binary              | utf8                           |
+---------------------+--------------------------------+

按照上面的提醒,使用using utf8 给出一个字符串,即把二进制转换成了字符串。指定字符集后再次执行:

jinyizhou@localhost : test 06:14:52>insert into abc values(2,concat(‘cardName:校园网‘,char(59 using utf8),‘offlineCardType:campus‘));
Query OK, 1 row affected (0.00 sec)

jinyizhou@localhost : test 06:22:24>select * from abc;
+------+-------------------------------------------+
| id   | c                                         |
+------+-------------------------------------------+
|    1 | 我们                                      |
|    2 | cardName:鏍″洯缃                          |
|    1 | 校园网                                    |
|    2 | cardName:校园网;offlineCardType:campus    |
|    2 | cardName:校园网;offlineCardType:campus    |
+------+-------------------------------------------+

看到成功插入,问题现在很明显了,就是出在char()函数上面?再推敲下:

jinyizhou@localhost : test 09:38:17>insert into abc values(2,char(59));
Query OK, 1 row affected (0.00 sec)

jinyizhou@localhost : test 09:38:50>select * from abc;
+------+-------------------------------------------+
| id   | c                                         |
+------+-------------------------------------------+
|    1 | 我们                                      |
|    2 | cardName:鏍″洯缃                          |
|    1 | 校园网                                    |
|    2 | cardName:校园网;offlineCardType:campus    |
|    2 | cardName:校园网;offlineCardType:campus    |
|    2 | ;                                         |
+------+-------------------------------------------+

问题要是出在char()上面的话,上面的应该报错,那为什么没问题呢,再看看和concat一起用:

jinyizhou@localhost : test 09:38:53>insert into abc values(2,concat(‘我‘,char(59)));
Query OK, 1 row affected, 1 warning (0.00 sec)

jinyizhou@localhost : test 09:39:38>select * from abc;
+------+-------------------------------------------+
| id   | c                                         |
+------+-------------------------------------------+
|    1 | 我们                                      |
|    2 | cardName:鏍″洯缃                          |
|    1 | 校园网                                    |
|    2 | cardName:校园网;offlineCardType:campus    |
|    2 | cardName:校园网;offlineCardType:campus    |
|    2 | ;                                         |
|    2 |                                         |
+------+-------------------------------------------+

这里就看出来问题所在了,最终问题定位在concat+char上面

上面已经知道char()在官方的说明了,继续在手册里看看concat函数的相关知识:

If the arguments include any binary strings, the result is a binary string. A numeric argument is converted to its equivalent string form. to avoid that and produce a nonbinary string, you can use an explicit type cast, as in this example:
SELECT CONCAT(CAST(int_col AS CHAR), char_col);
如果所有参数均为非二进制字符串,则结果为非二进制字符串。 如果自变量中含有任一二进制字符串,则结果为一个二进制字符串。一个数字参数被转化为与之相等的二进制字符串格式;若要避免这种情况,可使用显式类型 cast, 例如: SELECT CONCAT(CAST(int_col AS CHAR), char_col)

用charset查看类型:

jinyizhou@localhost : test 10:58:21>select charset(concat(‘cardName:校园网‘,char(56),‘offlineCardType:campus‘));
+------------------------------------------------------------------------+
| charset(concat(‘cardName:校园网‘,char(56),‘offlineCardType:campus‘)) |
+------------------------------------------------------------------------+
| binary                                                                 |
+------------------------------------------------------------------------+
1 row in set (0.00 sec)

按照上面的提醒,使用cast给出一个字符串,即把二进制转换成了字符串:

jinyizhou@localhost : test 10:18:43>insert into abc values(2,concat(‘cardName:校园网‘,cast(char(59) as char),‘offlineCardType:campus‘));
Query OK, 1 row affected (0.00 sec)

jinyizhou@localhost : test 10:19:13>select * from abc;
+------+-------------------------------------------+
| id   | c                                         |
+------+-------------------------------------------+
|    1 | 我们                                      |
|    2 | cardName:鏍″洯缃                          |
|    1 | 校园网                                    |
|    2 | cardName:校园网;offlineCardType:campus    |
|    2 | cardName:校园网;offlineCardType:campus    |
|    2 | ;                                         |
|    2 | 鎴                                        |
|    2 | cardName:校园网;offlineCardType:campus    |
+------+-------------------------------------------+

看到成功插入。说到这里大家都知道原因了:

char()函数返回的是一个二进制,concat()函数里只要有一个参数是二进制,则结果就是一个二进制。由于把一个二进制的值转成十六进制再写入到表,所以在插入的时候就会出现乱码,为什么不报错呢?因为转换出来的编码在字符集里找不到,虽然不报错,但会变成乱码。乱码的原因是:char()函数返回的是一个binary类型字符串,在进行concat时,会导致‘cardName:校园网‘字符串到binary的转换,整个结果返回是一个binary。UTF8字符集的三个汉字“校园网”占了9个字节。由于目标表字符集是GBK,因此在入库时,还会发生一次binary到GBK的转码,此时由于utf8和gbk字符集的范围不一样导致转换出错。要是表的字符集是utf8,则不会报错。

解决:

1)直接把char函数用其转换好的结果表示:如用";"来代替char(56)。

2)直接转化成字符串:char(59 using utf8) 来替换char(56)。

3)直接转化成字符串:cast(char(59) as char) 来代替char(56)。

4)直接把表的字符集改成utf8。

总结:

MySQL乱码的问题有很多情况,大致的原理可以看 http://www.cnblogs.com/zhoujinyi/p/4618887.html。总的一句话就是保证客户端,服务端和表的字符集一致,尽量避免不一致引发的问题。

参考:

http://www.cnblogs.com/cchust/p/4601536.html

http://www.cnblogs.com/zhoujinyi/p/4618887.html

时间: 2024-10-29 10:46:20

字符集转换引发插入乱码问题的相关文章

PHP函数imagefttext( )因--enable-gd-jis-conv引发中文乱码解决方案

阐述 最近几天与公司的PHP开发人员测试即将上线的WEB站点(致难忘的青春岁月:http://hd.gfan.com),在内网测试环境没有任何问题,但在线上测试时,发现通过PHP的GD函数 imagefttext()引用FreeType字体将文本信息写入图像时,出现中文乱码:对此问题,我们在整个排查过程中,总结了三个能引起中文乱码的原因,如下: 1.Linux系统字符集是否支持中文? [email protected]:~#locale LANG=en_US.UTF-8 LANGUAGE=en_

java中文编码(字符集)分析-中文乱码分析及解决方案

 注:本文部分内容摘自网络,摘抄内容版权归原作者所有.  1.      背景知识 1.1.     Http协议 1.1.1.   URL和URI 1.1.2.   媒体类型定义 HTTP 在 Content-Type(14.17 节)和 Accept(14.1 节)头部域中使用因特网媒体类型 [17],为了提供打开和可扩展的数据类型和类型协议. media-type = type "/" subtype *( ";" parameter ) type = t

libiconv字符集转换库在C#中的使用

<libiconv字符集转换库使用方法>一文中说到了libiconv可以实现不同字符集的转换.比如GBK转BIG5等.在项目中因为需要,找到这个库.可是这个库在C#中没有很好的支持.不过,想着既然是C++的库,那只要动态加载DLL的接口就好了.可是调用并不顺利,传进去的IntPtr或者byte数组总是拿不到数据.后面回到了C++的方式去调用,几经调试,总算找到了原因. 是iconv接口在转换完成后,指针的位置往后移了.而在C#中调用DLL后回来的指针,已经是移动后的,所以拿不到所要的数据. 经

中文参数引发的乱码问题

新手一枚,使用spring MVC框架开发项目 路径中文传输变乱码 找到的解决方法 JS URL传中文参数引发的乱码问题 解决方法如下: 1.在JS里对中文参数进行两次转码  代码如下: var login_name = document.getElementById("loginname").value; login_name = encodeURI(login_name); login_name = encodeURI(login_name);//必须加密两次 或 window.l

iconv_open函数申请一个字符集转换的描述符

iconv_open函数 iconv_open函数 iconv_open——申请一个字符集转换的描述符 #include <iconv.h> iconv_t iconv_open(const char* tocode,const char* fromcode) 描述: iconv_open()函数申请一个转换描述符,转换字符序列从编码fromcode到编码tocode 转换描述符包含转换状态,调用iconv_open创建以后,转换状态处于初始状态,调用iconv函数以后改变转换描述符的转换状态

Python任意字符集转换

在python处理文本的过程中,经常会有文本字符集转换的情况, 而我们希望用一个方法,不用关心文本原本的字符集是什么样的,直接转换成想要的任何字符集就可以了. 方法一: import chardet def convert_encoding(data,new_coding='UTF-8'): # 任意字符集转换 encoding = chardet.detect(data)['encoding'] if new_coding.upper() != encoding.upper(): data =

MySQL中文乱码处理_字符集转换处理

-- 中文乱码修复 -- 查看MySQL服务参数设置mysql> show variables like '%character%';+--------------------------+----------------------------------+| Variable_name | Value |+--------------------------+----------------------------------+| character_set_client | utf8 ||

JS URL传中文参数引发的乱码问题

今天的项目中碰到了一个乱码问题,从JS里传URL到服务器,URL中有中文参数,服务器里读出的中文参数来的全是“?”,查了网上JS编码相关资料得以解决. 解决方法一: 1.在JS里对中文参数进行两次转码 var login_name = document.getElementById("loginname").value; login_name = encodeURI(login_name); login_name = encodeURI(login_name); 2.在服务器端对参数进

Oracle字符集转换

这几天在工作中碰到一个字符乱码的问题,发现在cmd窗口的sqlplus中直接update一个中文和使用@调用一个文件作同样更新的时候,存储的结果 竟不一样.一时比较迷惑,对Oracle如何处理各个字符集的问题不是很清楚.特此通过一些资料和实验总结,系统学习一下Oracle中字符集的相关知 识. 一. 字符集的基础知识:在网络上已有不少网友对字符集进行了研究,个人觉得有几个不错的网站可以参考http://blog.csdn.net/tianlesoftware/article/details/49