字符集轉(zhuǎn)換概覽
我們需要說明文字實際上是面向人類的概念。計算機不關(guān)心什么是字符,只關(guān)心什么是與該字符相對應(yīng)的字節(jié)編碼。(約翰肯尼迪)。
對于一個字節(jié)序列,計算機怎么知道它是使用什么字符集編碼的呢?計算機不知道,所以其實在計算機中表示一個字符串時,都需要附帶上它對應(yīng)的字符集是什么,就像這樣(以C++語言為例):class String {
byte* content;
CHARSET_INFO* charset;
}
比方說我們現(xiàn)在有一個以utf8
字符集編碼的漢字'我'
,那么意味著計算機中不僅僅要存儲'我'
的utf8編碼0xE68891
,還需要存儲它是使用什么字符集編碼的信息,就像這樣:
{
content: 0xE68891;
charset: utf8;
}
計算機內(nèi)部包含將一種字符集轉(zhuǎn)換成另一種字符集的函數(shù)庫,也就是某個字符在某種字符集下的編碼可以很順利的轉(zhuǎn)換為另一種字符集的編碼,我們將這個過程稱之為字符集轉(zhuǎn)換
。比方說我們可以將上述采用utf8字符集編碼的字符'我',轉(zhuǎn)換成gbk字符集編碼的形式,就變成了這樣:
{
content: 0xCED2;
charset: gbk;
}
小貼士:我們上邊所說的'編碼'可以當作動詞,也可以當作名詞來理解。當作動詞的話意味著將一個字符映射到一個字節(jié)序列的過程,當作名詞的話意味著一個字符對應(yīng)的字節(jié)序列。大家根據(jù)上下文理解'編碼'的含義。
mysql客戶端和服務(wù)器是怎么通信的
MySQL客戶端發(fā)送給服務(wù)器的請求以及服務(wù)器發(fā)送給客戶端的響應(yīng)其實都是遵從一定格式的,我們把它們通信過程中事先規(guī)定好的數(shù)據(jù)格式稱之為MySQL通信協(xié)議,這個協(xié)議是公開的,我們可以簡單的使用wireshark等截包軟件十分方便的分析這個通信協(xié)議。在了解了這個通信協(xié)議之后,我們甚至可以動手制作自己的客戶端軟件。市面上的MySQL客戶端軟件多種多樣,我們并不想各個都分析一下,現(xiàn)在只選取在MySQL安裝目錄的bin
目錄下自帶的mysql
程序(此處的mysql
程序指的是名字叫做mysql
的一個可執(zhí)行文件),如圖所示:
我們在計算機的黑框框中執(zhí)行該可執(zhí)行文件,就相當于啟動了一個客戶端,就像這樣:
小貼士:我們這里的'黑框框'指的是Windows操作系統(tǒng)中的cmd.exe或者UNIX系統(tǒng)中的Shell。
我們通常是按照下述步驟使用MySQL的:
啟動客戶端并連接到服務(wù)器
客戶端發(fā)送請求。
服務(wù)器接收到請求
服務(wù)器處理請求
服務(wù)器處理請求完畢生成對該客戶端的響應(yīng)
客戶端接收到響應(yīng)
下邊我們就詳細分析一下每個步驟中都影響到了哪些字符集。
啟動客戶端并連接到服務(wù)器過程
每個MySQL客戶端都維護者一個客戶端默認字符集,這個默認字符集按照下邊的套路進行取值:
自動檢測操作系統(tǒng)使用的字符集
MySQL客戶端會在啟動時檢測操作系統(tǒng)當前使用的字符集,并按照一定規(guī)則映射成為MySQL支持的一些字符集(通常是操作系統(tǒng)當前使用什么字符集,就映射為什么字符集,有一些特殊情況,比方說如果操作系統(tǒng)當前使用的是ascii字符集,會被映射為latin1字符集)。
當我們使用UNIX操作系統(tǒng);
此時會調(diào)用操作系統(tǒng)提供的nl_LANGinfo(CODESET)
函數(shù)來獲取操作系統(tǒng)當前正在使用的字符集,而這個函數(shù)的結(jié)果是依賴LC_ALL
、LC_CTYPE
、LANG
這三個環(huán)境變量的。其中LC_ALL
的優(yōu)先級比LC_CTYPE
高,LC_CTYPE
的優(yōu)先級比LANG
高。也就是說如果設(shè)置了LC_ALL
,不論有沒有設(shè)置LC_CTYPE
或者LANG
,最終都以LC_ALL
為準;如果沒有設(shè)置LC_ALL
,那么就以LC_CTYPE
為準;如果既沒有設(shè)置LC_ALL
也沒有設(shè)置LC_CTYPE
,就以LANG
為準。比方說我們將環(huán)境變量LC_ALL
設(shè)置為z
,就像這樣:
export LC_ALL=z
那么我們在黑框框里啟動MySQL客戶端時,MySQL客戶端就會檢測到這個操作系統(tǒng)使用的是utf8
字符集,并將客戶端默認字符集設(shè)置為utf8
。當然,如果這三個環(huán)境變量都沒有設(shè)置,那么nl_langinfo(CODESET)
函數(shù)將返回操作系統(tǒng)默認的字符集,比方說在我的macOS 10.15.3
操作系統(tǒng)中,該默認字符集為:
US-ASCII
此時MySQL客戶端的默認字符集將會被設(shè)置為latin1
。另外,我們這里還需要強調(diào)一下,我們使用的黑框框展示字符的時候有一個自己特有的字符集,比如在我的mac上使用iTerm2
作為黑框框,我們可以打開:Preferences->Profiles->Terminal選項卡,可以看到iTerm2
使用utf8
來展示字符:
我們一般要把黑框框展示字符時采用的編碼和操作系統(tǒng)當前使用的編碼保持一致,如果不一致的話,我們敲擊的字符可能都無法顯示到屏幕上。比方說如果我此時把LC_ALL
屬性設(shè)置成GBK
,那么我們再向黑框框上輸入漢字的話,屏幕都不會顯示了,就像這樣(如下圖所示,我敲擊了漢字'我'
的效果):
當我們使用Windows操作系統(tǒng)時
此時會調(diào)用操作系統(tǒng)提供的GetConsoleCP
函數(shù)來獲取操作系統(tǒng)當前正在使用的字符集。在Windows里,會把當前cmd.exe使用的字符集映射到一個數(shù)字,稱之為代碼頁(英文名:code page
),我們可以通過右鍵點擊cmd.exe
標題欄,然后點擊屬性->選項,如下圖所示,當前代碼頁
的值是936,代表當前cmd.exe使用gbk字符集:
更簡便一點,我們可以運行chcp
命令直接看到當前code page是什么:
這樣我們在黑框框里啟動MySQL客戶端時,MySQL客戶端就會檢測到這個操作系統(tǒng)使用的是gbk
字符集,并將客戶端默認字符集設(shè)置為gbk
。我們前邊提到的utf8字符集對應(yīng)的代碼頁為65001
,如果當前代碼頁的值為65001,之后再啟動MySQL客戶端,那么客戶端的默認字符集就會變成utf8
。如果MySQL不支持自動檢測到的操作系統(tǒng)當前正在使用的字符集,或者在某些情況下不允許自動檢測的話,MySQL會使用它自己的內(nèi)建的默認字符集作為客戶端默認字符集。這個內(nèi)建的默認字符集在MySQL 5.7
以及之前的版本中是latin1
,在MySQL 8.0
中修改為了utf8mb4
。使用了default-character-set
啟動參數(shù)如果我們在啟動MySQL客戶端是使用了default-character-set
啟動參數(shù),那么客戶端的默認字符集將不再檢測操作系統(tǒng)當前正在使用的字符集,而是直接使用啟動參數(shù)default-character-set
所指定的值。比方說我們使用如下命令來啟動客戶端:
mysql --default-character-set=utf8
那么不論我們使用什么操作系統(tǒng),操作系統(tǒng)目前使用的字符集是什么,我們都將會以utf8作為MySQL客戶端的默認字符集。
在確認了MySQL客戶端默認字符集之后,客戶端就會向服務(wù)器發(fā)起登陸請求,傳輸一些諸如用戶名、密碼等信息,在這個請求里就會包含客戶端使用的默認字符集是什么的信息,服務(wù)器收到后就明白了稍后客戶端即將發(fā)送過來的請求是采用什么字符集編碼的,自己生成的響應(yīng)應(yīng)該以什么字符集編碼了(劇透一下:其實服務(wù)器在明白了客戶端使用的默認字符集之后,就會將character_set_client
、character_set_connection
以及character_set_result
這幾個系統(tǒng)變量均設(shè)置為該值)。
客戶端發(fā)送請求
登陸成功之后,我們就可以使用鍵盤在黑框框中鍵入我們想要輸入的MySQL語句,輸入完了之后就可以點擊回車鍵將該語句當作請求發(fā)送到服務(wù)器,可是客戶端發(fā)送的語句(本質(zhì)是個字符串)到底是采用什么字符集編碼的呢?這其實涉及到應(yīng)用程序和操作系統(tǒng)之間的交互,我們的MySQL客戶端程序其實是一個應(yīng)用程序,它從黑框框中讀取數(shù)據(jù)其實是要調(diào)用操作系統(tǒng)提供的讀取接口。在不同的操作系統(tǒng)中,調(diào)用的讀取接口其實是不同的,我們還得分情況討論一下:
對于UNIX操作系統(tǒng)來說
在我們使用某個輸入法軟件向黑框框中輸入字符時,該字符采用的編碼字符集其實是操作系統(tǒng)當前使用的字符集。比方說當前LC_ALL
環(huán)境變量的值為z
,那么意味著黑框框中的字符其實是使用utf8字符集進行編碼。稍后MySQL客戶端程序?qū)⒄{(diào)用操作系統(tǒng)提供的read函數(shù)從黑框框中讀取數(shù)據(jù)(其實就是所謂的從標準輸入流中讀取數(shù)據(jù)),所讀取的數(shù)據(jù)其實就是采用utf8字符集進行編碼的字節(jié)序列,稍后將該字節(jié)序列作為請求內(nèi)容發(fā)送到服務(wù)器。這樣其實會產(chǎn)生一個問題,如果客戶端的默認字符集和操作系統(tǒng)當前正在使用的字符集不同,那么將產(chǎn)生比較尷尬的結(jié)果。比方說我們在啟動客戶端是攜帶了--default-character-set=gbk
的啟動參數(shù),那么客戶端的默認字符集將會被設(shè)置成gbk,而如果操作系統(tǒng)此時采用的字符集是utf8。比方說我們的語句中包含漢字'我'
,那么客戶端調(diào)用read
函數(shù)讀到的字節(jié)序列其實是0xE68891
,從而將0xE68891
發(fā)送到服務(wù)器,而服務(wù)器認為客戶端發(fā)送過來的請求都是采用gbk進行編碼的,這樣就會產(chǎn)生問題(當然,這僅僅是發(fā)生亂碼問題的前奏,并不意味著產(chǎn)生亂碼,亂碼只有在最后一步,也就是客戶端應(yīng)用程序?qū)⒎?wù)器返回的數(shù)據(jù)寫到黑框框里時才會發(fā)生)。對于Windows操作系統(tǒng)來說
在Windows操作系統(tǒng)中,從黑框框中讀取數(shù)據(jù)調(diào)用的是Windows提供的ReadConsoleW
函數(shù)。在該函數(shù)執(zhí)行后,MySQL客戶端會得到一個寬字符數(shù)組(其實就是一組16位的UNICODE),然后客戶端需要把該寬字符數(shù)組再次轉(zhuǎn)換成客戶端使用的默認字符集編碼的字節(jié)序列,然后才將該字節(jié)序列作為請求的內(nèi)容發(fā)送到服務(wù)器。這樣在UNIX操作系統(tǒng)中可能產(chǎn)生的問題,在Windows系統(tǒng)中卻可以避免。比方說我們在啟動客戶端是攜帶了--default-character-set=gbk
的啟動參數(shù),那么客戶端的默認字符集將會被設(shè)置成gbk,假如此時操作系統(tǒng)采用的字符集是utf8。比方說我們的語句中包含漢字'我'
,那么客戶端調(diào)用ReadConsoleW
函數(shù)先讀到一個代表著我
字的寬字符數(shù)組,之后又將其轉(zhuǎn)換為客戶端的默認字符集,也就是gbk字符集編碼的數(shù)據(jù)0xCED2
,然后將0xCED2
發(fā)送到服務(wù)器。此時服務(wù)器也認為客戶端發(fā)送過來的請求就是采用gbk進行編碼的,這樣就完全正確了~
服務(wù)器接收請求
服務(wù)器接收到到的請求本質(zhì)上就是一個字節(jié)序列,服務(wù)器將其看作是采用系統(tǒng)變量character_set_client
代表的字符集進行編碼的字節(jié)序列。character_set_client
是一個SESSION級別的系統(tǒng)變量,也就是說每個客戶端和服務(wù)器建立連接后,服務(wù)器都會為該客戶端維護一個單獨的character_set_client
變量,每個客戶端在登錄服務(wù)器的時候都會將客戶端的默認字符集通知給服務(wù)器,然后服務(wù)器設(shè)置該客戶端專屬的character_set_client
。我們可以使用SET命令單獨修改character_set_client
對應(yīng)的值,就像這樣:SET character_set_client=gbk;
需要注意的是,character_set_client
對應(yīng)的字符集一定要包含請求中的字符,比方說我們把character_set_client
設(shè)置成ascii
,而請求中發(fā)送了一個漢字'我'
,將會發(fā)生這樣的事情:
mysql> SET character_set_client=ascii;
Query OK, 0 rows affected sec)
mysql> SHOW VARIABLES LIKE 'character%';
+--------------------------+------------------------------------------------------+
| Variable_name | Value |
+--------------------------+------------------------------------------------------+
| character_set_client | ascii |
| 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/Cellar/mysql |
+--------------------------+------------------------------------------------------+
8 rows in set sec)
mysql> SELECT '我';
+-----+
| ??? |
+-----+
| ??? |
+-----+
1 row in set, 1 warning sec)
mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
Level: Warning
Code: 1300
Message: Invalid ascii character string: '\xE6\x88\x91'
1 row in set sec)
如圖所示,最后提示了'E6、88、91'
并不是正確的ascii字符。
小貼士:可以將character_set_client設(shè)置為latin1,看看還會不會報告WARNINGS,以及為什么~
服務(wù)器處理請求
服務(wù)器在處理請求時會將請求中的字符再次轉(zhuǎn)換為一種特定的字符集,該字符集由系統(tǒng)變量character_set_connection
表示,該系統(tǒng)變量也是SESSION級別的。每個客戶端在登錄服務(wù)器的時候都會將客戶端的默認字符集通知給服務(wù)器,然后服務(wù)器設(shè)置該客戶端專屬的character_set_connection
。不過我們之后可以通過SET命令單獨修改這個character_set_connection
系統(tǒng)變量。比方說客戶端發(fā)送給服務(wù)器的請求中包含字節(jié)序列0xE68891
,然后服務(wù)器針對該客戶端的系統(tǒng)變量character_set_client
為utf8
,那么此時服務(wù)器就知道該字節(jié)序列其實是代表漢字'我'
,如果此時服務(wù)器針對該客戶端的系統(tǒng)變量character_set_connection
為gbk,那么在計算機內(nèi)部還需要將該字符轉(zhuǎn)換為采用gbk字符集編碼的形式,也就是0xCED2
。有同學(xué)可能會想這一步有點兒像脫了褲子放屁的意思,但是大家請考慮下邊這個查詢語句:
mysql> SELECT 'a' = 'A';
請問大家這個查詢語句的返回結(jié)果應(yīng)該是TRUE還是FALSE?其實結(jié)果是不確定。這是因為我們并不知道比較兩個字符串的大小到底比的是什么!我們應(yīng)該從兩個方面考慮:
考慮一:這些字符串是采用什么字符集進行編碼的呢?
- 考慮二:在我們確定了編碼這些字符串的字符集之后,也就意味著每個字符串都會映射到一個字節(jié)序列,那么我們怎么比較這些字節(jié)序列呢,是直接比較它們二進制的大小,還是有別的什么比較方式?比方說
'a'
和'A'
在utf8字符集下的編碼分別為0x61
和0x41
,那么'a' = 'A'
是應(yīng)該直接比較0x61
和0x41
的大小呢,還是將0x61
減去32之后再比較大小呢?其實這兩種比較方式都可以,每一種比較方式我們都稱作一種比較規(guī)則
(英文名:collation
)。
MySQL
中支持若干種字符集,我們可以使用SHOW CHARSET
命令查看,如下圖所示(太多了,只展示幾種,具體自己運行一下該命令):
mysql> SHOW CHARSET;
+----------+---------------------------------+---------------------+--------+
| Charset | Description | Default collation | Maxlen |
+----------+---------------------------------+---------------------+--------+
| big5 | Big5 Traditional Chinese | big5_chinese_ci | 2 |
| latin1 | cp1252 West European | latin1_swedish_ci | 1 |
| latin2 | ISO 8859-2 Central European | latin2_general_ci | 1 |
| ascii | US ASCII | ascii_general_ci | 1 |
| gb2312 | GB2312 Simplified Chinese | gb2312_chinese_ci | 2 |
| gbk | GBK Simplified Chinese | gbk_chinese_ci | 2 |
| utf8 | UTF-8 Unicode | utf8_general_ci | 3 |
| utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | 4 |
| utf16 | UTF-16 Unicode | utf16_general_ci | 4 |
| utf16le | UTF-16LE Unicode | utf16le_general_ci | 4 |
| utf32 | UTF-32 Unicode | utf32_general_ci | 4 |
| binary | Binary pseudo charset | binary | 1 |
| gb18030 | China National Standard GB18030 | gb18030_chinese_ci | 4 |
+----------+---------------------------------+---------------------+--------+
41 rows in set sec)
其中每一種字符集又對應(yīng)著若干種比較規(guī)則,我們以utf8字符集為例(太多了,也只展示幾個):
mysql> SHOW COLLATION WHERE Charset='utf8';
+--------------------------+---------+-----+---------+----------+---------+
| Collation | Charset | Id | Default | Compiled | Sortlen |
+--------------------------+---------+-----+---------+----------+---------+
| utf8_general_ci | utf8 | 33 | Yes | Yes | 1 |
| utf8_bin | utf8 | 83 | | Yes | 1 |
| utf8_unicode_ci | utf8 | 192 | | Yes | 8 |
| utf8_icelandic_ci | utf8 | 193 | | Yes | 8 |
| utf8_latvian_ci | utf8 | 194 | | Yes | 8 |
| utf8_romanian_ci | utf8 | 195 | | Yes | 8 |
+--------------------------+---------+-----+---------+----------+---------+
27 rows in set sec)
其中utf8_general_ci
是utf8字符集默認的比較規(guī)則,在這種比較規(guī)則下是不區(qū)分大小寫的,不過utf8_bin
這種比較規(guī)則就是區(qū)分大小寫的。在我們將請求中的字節(jié)序列轉(zhuǎn)換為character_set_connection
對應(yīng)的字符集編碼的字節(jié)序列后,也要配套一個對應(yīng)的比較規(guī)則,這個比較規(guī)則就由collation_connection
系統(tǒng)變量來指定。我們現(xiàn)在通過SET命令來修改一下和collation_connection
的值分別設(shè)置為utf8
和utf8_general_ci
,然后比較一下'a'
和'A'
:
mysql> SET character_set_connection=utf8;
Query OK, 0 rows affected sec)
mysql> SET collation_connection=utf8_general_ci;
Query OK, 0 rows affected sec)
mysql> SELECT 'a' = 'A';
+-----------+
| 'a' = 'A' |
+-----------+
| 1 |
+-----------+
1 row in set sec)
可以看到在這種情況下這兩個字符串就是相等的。
我們現(xiàn)在通過SET命令來修改一下和collation_connection
的值分別設(shè)置為utf8
和utf8_bin
,然后比較一下'a'
和'A'
:
mysql> SET character_set_connection=utf8;
Query OK, 0 rows affected sec)
mysql> SET collation_connection=utf8_bin;
Query OK, 0 rows affected sec)
mysql> SELECT 'a' = 'A';
+-----------+
| 'a' = 'A' |
+-----------+
| 0 |
+-----------+
1 row in set sec)
可以看到在這種情況下這兩個字符串就是不相等的。
當然,如果我們并不需要單獨指定將請求中的字符串采用何種字符集以及比較規(guī)則的話,并不用太關(guān)心character_set_connection
和collation_connection
設(shè)置成啥,不過需要注意一點,就是character_set_connection
對應(yīng)的字符集必須包含請求中的字符。
服務(wù)器處理請求完畢生成對該客戶端的響應(yīng)
為了故事的順利發(fā)展,我們先創(chuàng)建一個表:
CREATE TABLE t (
c VARCHAR(100)
) ENGINE=INNODB CHARSET=utf8;
然后向這個表插入一條記錄:
INSERT INTO t VALUE('我');
現(xiàn)在這個表中的數(shù)據(jù)就如下所示:
mysql> SELECT * FROM t;
+------+
| c |
+------+
| 我 |
+------+
1 row in set sec)
我們可以看到該表中的字段其實是使用utf8
字符集編碼的,所以底層存放格式是:0xE68891
,將它讀出后需要發(fā)送到客戶端,是不是直接將0xE68891
發(fā)送到客戶端呢?這可不一定,這個取決于character_set_result
系統(tǒng)變量的值,該系統(tǒng)變量也是一個SESSION級別的變量。服務(wù)器會將該響應(yīng)轉(zhuǎn)換為character_set_result
系統(tǒng)變量對應(yīng)的字符集編碼后的字節(jié)序列發(fā)送給客戶端。每個客戶端在登錄服務(wù)器的時候都會將客戶端的默認字符集通知給服務(wù)器,然后服務(wù)器設(shè)置該客戶端專屬的character_set_result
。我們也可以使用SET命令來設(shè)置character_set_result
的值。不過也需要注意,character_set_result
對應(yīng)的字符集應(yīng)該包含響應(yīng)中的字符。這里再強調(diào)一遍,character_set_client
、character_set_connection
和character_set_result
這三個系統(tǒng)變量是服務(wù)器的系統(tǒng)變量,每個客戶端在與服務(wù)器建立連接后,服務(wù)器都會為這個連接維護這三個變量,如圖所示(我們假設(shè)連接1的這三個變量均為utf8
,連接1的這三個變量均為gbk
,連接1的這三個變量均為ascii
,):
一般情況下character_set_client
、character_set_connection
和character_set_result
這三個系統(tǒng)變量應(yīng)該和客戶端的默認字符集相同,SET names
命令可以一次性修改這三個系統(tǒng)變量:
SET NAMES 'charset_name'
該語句和下邊三個語句等效:
SET character_set_client = charset_name;
SET character_set_results = charset_name;
SET character_set_connection = charset_name;
不過這里需要大家特別注意,SET names
語句并不會改變客戶端的默認字符集!
客戶端接收到響應(yīng)
客戶端收到的響應(yīng)其實仍然是一個字節(jié)序列??蛻舳耸侨绾螌⑦@個字節(jié)序列寫到黑框框中的呢,這又涉及到應(yīng)用程序和操作系統(tǒng)之間的一次交互。
- 對于UNIX操作系統(tǒng)來說,MySQL客戶端向黑框框中寫入數(shù)據(jù)使用的是操作系統(tǒng)提供的
fputs
、putc
或者fwrite
函數(shù),這些函數(shù)基本上相當于直接就把接收到的字節(jié)序列寫到了黑框框中(請注意我們用詞:'基本上相當于'
,其實內(nèi)部還會做一些工作,但是我們這里就不想再關(guān)注這些細節(jié)了)。此時如果該字節(jié)序列實際的字符集和黑框框展示字符所使用的字符集不一致的話,就會發(fā)生所謂的亂碼(大家注意,這個時候和操作系統(tǒng)當前使用的字符集沒啥關(guān)系)。比方說我們在啟動MySQL客戶端的時候使用了--default-character-set=gbk
的啟動參數(shù),那么服務(wù)器的character_set_result
變量就是gbk。然后再執(zhí)行SELECT * FROM t
語句,那么服務(wù)器就會將字符'我'
的gbk編碼,也就是0xCDE2
發(fā)送到客戶端,客戶端直接把這個字節(jié)序列寫到黑框框中,如果黑框框此時采用utf8字符集展示字符,那自然就會發(fā)生亂碼。 - 對于Windows操作系統(tǒng)來說,MySQL客戶端向黑框框中寫入數(shù)據(jù)使用的是操作系統(tǒng)提供的
WriteConsoleW
函數(shù),該函數(shù)接收一個寬字符數(shù)組,所以MySQL客戶端調(diào)用它的時候需要顯式地將它從服務(wù)器收到的字節(jié)序列按照客戶端默認的字符集轉(zhuǎn)換成一個寬字符數(shù)組。正因為這一步驟的存在,所以可以避免上邊提到的一個問題。比方說我們在啟動MySQL客戶端的時候使用了--default-character-set=gbk
的啟動參數(shù),那么服務(wù)器的character_set_result
變量就是gbk。然后再執(zhí)行SELECT * FROM t
語句,那么服務(wù)器就會將字符'我'
的gbk編碼,也就是0xCDE2
發(fā)送到客戶端,客戶端將這個字節(jié)序列先從客戶端默認字符集,也就是gbk的編碼轉(zhuǎn)換成一個寬字符數(shù)組,然后再調(diào)用WriteConsoleW
函數(shù)寫到黑框框,黑框框自然可以把它顯示出來。
亂碼問題應(yīng)該如何分析
好了,介紹了各個步驟中涉及到的各種字符集,大家估計也看的眼花繚亂了,下邊總結(jié)一下我們遇到亂碼的時候應(yīng)該如何分析,而不是胡子眉毛一把抓,隨便百度一篇文章,然后修改某個參數(shù),運氣好修改了之后改對了,運氣不好改了一天也改不好。知其然也要知其所以然,在學(xué)習(xí)了本篇文章后,大家一定要有節(jié)奏的去分析亂碼問題:
我使用的是什么操作系統(tǒng)
對于UNIX系統(tǒng)用戶來說,要搞清楚我使用的黑框框到底是使用什么字符集展示字符,就像是iTerm2中的character encoding屬性:同樣還要搞清楚操作系統(tǒng)當前使用什么字符集,運行l(wèi)ocale命令查看:王大爺喊你輸入呢,跟這兒>locale
LANG=""
LC_COLLATE="z"
LC_CTYPE="z"
LC_MESSAGES="z"
LC_MONETARY="z"
LC_NUMERIC="z"
LC_TIME="z"
LC_ALL="z"
王大爺喊你輸入呢,跟這兒>沒有什么特別極端的特殊需求的話,一定要保證上述兩個字符集是相同的,否則可能連漢字都輸入不進去!對于Windows用戶來說搞清楚自己使用的黑框框的代碼頁是什么,也就是操作系統(tǒng)當前使用的字符集是什么。
搞清楚客戶端的默認字符集是什么
啟動MySQL客戶端的時候有沒有攜帶--default-character-set
參數(shù),如果攜帶了,那么客戶端默認字符集就以該參數(shù)指定的值為準。否則分析自己操作系統(tǒng)當前使用的字符集是什么。
搞清楚客戶端發(fā)送請求時是以什么字符集編碼請求的
對于UNIX系統(tǒng)來說,我們可以認為請求就是采用操作系統(tǒng)當前使用的字符集進行編碼的。
對于Windows系統(tǒng)來說,我們可以認為請求就是采用客戶端默認字符集進行編碼的。
通過執(zhí)行SHOW VARIABLES LIKE 'character%'
命令搞清楚:
character_set_client
:服務(wù)器是怎樣認為客戶端發(fā)送過來的請求是采用何種字符集編碼的character_set_connection
:服務(wù)器在運行過程中會采用何種字符集編碼請求中的字符character_set_result
:服務(wù)器會將響應(yīng)使用何種字符集編碼后再發(fā)送給客戶端的客戶端收到響應(yīng)之后:
對于服務(wù)器發(fā)送過來的字節(jié)序列來說:
在UNIX操作系統(tǒng)上,可以認為會把該字節(jié)序列直接寫到黑框框里。此時應(yīng)該搞清楚我們的黑框框到底是采用何種字符集展示數(shù)據(jù)。
在Windows操作系統(tǒng)上,該字節(jié)序列會被認為是由客戶端字符集編碼的數(shù)據(jù),然后再轉(zhuǎn)換成寬字符數(shù)組寫入到黑框框中。
請認真分析上述的每一個步驟,然后發(fā)出驚呼:小樣,不就是個亂碼嘛,還治不了個你!
-END-
如果看到這里,說明你喜歡這篇文章,請轉(zhuǎn)發(fā)。同時標星(置頂)本公眾號可以第一時間接受到博文推送。1.Restful快速開發(fā)后端腳手架今天聊聊HashMap和TreeMap的內(nèi)部結(jié)構(gòu)3.總結(jié)一些關(guān)于CPU的基本知識Nginx一個牛X的功能,流量拷貝!
1.《mysql數(shù)據(jù)庫有亂碼怎么解決方法?終于找到答案了亂碼問題讓人頭疼,徹底解決 MySQL 中的亂碼問題》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識,僅代表作者本人觀點,與本網(wǎng)站無關(guān),侵刪請聯(lián)系頁腳下方聯(lián)系方式。
2.《mysql數(shù)據(jù)庫有亂碼怎么解決方法?終于找到答案了亂碼問題讓人頭疼,徹底解決 MySQL 中的亂碼問題》僅供讀者參考,本網(wǎng)站未對該內(nèi)容進行證實,對其原創(chuàng)性、真實性、完整性、及時性不作任何保證。
3.文章轉(zhuǎn)載時請保留本站內(nèi)容來源地址,http://f99ss.com/gl/3047119.html