編碼:字符 -> 二進制;
解碼:二進制 -> 字符;
爲什麼會出現亂碼?因爲編碼和解碼的規則不同。本質上都是同樣的一串二進制流,按照不同的規則解讀的結果當然是不同的。類比一下我們的時間戳轉時間的場景,時間戳就好比是二進制,時區就好比是不同的字符集,同一個時間戳用不同的時區轉換,得到的結果當然是不同的。所以,我們只要保證編碼和解碼用同一套字符集就不會出現亂碼了。
mysql裏支持的字符集
這裏主要指存儲數據的字符集。可以使用如下命令查看:
show charset
另外我們可以設置不同級別的字符集,服務器、數據庫、表、列四個級別。
存儲不同字符集的數據,消耗的空間是不同的。比如ascii字符集,就是固定一個字節。但是對於utf8字符集,需要1-3個字節,這是變長字符集,所以空間是不固定的。
mysql中utf8其實指的是utf8mb3字符集,是mysql裏的一種原始utf8字符集的變種,原始的utf8字符集需要1-4字節,而utf8mb3只需要1-3字節。原始的utf8字符集在mysql裏叫utf8mb4。
字符集轉換
在客戶端發起一條sql語句,到接受服務端返回,這個過程中的字符集是如何轉換的?
1.客戶端使用mysql client發起sql語句查詢,此時在客戶端機器上,使用操作系統的字符集對命令進行了編碼,轉成了二進制流發送到網絡中;
2.mysql服務端接受二進制流,使用character_set_client指定的字符集解碼二進制流;
3.將字符流使用character_set_connection指定的字符集轉換;
4.將字符集與列數據作比較;
5.將結果使用character_set_results指定的字符集編碼,返回給客戶端;
這裏有個問題,爲什麼需要轉成character_set_connection?
https://stackoverflow.com/questions/16082480/what-is-the-purpose-of-character-set-connection
這裏有一段解釋。意思是在做字符常量比較時,需要使用character_set_connection對常量進行轉換。啥意思?
比如select * from text where name = '哈哈'這條語句。服務端首先會用character_set_client解碼,然後處理語句。裏面的where查詢中的name列,會按照該列對應的字符集轉碼,但是對於常量‘哈哈’怎麼處理?顯然如果不處理,那麼會按照character_set_client處理,但這可能有問題啊,可能會導致參與比較的雙方使用的字符集都不一樣,這還怎麼比?所以就需要再按照character_set_connection來轉換一次常量,所以character_set_connection貌似應該與比較的列名的字符集一致。
這裏有三種字符集需要設置?其實可以只用如下命令:
set names xxx
便可以同時設定如上三個字符集。
幾個例子:
默認都是utf8編碼。
create table text (
name varchar(10)
) engine=Innodb default charset=utf8;
mysql> insert into text values('我');
Query OK, 1 row affected (0.01 sec)
建表並插入一條數據;
mysql> set character_set_results=gbk;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from text;
+------+
| name |
+------+
| �� |
+------+
1 row in set (0.00 sec)
這裏設置了character_set_results爲不同的編碼,導致查詢結果亂碼;
mysql> set character_set_connection = ascii;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from text where name = '我';
Empty set, 1 warning (0.00 sec)
這是設置了character_set_connection,導致無法查到數據;