在計算機的眼中只有0和1,可是在人類世界中卻有上百種語言,每種語言又有成千上萬的文字,那麼如何在計算中表示人類世界中的這些文字呢?html
在上個世紀60年代的時候,美國首先定義了一套規則,在這個規則中一共定義了128個字符對應的二進制編碼,好比大寫的字母A是65(01000001),空格是32(00100000)等等。經過這個規則,計算機也就知道了01000001表示字母A,這也就是所謂的ASCII。在計算機中使用一個字節來存儲ASCII。mysql
很顯然,只有ASCII是不夠的,128個字符是遠遠不能表示各類語言的,這個時候各個地區也就逐漸定義了本身的編碼方式,好比在歐洲的一些國家就經過啓用ASCII編碼的最高位來拓展支持的字符,由於在ASCII中只有128個字符,一個字節有8位,那麼在ASCII中的最高位確定是0,經過啓動這個閒置的最高位就把支持的字符擴展到256個了,已經徹底能知足本身地區的需求。可是,隨着互聯網的發展,地理上隔開的世界被一根根的網線鏈接在一塊兒了,若是各個國家地區都使用本身的編碼方式,顯然是有問題的。因而就有了unicode,我的感受Unicode的做用就像他的名字同樣unique code,幾乎給世界上的每個符號都作了定義,若是你們都使用這個定義去作,那就不存在相互看不懂編碼的問題,好比:linux
到此爲止,已經解決了編碼的問題了,可是又有了新的問題。unicode只是定義了一個字符怎麼去編碼,沒有說明怎麼去存儲啊。好比「龜」在unique的代碼爲9F9F(1001111110011111)他須要2個字節,有的符號對應的編碼更大可能須要3個字節或者更多。如何區分呢?好比0001111110011111怎麼知道究竟是表示兩個ACSII字符仍是表示一個佔用2個字節的Unicod字符呢?sql
上面的這個問題就須要交給UTF-8(或者其餘)去解決了,一句話就是Unicode定義了字符的編碼,UTF-8定義了這個編碼的實現(存儲)方式,在Unicode的編碼中,最特別的一點就是他是變長的,變長的好處就是按需求佔用空間,試想一下,用3個字節去存儲一個單字符划算嗎?在UTF-8的實現規則中只有2點:數據庫
固然,這個規則不是我要了解的重點,只須要知道unicode定義了字符的編碼,UTF-8是一種實現Unicode的方式就好。bash
看一下Mysql中和字符集相關的參數服務器
mh01@3306>show variables like '%char%';
+--------------------------+---------------------------------------------------------------+
| Variable_name | Value |
+--------------------------+---------------------------------------------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/mysql-5.7.16-linux-glibc2.5-x86_64/share/charsets/ |
+--------------------------+---------------------------------------------------------------+
8 rows in set (0.00 sec)
複製代碼
根據上面的相關variable,先看看和建立對象相關的變量。 character_set_server,這個變量爲建立DB時候的默認值,Mysql在建立對象的時候有一個階梯狀的規則。若是建立database的時候沒有指定這個db的字符集,就使用character_set_server的值;若是在建立表的時候沒有指定字符集就使用這個表所在db的字符集,若是建立列的時候沒有指定字符集,就使用這個表的字符字符集。測試
這個是主要的參數,這部分的參數主要有character_set_client、character_set_connection、character_set_results這幾個ui
首先看一下character_set_client,這個定義了客戶端傳輸的字符集,用來告訴Mysql Server,客戶端使用character_set_client的值來傳輸數據。也就是說Mysql老是認爲客戶端傳輸的是用該參數對應的編碼來寫的,可是真實狀況卻不必定,若是這個參數設置錯誤,可能會致使亂碼,詳情能夠看後續的測試部分。編碼
character_set_connection,Mysql須要把字符集轉換爲character_set_connection來處理SQL語句
charcter_set_results,這個參數定義了用何種字符集返回給客戶端。 下圖是Mysql在處理客戶端和服務器通訊時候字符集的轉換方式:
轉換規則:若是 character_set_client 和 character_set_connection 同樣,或者當前的字符編碼是和ASCII兼容,而且都是ASCII範圍內的,就不轉換,其它狀況就轉。
character_set_system是一個只讀變量,說明元數據的編碼方式 character_set_database表示了當前數據庫的默認字符集,好比在使用use切換數據庫的時候,該參數會隨着當前數據庫默認字符集的改變而改變。
在通常實踐中,常常會使用set names UTF8的方式去設置相關的字符集參數,這種方式通常是同時修改了三個變量分別是
SET character_set_client = UTF8;
SET character_set_results = UTF8;
SET character_set_connection = UTF8;
複製代碼
爲何要同時這個這三個參數,若是這個三個參數不一致會發生什麼狀況。 先建立一個表,用UTF8的方式插入一些數據
CREATE TABLE `char_test` (
`id` int(11) DEFAULT NULL,
`name` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
set names utf8;
mh01@3306>insert into char_test (1,'哈哈');
mh01@3306>select id,name,hex(name) from char_test;
+------+--------+--------------+
| id | name | hex(name) |
+------+--------+--------------+
| 1 | 哈哈 | E59388E59388 |
+------+--------+--------------+
1 row in set (0.00 sec)
複製代碼
使用UTF8的終端(也就意味中客戶端的字符集都是UTF8)插入了一條數據,發現目前顯示都正確。
mh01@3306>set character_set_client=gbk;
Query OK, 0 rows affected (0.00 sec)
mh01@3306>insert into char_test values (2,'哈哈');
Query OK, 1 row affected (0.01 sec)
mh01@3306>select id,name,hex(name) from char_test;
+------+-----------+--------------------+
| id | name | hex(name) |
+------+-----------+--------------------+
| 1 | 哈哈 | E59388E59388 |
| 2 | 鍝堝搱 | E98D9DE5A09DE690B1 |
+------+-----------+--------------------+
2 rows in set (0.00 sec)
複製代碼
從測試中能夠看出,如今已經出現亂碼了。爲何?
客戶端使用的是UTF8,那麼在發送「哈哈」的時候就會使用「E59388E59388」的編碼去發送,這裏的「哈哈」佔用了6個字節。
當Mysql Server接收到這個字符的時候,根據character_set_client的值,Mysql認爲客戶端是用GBK作編碼的,這裏就出現問題了由於實際上客戶端使用的是UTF8的編碼。根據上面提到的轉換規則(character_set_client的值和character_set_connection的值不同)就會發生字符集的轉換。由於Mysql認爲這個編碼是GBK的編碼,因此就認爲「E59388E59388」是三個字符(對應的漢子就是「鍝堝搱」),而後把「鍝堝搱」三個字符轉換爲UTF8,因此最終會發現id=2的值竟然多了3字節。由於Mysql從一開始就是把UTF8的編碼當成了GBK的編碼去理解,理解錯了天然最終的存儲也就錯了。
mh01@3306>set names utf8;
Query OK, 0 rows affected (0.00 sec)
mh01@3306>set character_set_connection=gbk;
Query OK, 0 rows affected (0.00 sec)
mh01@3306>insert into char_test values (3,'哈哈');
Query OK, 1 row affected (0.01 sec)
mh01@3306>select id,name,hex(name) from char_test;
+------+-----------+--------------------+
| id | name | hex(name) |
+------+-----------+--------------------+
| 1 | 哈哈 | E59388E59388 |
| 2 | 鍝堝搱 | E98D9DE5A09DE690B1 |
| 3 | 哈哈 | E59388E59388 |
+------+-----------+--------------------+
3 rows in set (0.00 sec)
複製代碼
正常顯示,爲何?客戶端的編碼是UTF8,Mysql Server收到後根據character_set_client的設置認爲是UTF8編碼(答對了),可是由於character_set_connection的編碼是GBK,須要進行轉換。和第一個例子不同的地方是,在這裏Mysql的理解是正確的(上一個例子中Mysql是誤把「哈哈」理解爲「鍝堝搱」),因此Mysql會正確的把UTF8編碼轉換爲GBK編碼。最後,因爲表的編碼是UTF8,因此Mysql又把GBK轉換爲UTF8編碼存儲起來,這個過程無非就是作了一些無謂的轉換,可是結果是正常的。
mh01@3306>set character_set_results=gbk;
Query OK, 0 rows affected (0.00 sec)
mh01@3306>insert into char_test values (4,'哈哈');
Query OK, 1 row affected (0.01 sec)
mh01@3306>select id,name,hex(name) from char_test;
+------+--------+--------------------+
| id | name | hex(name) |
+------+--------+--------------------+
| 1 | | E59388E59388 |
| 2 | 哈哈 | E98D9DE5A09DE690B1 |
| 3 | | E59388E59388 |
| 4 | | E59388E59388 |
+------+--------+--------------------+
4 rows in set (0.00 sec)
複製代碼
由於character_set_connection、character_set_client、客戶端、表都是使用UTF8編碼,因此在存儲的時候存儲的是正確的編碼,「哈哈」=E59388E59388。由於character_set_results是GBK,因此作了一次轉換,注意此次轉換也是正常的轉換,問題出在客戶端這裏,Mysql使用GBK編碼把結果返回給客戶端,可是客戶端的使用的UTF8的,客戶端沒那麼聰明,他會認爲本身收到的都是UTF8的編碼,可是實際上他收到的編碼是GBK的編碼,因此這回是客戶端理解錯誤了,也就是顯示出了錯誤的結果。
有意思的是,爲何id=2的顯示正常?由於在id=2的插入過程當中,使用了UTF8-->錯誤理解爲GBK編碼--->轉換爲UTF8的方式,而返回的時候,由於server會將存的UTF8又給轉回GBK,而後客戶端又拿着這個GBK誤覺得是UTF8解析,實際上就是一個逆向過程,相似負負得正~