【勝通】mysql連線通道中的字符集和校驗規則

idba發表於2008-07-09

這裡首先需要解釋的是,我想應該就是連線通道的含義了。那什麼是連線通道呢?
所謂連線通道,就是客戶端和伺服器端保持連線的一個通道,它是邏輯上的一個概念。客戶端通過連線通道傳送sql語句到伺服器端,服務端執行,將結果再通過連線通道返回至客戶端。the connection is the pass when you connect to the server.

這個過程中,有幾個臨界點(邏輯上概念),是我們需要注意的,mysql也就在這幾個臨界點上做了文章。

1、當語句離開客戶端的時候:
從客戶端出來的,包括sql語句本身(這裡裡面就包含字串和關鍵字等了),以及character_set_client系統變數。為什麼要包含這個變數呢?這個變數的作用說明2點,也是它的作用:一是表示該語句中的字符集是使用character_set_client指定的字符集編碼的,二是通過此係統變數來告訴伺服器所傳送來的語句中的字符集編碼。
2、當伺服器端接受到客戶端的語句的時候:
mysql會使用character_set_connection/collation_connection指定的字符集以及校驗規則,將客戶端的字串,做一個從character_set_client到character_set_connection的轉換。
3、當伺服器處理好結果以後,在把結果傳給客戶端前:
mysql會先將結果轉換成character_set_results指定的字符集,然後傳回給客戶端。

當字串在mysql伺服器的時候,最終以什麼格式儲存到mysql資料庫中,這個是受到具體的資料表級別、列級別字符集設定的控制了。

從上面的介紹中,我們就知道和連線通道相關幾個引數了,他們分別是character_set_client/connection/results,可以如下檢視:

mysql> show variables like 'char%';
+--------------------------+-------------------+
| Variable_name            | Value             |
+--------------------------+-------------------+
| character_set_client     | latin1            |
| character_set_connection | latin1            |
| character_set_database   | gbk               |
| character_set_results    | latin1            |
| .........................| ......            |
+--------------------------+-------------------+
8 rows in set (0.00 sec)

mysql> show variables like 'colla%';
+----------------------+-------------------+
| Variable_name        | Value             |
+----------------------+-------------------+
| collation_connection | latin1_swedish_ci |
| collation_database   | gbk_bin           |
| collation_server     | latin1_swedish_ci |
+----------------------+-------------------+
3 rows in set (0.00 sec)

下面我們來做個實驗,來證明一下這個結果:
首先保證character_set_connection與character_set_results以及底層儲存字符集的一致性,看看character_set_client的效果。

mysql> show variables like 'char%';
+--------------------------+------------------+
| Variable_name            | Value            |
+--------------------------+------------------+
| character_set_client     | latin1           |
| character_set_connection | gbk              |
| character_set_database   | gbk              |
| character_set_results    | gbk              |
| .........................| ......           |
+--------------------------+------------------+
8 rows in set (0.00 sec)

mysql> create table t (a varchar(10));  --  這裡沒有指定字符集,就預設使用了database的字符集gbk了
Query OK, 0 rows affected (0.08 sec)

mysql> insert into t values('中國');
Query OK, 1 row affected (0.00 sec)

mysql> select * from t;
+-------+
| a     |
+-------+
| ???ú |
+-------+
1 row in set (0.00 sec)

由此可以看到由於latin1與gbk對漢字的編碼方式不一樣(或者說latin1根本就不能正確編碼漢字),在這個collection過程中,從character_set_client到character_set_connection轉換時,就把你輸入的好好的漢字轉換成亂碼了。那麼,如果讓過程不發生轉換呢?

mysql> set character_set_client=gbk;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t values('人民');
Query OK, 1 row affected (0.02 sec)

mysql> select * from t;
+-------+
| a     |
+-------+
| ???ú |
| 人民  |
+-------+
2 rows in set (0.00 sec)

可見,這裡是能正確儲存和顯示的,很簡單,因為任何轉換都沒有發生,當然就不會出現亂碼了。

接著做實驗,我們讓character_set_connection發生變化:

mysql> set character_set_connection=latin1;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t values ('共和國');
Query OK, 1 row affected (0.00 sec)

mysql> select * from t;
+-------+
| a     |
+-------+
| ???ú |
| 人民  |
| ???   |
+-------+
3 rows in set (0.00 sec)

可見,這裡character_set_client=gbk,character_set_connection=latin1,character_set_database=gbk。首先,client到connection過程中,漢字被轉換成亂碼(這個過程可能就會丟失資訊);然後儲存資料的時候,又從connection到儲存的字符集(gbk)發生一次轉換,亂碼被轉換成“更”亂碼。這裡如果connection與client的字符集有種包容性關係的話,如character_set_client=gbk, character_set_connection=utf8,character_set_results=gbk,底層儲存也是gbk編碼,由於utf8“相容”所有的字符集,故在轉換過程中不會發生資訊丟失,查詢的時候也不會是亂碼,如下:

mysql> show variables like 'char%';
+--------------------------+-----------------+
| Variable_name            | Value           |
+--------------------------+-----------------+
| character_set_client     | gbk             |
| character_set_connection | utf8            |
| character_set_database   | gbk             |
| character_set_results    | gbk             |
| .........................| ......          |
+--------------------------+-----------------+
8 rows in set (0.00 sec)

mysql> create table t (a varchar(10)) charset=gbk;
Query OK, 0 rows affected (0.05 sec)

mysql> insert into t values('中國');
Query OK, 1 row affected (0.00 sec)

mysql> select * from t;
+------+
| a    |
+------+
| 中國 |
+------+
1 rows in set (0.00 sec)

我來解釋一下這個過程,假設“中國”的gbk編碼是1234,而utf8的編碼是4321,utf8的編碼為1234的假設是“淘寶”。client的“中國”的編碼1234進入connection,仍然是1234(但實際上它的含義已經發生變化為“淘寶”),儲存的時候connection的1234編碼到表儲存也是1234,剛好正確表達了“中國”的意思,查詢返回時,由於results也是gbk,所以不發生轉換,正確顯示。很顯然,如果results又是一種與gbk不相容的字符集如latin1,查詢又會出問題,如下:

mysql> set character_set_results=latin1;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t;
+------+
| a    |
+------+
| ??   |
+------+
1 rows in set (0.00 sec)

當我們改變底層儲存的字符集的時候,會怎樣?請看如下實驗:

mysql> show variables like 'char%';
+--------------------------+---------------+
| Variable_name            | Value         |
+--------------------------+---------------+
| character_set_client     | gbk           |
| character_set_connection | gbk           |
| character_set_database   | gbk           |
| character_set_results    | gbk           |
| .........................| ......        |
+--------------------------+---------------+
8 rows in set (0.01 sec)

mysql> create table t (a varchar(10)) charset=latin1;
Query OK, 0 rows affected (0.05 sec)

mysql> insert into t values('淘寶');
Query OK, 1 row affected, 1 warning (0.02 sec)

mysql> select * from t;
+------+
| a    |
+------+
| ??   |
+------+
1 row in set (0.00 sec)

很顯然,由於latin1字符集無法儲存漢字,故出現亂碼。
下面,我們只改變results字符集,看看效果如何:

mysql> show variables like 'char%';
+--------------------------+----------------+
| Variable_name            | Value          |
+--------------------------+----------------+
| character_set_client     | gbk            |
| character_set_connection | gbk            |
| character_set_database   | gbk            |
| character_set_results    | latin1         |
| .........................| ......         |
+--------------------------+----------------+
8 rows in set (0.00 sec)

mysql> create table t (a varchar(10)) charset=gbk;
Query OK, 0 rows affected (0.08 sec)

mysql> insert into t values('淘寶');
Query OK, 1 row affected (0.00 sec)

mysql> select * from t;
+------+
| a    |
+------+
| ??   |
+------+
1 row in set (0.00 sec)

很顯然,底層為gbk編碼的字串轉換成latin1的字符集的字元,變成亂碼,傳給客戶端,再次轉換成gbk字符集,變成“更”亂碼,故顯示亂碼。

下面,我再介紹幾個“簡約”命令。
SET NAMES ‘x’,(SET NAMES ‘charset_name’ COLLATE ‘collation_name’)相當於:

SET character_set_client = x;
SET character_set_results = x;
SET character_set_connection = x;

實驗如下:

mysql> show variables like 'char%';
+--------------------------+----------------+
| Variable_name            | Value          |
+--------------------------+----------------+
| character_set_client     | gbk            |
| character_set_connection | gbk            |
| character_set_results    | latin1         |
| .........................| ......         |
+--------------------------+----------------+
8 rows in set (0.00 sec)

mysql> set names 'ascii';
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'char%';
+--------------------------+----------------+
| Variable_name            | Value          |
+--------------------------+----------------+
| character_set_client     | ascii          |
| character_set_connection | ascii          |
| character_set_results    | ascii          |
| .........................| ......         |
+--------------------------+----------------+

SET CHARACTER SET ‘x’,相當於:

SET character_set_client = x;
SET character_set_results = x;
SET collation_connection = @@collation_database;

這裡collation_connection = @@collation_database的意思就是把collation_connection的設定的collation_database的值,由於collation肯定能確定character set,故其又相當於多做了個設定:把character_set_connetion設定成character_set_database的值。請看如下實驗:

mysql> show variables like 'char%';
+--------------------------+-------------+
| Variable_name            | Value       |
+--------------------------+-------------+
| character_set_client     | latin1      |
| character_set_connection | latin1      |
| character_set_database   | gbk         |
| character_set_results    | latin1      |
| .......................  | ......      |
+--------------------------+-------------+
8 rows in set (0.00 sec)

mysql> show variables like 'collat%';
+----------------------+-------------------+
| Variable_name        | Value             |
+----------------------+-------------------+
| collation_connection | latin1_swedish_ci |
| collation_database   | gbk_bin           |
| collation_server     | latin1_swedish_ci |
+----------------------+-------------------+
3 rows in set (0.00 sec)

mysql> set character set 'ascii';
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'char%';
+--------------------------+-------------+
| Variable_name            | Value       |
+--------------------------+-------------+
| character_set_client     | ascii       |
| character_set_connection | gbk         |
| character_set_database   | gbk         |
| character_set_results    | ascii       |
| .......................  | ......      |
+--------------------------+-------------+
8 rows in set (0.00 sec)

mysql> show variables like 'collat%';
+----------------------+-------------------+
| Variable_name        | Value             |
+----------------------+-------------------+
| collation_connection | gbk_bin           |
| collation_database   | gbk_bin           |
| collation_server     | latin1_swedish_ci |
+----------------------+-------------------+
3 rows in set (0.00 sec)

通過以上的介紹,所以為了防止出現亂碼,我們可以把character_set_client,character_set_connection,character_set_database,character_set_results設定成同樣的值,把collation_connection和collation_database也設定成同樣的值,這樣就“一勞永逸”了。程式連線mysql的時候,一般都會顯示設定character_set_client的值,如java連線中,一般都有如下的一段程式碼來顯示設定這個值:

jdbc:mysql://10.1.6.174:3306/notify?connectTimeout=1000&characterEncoding=utf8

<!--

http://rdc.taobao.com/blog/dba/html/184_mysql_connect_charset_collation.html/trackback

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1384/viewspace-374818/,如需轉載,請註明出處,否則將追究法律責任。

相關文章