一條詭異的insert語句


問題背景

有同事反饋在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)實際是字符“;”,為了驗證想法,做了兩個實驗:

  1. 將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. 連接字符集與表字符集不匹配
  2. 使用了char函數

解決辦法

1.char函數提供了using語法來實現返回特定字符集的字符串,比如:char(59 using utf8)

2.保證連接字符集與表字符集一致。




免責聲明!

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



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