MySQL插入數(shù)據(jù)會失?。繛槭裁??
那天,我還在外面吃成都六姐的冒菜。
牛肉丸裹上麻醬后,狠狠嘬一口,都要入嘴了。
產(chǎn)品經(jīng)理突然發(fā)來消息。
"線上有些用戶不能注冊了"。
心想著"關(guān)我x事,又不是我做的模塊",放下手機(jī)。
不對,那老哥上禮拜剛離職了,想到這里,夾住毛肚的手微微顫抖。
對面繼續(xù)發(fā):"還有些用戶不能改名"。
"如果用上表情符號的話,問題必現(xiàn)"。
可以了,這下問題幾乎直接定位了。
危,速歸。
有經(jīng)驗的兄弟們很容易看出,這肯定是因為字符集的緣故。
復(fù)現(xiàn)問題
我們來簡單復(fù)現(xiàn)下這個問題。
如果你有一張數(shù)據(jù)庫表,建表sql就像下面一樣。
建表sql語句
接下來如果你插入的數(shù)據(jù)是。
insert成功case
能成功。一切正常。
但如果你插入的是
insert失敗case
就會報錯。
Incorrect string value: '\xF0\x9F\x98\x81' for column 'name' at row 1
區(qū)別在于后者多了個emoji表情。
明明也是字符串,為什么字符串里含有emoji表情,插入就會報錯呢?
我們從字符集編碼這個話題開始聊起。
編碼和字符集的關(guān)系
雖然我們平時可以在編輯器上輸入各種中文英文字母,但這些都是給人讀的,不是給計算機(jī)讀的,其實計算機(jī)真正保存和傳輸數(shù)據(jù)都是以二進(jìn)制0101的格式進(jìn)行的。
那么就需要有一個規(guī)則,把中文和英文字母轉(zhuǎn)化為二進(jìn)制,比如"debug",計算機(jī)就需要把它轉(zhuǎn)化為下圖這樣。
debug的編碼
其中d對應(yīng)十六進(jìn)制下的64,它可以轉(zhuǎn)換為01二進(jìn)制的格式。
于是字母和數(shù)字就這樣一一對應(yīng)起來了,這就是ASCII編碼格式。
它用一個字節(jié),也就是8位來標(biāo)識字符,基礎(chǔ)符號有128個,擴(kuò)展符號也是128個。
也就只能表示下英文字母和數(shù)字。
這哪里夠用。
塞牙縫都不夠。
于是為了標(biāo)識中文,出現(xiàn)了GB2312的編碼格式。為了標(biāo)識希臘語,出現(xiàn)了greek編碼格式,為了標(biāo)識俄語,整了cp866編碼格式。
這百花齊放的場面,顯然不是一個愛寫if else的程序員想看到的。
為了統(tǒng)一它們,于是出現(xiàn)了Unicode編碼格式,它用了2~4個字節(jié)來表示字符,這樣理論上所有符號都能被收錄進(jìn)去,并且它還完全兼容ASCII的編碼,也就是說,同樣是字母d,在ASCII用64表示,在Unicode里還是用64來表示。
但不同的地方是ASCII編碼用1個字節(jié)來表示,而Unicode用則兩個字節(jié)來表示。
比如下圖,同樣都是字母d,unicode比ascii多使用了一個字節(jié)。
unicode比ascii多使用一個字節(jié)
我們可以注意到,上面的unicode編碼,放在前面的都是0,其實用不上,但還占了個字節(jié),有點浪費,完全能隱藏掉。如果我們能做到該隱藏時隱藏,這樣就能省下不少空間,按這個思路,就是就有了UTF-8編碼。
編碼格式
來總結(jié)下。
按照一定規(guī)則把符號和二進(jìn)制碼對應(yīng)起來,這就是編碼。而把n多這種已經(jīng)編碼的字符聚在一起,就是我們常說的字符集。
比如utf-8字符集就是所有utf-8編碼格式的字符的合集。
字符和字符集的關(guān)系
mysql的字符集
想看下mysql支持哪些字符集。可以執(zhí)行 show charset。
數(shù)據(jù)庫支持哪些字符集
上面這么多字符集,我們只需要關(guān)注utf8和utf8mb4就夠了。
utf8和utf8mb4的區(qū)別
上面提到utf-8是在unicode的基礎(chǔ)上做的優(yōu)化,既然unicode有辦法表示所有字符,那utf-8也一樣可以表示所有字符,為了避免混淆,我在后面叫它大utf8。
而從上面mysql支持的字符集的圖里,我們看到了utf8和utf8mb4。
先說utf8mb4編碼,mb4就是most bytes 4的意思,從上圖最右邊的Maxlen可以看到,它最大支持用4個字節(jié)來表示字符,它幾乎可以用來表示目前已知的所有的字符。
再說mysql字符集里的utf8,它是數(shù)據(jù)庫的默認(rèn)字符集。但注意,此utf8非彼utf8,我們叫它小utf8字符集。為什么這么說,因為從Maxlen可以看出,它最多支持用3個字節(jié)去表示字符,按utf8mb4的命名方式,準(zhǔn)確點應(yīng)該叫它utf8mb3。
不好意思,有被嚴(yán)謹(jǐn)?shù)降男值軅儯u論區(qū)扣個"嚴(yán)謹(jǐn)"。
它就像是閹割版的utf8mb4,只支持部分字符。比如emoji表情,它就不支持。
utf8mb3和utf8mb4的關(guān)系
而mysql支持的字符集里,第三列,collation,它是指字符集的比較規(guī)則。
比如,"debug"和"Debug"是同一個單詞,但它們大小寫不同,該不該判為同一個單詞呢。
這時候就需要用到collation了。
通過SHOW COLLATION WHERE Charset = 'utf8mb4';可以查看到utf8mb4下支持什么比較規(guī)則。
utf8mb4字符集比較規(guī)則
如果collation = utf8mb4_general_ci,是指使用utf8mb4字符集的前提下,挨個字符進(jìn)行比較(general),并且不區(qū)分大小寫(_ci,case insensitice)。
這種情況下,"debug"和"Debug"是同一個單詞。
對比規(guī)則-大小寫不敏感
如果改成collation=utf8mb4_bin,就是指挨個比較二進(jìn)制位大小。
于是"debug"和"Debug"就不是同一個單詞。
對比規(guī)則-大小寫敏感
那utf8mb4對比utf8mb3有什么劣勢嗎?
我們知道數(shù)據(jù)庫表里,字段類型如果是char(2)的話,里面的2是指字符個數(shù),也就是說不管這張表用的是什么編碼的字符集,都能放上2個字符。
而char又是固定長度,為了能放下2個utf8mb4的字符,char會默認(rèn)保留2*4(maxlen=4)= 8個字節(jié)的空間。
如果是utf8mb3,則會默認(rèn)保留 2 * 3 (maxlen=3) = 6個字節(jié)的空間。也就是說,在這種情況下,utf8mb4會比utf8mb3多使用一些空間。
但這真的無關(guān)緊要,如果我不用char,用varchar就好了,varchar不是固定長度,也就沒有上面這些麻煩事了。
所以我個人認(rèn)為,utf8mb4比起 utf8mb3 幾乎沒有劣勢。
如何查看數(shù)據(jù)庫表的字符集
如果我們不知道自己的表是用的哪種字符集,可以通過下面的方式進(jìn)行查看。
查看數(shù)據(jù)庫表的字符集
再看報錯原因
到這里,我們回到文章開頭的問題。
因為數(shù)據(jù)庫表在建表的時候使用 DEFAULT CHARSET=utf8, 相當(dāng)于指定了utf8mb3字符集格式。
而在執(zhí)行insert數(shù)據(jù)的時候,又不講武德,加入了emoji表情這種utf8mb4才能支持的字符,mysql識別到這是utf8mb3不支持的字符,于是忍痛報錯。
要修復(fù)也很簡單,執(zhí)行下面的sql語句,就可以把數(shù)據(jù)庫表的字符集改成utf8mb4。
ALTER TABLE user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
答應(yīng)我,以后建表,我們都無腦選utf8mb4。
選utf8除了在char字段場景下會比utf8mb4稍微省一點空間外,幾乎沒任何好處。
這點空間省下來了能提高你的績效嗎?不能。
但如果因此炸雷了,那你號就沒了。
總結(jié)
- ASCII編碼支持?jǐn)?shù)字和字母。大佬們?yōu)榱酥С种形囊肓薌B2312編碼格式,其他國家的大佬們?yōu)榱酥С指嗾Z言和符號,也引入了相應(yīng)的編碼格式。為了統(tǒng)一這些各種編碼格式,大佬們又引入了unicode編碼格式,而utf-8則在unicode的基礎(chǔ)上做了優(yōu)化,壓縮了空間。
- mysql默認(rèn)的utf8字符集,其實只是utf8mb3,并不完整,當(dāng)插入emoji表情等特殊字符時,會報錯,導(dǎo)致插入、更新數(shù)據(jù)失敗。改成utf8mb4就好了,它能支持更多字符。
- mysql建表時如果不知道該選什么字符集,無腦選utf8mb4就行了,你會感謝我的。
最后
原本A同學(xué)設(shè)計這張表的時候非常簡單,也有字符串類型的字段,但字段含義決定了肯定不會有奇奇怪怪的字符,用utf8很合理,還省空間。
后來交接給了B同學(xué),B同學(xué)在這基礎(chǔ)上加過非常多的字段,離職前最后一個需求加的這個名稱字段,所幸并沒炸雷。最后到了我這里。
好一個擊鼓傳雷。
有點東西哦。
那么問題來了。
這樣的一個事故,復(fù)盤會一開,會掛P幾呢?