mysql 的 set names 做了什麼?

本文轉自 https://blog.csdn.net/czh500/article/details/86665509

set names charset_name (如 set names utf8mb4)是以下 3 個語句的快捷方式

SET character_set_client = charset_name;
SET character_set_results = charset_name;
SET character_set_connection = charset_name;

character_set_client 是指客戶端發送過來的語句的編碼;
character_set_connection 是指mysqld收到客戶端的語句後,要轉換到的編碼;
character_set_results 是指server執行語句後,返回給客戶端的數據的編碼。

對人來說,能夠理解的是各種各樣的符號,而對計算機來說,只能理解二進制,二進制和符號之間的對應關係就是編碼。不同地域國家都有自己的一套符號集合,每個都各自用一組二進制數字表示,從而形成了不同的編碼,字符集就可以看作是編碼和符號的對應關係集合。同一個二進制數在不同的字符集下可能對應完全不一樣的字符,如在GBK字符集中,C4E3 對應的是,而在big5字符集中對應的是,而 在unicode中的編碼是4F60,在Collation-Charts 這個網站有字符集和編碼對應關係圖,可以非常直觀地看到不同編碼下二進制數和符號的對應關係。

set names 設置的3個變量就是設置mysqld和客戶端通信時,mysqld應該如何解讀client發來的字符,以及返回給客戶端什麼樣的編碼。

實驗測試

  • 環境

show variables like 'character%';
+--------------------------+-----------------------------------------------------------+
| Variable_name            | Value                                                     |
+--------------------------+-----------------------------------------------------------+
| character_set_client     | utf8mb4                                                   |
| character_set_connection | utf8mb4                                                   |
| character_set_database   | utf8mb4                                                   |
| character_set_filesystem | binary                                                    |
| character_set_results    | utf8mb4                                                   |
| character_set_server     | utf8mb4                                                   |
| character_set_system     | utf8                                                      |
| character_sets_dir       | /usr/local/mysql-5.7.20-macos10.12-x86_64/share/charsets/ |
+--------------------------+-----------------------------------------------------------+

server端的3個編碼設置都是utf8mb4。

  • 建一張表作爲測試

CREATE TABLE t1(id INT, name VARCHAR(200)) engine=InnoDB;
  • 默認配置測試

INSERT INTO t1 VALUES(0, '你好');
SELECT id, name, hex(name) FROM t1;
+------+--------+--------------+
| id   | name   | hex(name)    |
+------+--------+--------------+
|    0 | 你好   | E4BDA0E5A5BD |
+------+--------+--------------+
  • case1: 只改變 character_set_client

SET character_set_client=gbk;
INSERT INTO t1 VALUES(1, '你好');
SELECT id, name, hex(name) FROM t1;
+------+-----------+--------------------+
| id   | name      | hex(name)          |
+------+-----------+--------------------+
|    0 | 你好      | E4BDA0E5A5BD       |
|    1 | 浣犲ソ    | E6B5A3E78AB2E382BD |
+------+-----------+--------------------+
  • 返回的數據已經亂碼了
  • 數據庫裏存的確實和第一條不一樣

 

  • case2: 只改變 character_set_connection

SET names utf8mb4;
SET character_set_connection = gbk;
INSERT INTO t1 VALUES(2, '你好');
SELECT id, name, hex(name) FROM t1;
+------+-----------+--------------------+
| id   | name      | hex(name)          |
+------+-----------+--------------------+
|    0 | 你好      | E4BDA0E5A5BD       |
|    1 | 浣犲ソ    | E6B5A3E78AB2E382BD |
|    2 | 你好      | E4BDA0E5A5BD       |
+------+-----------+--------------------+
  • 返回數據正確
  • 數據庫存儲的和第一條一樣

 

  • case3: 只改變 character_set_results

SET names utf8mb4;
SET character_set_results = gbk;
INSERT INTO t1 VALUES(3, '你好');
SELECT id, name, hex(name) FROM t1;
+------+--------+--------------------+
| id   | name   | hex(name)          |
+------+--------+--------------------+
|    0 | <C4><E3><BA><C3>       | E4BDA0E5A5BD       |
|    1 | 你好   | E6B5A3E78AB2E382BD |
|    2 | <C4><E3><BA><C3>       | E4BDA0E5A5BD       |
|    3 | <C4><E3><BA><C3>       | E4BDA0E5A5BD       |
+------+--------+--------------------+
  • 返回數據錯誤
  • 數據庫存儲的和第一條一樣

分析

我們先理下字符集在整個過程中是怎樣變化的,然後再分析上面的case

  • 客戶發送請求時:
A1 客戶端發送出語句(總是以utf8)------> A2 sever收到語句解析(按character_set_client指定編碼)
                                                                    |
                                                                    v
A4 數據進入mysqld內部存儲<--------- A3 sever判斷是否需要轉換編碼(以character_set_connection 目標編碼)
  • server返回結果時:
B1 server返回結果(按character_set_results 指定編碼) ----->B2客戶端解析編碼顯示(總是以utf8)

A3步是否需要轉換編碼,代碼中的邏輯是這樣的,在sql_yacc.yy文件中:

 LEX_STRING tmp;
  THD *thd= YYTHD;
  const CHARSET_INFO *cs_con= thd->variables.collation_connection;
  const CHARSET_INFO *cs_cli= thd->variables.character_set_client;
  uint repertoire= thd->lex->text_string_is_7bit &&
                   my_charset_is_ascii_based(cs_cli) ?
                   MY_REPERTOIRE_ASCII : MY_REPERTOIRE_UNICODE30;
  if (thd->charset_is_collation_connection ||
      (repertoire == MY_REPERTOIRE_ASCII &&
       my_charset_is_ascii_based(cs_con)))
     tmp= $1;
  else
  {
    if (thd->convert_string(&tmp, cs_con, $1.str, $1.length, cs_cli))
        MYSQL_YYABORT;
  }
  $$= new (thd->mem_root) Item_string(tmp.str, tmp.length, cs_con,
                                      DERIVATION_COERCIBLE,
                                      repertoire);
  if ($$ == NULL)
     MYSQL_YYABORT;

如果 character_set_client 和 character_set_connection 一樣,或者當前的字符編碼是和ASCII兼容,並且都是ASCII範圍內的,就不轉換,其它情況就轉。

  • 對於case1

實際上客戶端發過來是UTF8的,但A2步驟server認爲客戶端的編碼是GBK的,就按GBK來解析,同時滿足A3步驟的轉換條件,所以就誤將UTF8編碼認爲是GBK,然後又給轉成了UTF8。
你好的UTF8編碼是 E4BDA0E5A5BD 6個字節,每個字符3個字節,按GBK來解析的話,因爲GBK是固定2個字節,就認爲有3個字符,然後轉成UTF8,雖然UTF8是變長的,但是這裏的3個GBK字符按值都是要佔3個字節的,轉出來一共9個字節。所以case1看到的實際存儲的值一共9個字節,比原來的大。
在返回時,是按UTF8返回的,因爲存了3個UTF8字符,所以客戶端看到的就是3個。

  • 對於case2

A2步驟沒問題,問題是出在A3,按照轉換邏輯,此時需要把UTF8轉成GBK,這裏因爲character_set_client是正確的,所以轉換的源不會識別錯,轉換成GBK自然也不會錯,後面存儲成UTF8時,再從GBK轉成UTF8,也沒錯,因爲UTF8和GBK字符集裏都包含 ‘你’和’好’,所以相互轉換也不會出錯,只是多了2次轉換。

  • 對於case3

錯在返回字符集設置的和客戶端不匹配,在返回時,server將所有字符轉成GBK的,結果客戶端一根筋的認爲是UTF8,就解析錯了。
比較有意思的是第二條記錄,即case1錯誤插進去的,顯示出來是對的。
爲什麼呢,因爲在case1中存的時候,是按 UTF8->強制解析爲GBK->然後轉爲UTF8 這個邏輯存下去的,而返回的時候,因爲server會將存的UTF8又給轉回GBK,然後客戶端又拿着這個GBK誤以爲是UTF8解析,實際上是case1的逆向過程,雖然2個方向都是錯的,最終顯示是好的,所謂的負負得正吧,哈哈。

對於case2 ,數據從客戶端進入server的時候,多做了2次轉換,最終顯示還是對的,但不是所有場景都是這樣,如下面這種

總結

character_set_client 和 character_set_results 是一定要和客戶端一致,不要依賴於負負得正,character_set_connection 設置和character_set_client 不一致,有丟失數據的風險,所以儘量也一致,總之這3個值就是要一樣,還要和客戶端一致,所以纔有了 set names 這個快捷命令。關於爲啥要有 character_set_connection 這一步轉換,筆者目前還沒看出來,以後理解了再更新,如果讀者朋友知道的話,請不吝賜教。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發佈了40 篇原創文章 · 獲贊 1 · 訪問量 3430
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章