問題背景
有同事反饋在mysql上面執行一條普通的insert語句,結果報錯,
execute failed due to >>> Incorrect string value: '\xA1;offl...' for
column 'biz_info' at row 1
經過半天的折騰,終於搞清楚了來龍去脈,這裡簡單給大家分享下。為了方便說明,我將測試例子中的表和語句簡化,但不影響問題重現。
問題復現
連接字符集:UTF8
表結構:
CREATE TABLE `ggg` (
`id` int(11) DEFAULT NULL,
`c` varchar(100) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=gbk;
root@test 06:13:48>insert into ggg
values(1,concat('cardName:校園網',char(59),'offlineCardType:campus'));
Query OK, 1 row affected, 1 warning (2.51
sec)
root@test 06:14:36>show warnings\G
*************************** 1. row ***************************
Level: Warning
Code: 1366
Message: Incorrect string value: '\x91;offl...' for column 'c' at row 1
查看結果
root@test 06:16:06>select * from ggg
where id=1;
*************************** 1. row
***************************
id: 1
c: cardName:鏍″洯缃
問題分析
從報錯的結果來看,感覺是字符集轉換引起的問題,而且由於連接串的字符集是UTF8,表的字符集是GBK,更容易引起懷疑。但是,即使是字符集轉換,也不應該導致插入報錯,因為語句中的中文字符“校園網"都是普通漢字,UTF8->GBK不應該存在問題。那我們在回過頭來看看insert語句,唯一特殊的是使用了concat和char兩個函數。會不會跟這兩個函數有關系?char(59)實際是字符“;”,為了驗證想法,做了兩個實驗:
insert into ggg values(1,concat('cardName:校園網',';','offlineCardType:campus'));
2.設置連接串字符集為GBK
insert into ggg
values(1,concat('cardName:校園網',char(59),'offlineCardType:campus'));
果然,兩種情況執行結果都是OK的,查詢結果如下:
root@test 09:22:32>select * from
ggg\G
*************************** 1. row
***************************
id: 1
c: cardName:鏍″洯缃
*************************** 2. row
***************************
id: 1
c: cardName:校園網;offlineCardType:campus
*************************** 3. row
***************************
id: 1
c: cardName:校園網;offlineCardType:campus
跟蹤了下源代碼,找到了原因。char()函數返回的是一個binary類型字符串,在進行concat時,會導致'cardName:校園網'字符串到binary的轉換。轉換前,mysql將字符串‘cardName:校園網’看作是9個英文字符和3個漢字字符;轉換後,mysql將其看作是18個字節的二進制串,其中,UTF8字符集的三個漢字“校園網”占了9個字節。由於目標表字符集是GBK,因此在入庫時,還會發生一次binary到GBK的轉碼,“校園網”的二級制編碼是E6A0A1 E59BAD E58DA1,在轉碼過程中,由於GBK字符集只包含一個字節(編碼值<128)和二個字節的字符(漢字和特殊字符),“校園網”的二進制串會按照兩個字節拆分E6A0 A1E5 9BAD E58D A1,前面四個變為“鏍″洯缃”,解析到A1時,由於A1既不是單字節字符,又不能與後面的字節組成一個合法的GBK字符,導致轉換出錯。
現在就很好解釋為啥改變語句後,兩種情況都OK了。第一種情況,將char(59)直接替換成‘;’,由於不涉及UF8到binary的轉換,只有utf8到gbk轉碼的過程,這個轉換是OK的,不會出現亂碼;第二種情況,將連接串的字符集設置為GBK,那麼會涉及GBK到binary的轉換,然後再從binary轉換到GBK,由於整個轉換過程並沒有二進制數據丟失,所以也是OK的。
問題產生的兩個關鍵點
解決辦法
1.char函數提供了using語法來實現返回特定字符集的字符串,比如:char(59 using utf8)
2.保證連接字符集與表字符集一致。