MySQL出現亂碼的緣由php
要了解爲何會出現亂碼,咱們就先要理解:從客戶端發起請求,到MySQL存儲數據,再到下次從表取回客戶端的過程當中,哪些環節會有編碼/解碼的行爲。爲了更好的解釋這個過程,博主製做了兩張流程圖,分別對應存入和取出兩個階段。mysql
存入MySQL經歷的編碼轉換過程web
mysqlflowsql
上圖中有3次編碼/解碼的過程(紅色箭頭)。三個紅色箭頭分別對應:客戶端編碼,MySQL Server解碼,Client編碼向表編碼的轉換。其中Terminal能夠是一個Bash,一個web頁面又或者是一個APP。本文中咱們假定Bash是咱們的Terminal,即用戶端的輸入和展現界面。圖中每個框格對應的行爲以下:shell
在terminal中使用輸入法輸入windows
terminal根據字符編碼轉換成二進制流bash
二進制流經過MySQL客戶端傳輸到MySQL Server網絡
Server經過character-set-client解碼ui
判斷character-set-client和目標表的charset是否一致編碼
若是不一致則進行一次從client-charset到table-charset的一次字符編碼轉換
將轉換後的字符編碼二進制流存入文件中
從MySQL表中取出數據經歷的編碼轉換過程
mysqlflow
上圖有3次編碼/解碼的過程(紅色箭頭)。上圖中三個紅色箭頭分別對應:客戶端解碼展現,MySQL Server根據character-set-client編碼,表編碼向character-set-client編碼的轉換。
從文件讀出二進制數據流
用表字符集編碼進行解碼
將數據轉換爲character-set-client的編碼
使用character-set-client編碼爲二進制流
Server經過網絡傳輸到遠端client
client經過bash配置的字符編碼展現查詢結果
形成MySQL亂碼的緣由
1. 存入和取出時對應環節的編碼不一致
這個會形成亂碼是顯而易見的。咱們把存入階段的三次編解碼使用的字符集編號爲C1,C2,C3(圖一從左到右);取出時的三個字符集依次編號爲C1’,C2’,C3’(從左到右)。那麼存入的時候bash C1用的是UTF-8編碼,取出的時候,C1'咱們卻使用了windows終端(默認是GBK編碼),那麼結果幾乎必定是亂碼。又或者存入MySQL的時候set names utf8(C2),而取出的時候卻使用了set names gbk(C2'),那麼結果也必然是亂碼
2. 單個流程中三步的編碼不一致
即上面任意一幅圖中的同方向的三步中,只要兩步或者兩部以上的編碼有不一致就有可能出現編解碼錯誤。若是差別的兩個字符集之間沒法進行無損編碼轉換(下文會詳細介紹),那麼就必定會出現亂碼。例如:咱們的shell是UTF8編碼,MySQL的character-set-client配置成了GBK,而表結構卻又是charset=utf8,那麼毫無疑問的必定會出現亂碼。
這裏咱們就簡單演示下這種狀況
view sourceprint?
master [localhost] {msandbox} (test) > create table charset_test_utf8 (id int primary key auto_increment, char_col varchar(50)) charset = utf8;
Query OK, 0 rows affected (0.04 sec)
master [localhost] {msandbox} (test) > set names gbk;
Query OK, 0 rows affected (0.00 sec)
master [localhost] {msandbox} (test) > insert into charset_test_utf8 (char_col) values ('中文');
Query OK, 1 row affected, 1 warning (0.01 sec)
master [localhost] {msandbox} (test) > show warnings;
+---------+------+---------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+---------------------------------------------------------------------------+
| Warning | 1366 | Incorrect string value: '\xAD\xE6\x96\x87' for column 'char_col' at row 1 |
+---------+------+---------------------------------------------------------------------------+
1 row in set (0.00 sec)
master [localhost] {msandbox} (test) > select id,hex(char_col),char_col from charset_test_utf8;
+----+----------------+----------+
| id | hex(char_col) | char_col |
+----+----------------+----------+
| 1 | E6B6933FE69E83 | ???? |
+----+----------------+----------+
1 row in set (0.01 sec)
關於MySQL的編/解碼
既然系統之間是按照二進制流進行傳輸的,那直接把這串二進制流直接存入表文件就好啦。爲何在存儲以前還要進行兩次編解碼的操做呢?
Client to Server的編解碼的緣由是MySQL須要對傳來的二進制流作語法和詞法解析。若是不作編碼解析和校驗,咱們甚至無法知道傳來的一串二進制流是insert仍是update。
File to Engine的編解碼是爲知道二進制流內的分詞狀況。武漢市哪裏看精神分裂好舉個簡單的例子:咱們想要從表裏取出某個字段的前兩個字符,執行了一句形如select left(col,2) from table的語句,存儲引擎從文件讀入該column的值是E4B8ADE69687。那麼這個時候若是咱們按照GBK把這個值分割成E4B8,ADE6,9687三個字,並那麼返回客戶端的值就應該是E4B8ADE6;若是按照UTF8分割成E4B8AD,E69687,那麼就應該返回E4B8ADE69687兩個字。可見,若是在從數據文件讀入數據後,不進行編解碼的話在存儲引擎內部是沒法進行字符級別的操做的。
http://www.fnb.hk/blog/space.php?uid=34121&do=blog&id=315468