MySQL字符集轉換引發插入亂碼問題


根據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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM