先重現下問題,建立數據庫 test_dbmysql
create database test_db default charset utf8 default collate utf8_general_ci;
複製代碼
建立數據表sql
CREATE TABLE `article` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(120) NOT NULL DEFAULT '' COMMENT '標題',
`abstract` varchar(600) NOT NULL DEFAULT '' COMMENT '摘要',
`created_at` int(11) NOT NULL DEFAULT '0',
`updated_at` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='文章表'
複製代碼
因爲數據庫的字符集和排序規則分別是 utf8
和 utf8_general_ci
,建立數據表時雖沒有指定字符集以及對應的排序規則,則取數據庫的配置。數據庫
使用 PyMySQL
鏈接數據庫,插入一個 emoji
表情bash
# coding=utf-8
import pymysql
connection = pymysql.connect(host='localhost',
user='user',
password='passwd',
db='db',
charset='utf8',
cursorclass=pymysql.cursors.DictCursor)
try:
with connection.cursor() as cursor:
sql = "INSERT INTO article (title) VALUES ('😄')"
cursor.execute(sql)
connection.commit()
finally:
connection.close()
複製代碼
執行後報以下錯誤服務器
pymysql.err.InternalError: (1366, "Incorrect string value: '\\xF0\\x9F\\x98\\x84' for column 'title' at row 1")
複製代碼
之因此出現這個錯誤,是由於 MySQL 的 utf8 字符集存儲不了 emoji 表情。編碼
誒,不會吧,utf8 不是一種 unicode 編碼嗎,不是應該支持世界上大部分的字符嗎?咱們日常說的 utf8 確實是這樣的,可是 MySQL 中的 utf8 實際上是閹割版的 utf8,它最多隻用 3 個字節存儲字符,因此存儲不了表情,這個 utf8 實際上是 utf8mb3 的別名。spa
若是咱們要支持表情的存儲,咱們須要完整的 utf8 字符集,最多能夠用 4 個字節來存儲字符,這個字符集名字叫 utf8mb4。code
因此,要支持表情的存儲,咱們將數據庫的字符集改成 utf8mb4 就能夠了。怎麼改呢?cdn
首先修改表 article 中類型爲字符串的列blog
ALTER TABLE article MODIFY title varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '標題';
ALTER TABLE article MODIFY abstract varchar(600) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '摘要';
複製代碼
爲了讓新增的列的字符串也是 utf8mb4 的字符集,咱們能夠修改表的字符集
ALTER TABLE article DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
複製代碼
若是想讓新增的表默認也是這個字符集,能夠修改數據庫的字符集
ALTER DATABASE test_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
複製代碼
具體是修改列、表仍是庫,按照你的須要自行決定。
修改腳本中鏈接數據庫的字符集,將
charset='utf8'
複製代碼
改成
charset='utf8mb4'
複製代碼
從新執行腳本,會發現執行成功。
到這裏,咱們能夠說是修改完成了,可是我以爲有些細節必須提下。否則,當出現問題的時候,仍是不知道啥狀況。
使用客戶端鏈接數據庫的時候,咱們能夠指定默認編碼
$ mysql -u root -p --default-character-set=utf8mb4
複製代碼
在配置數據庫的時候,咱們發如今 client 下也能夠配個相似的配置
[client]
default-character-set=utf8mb4
複製代碼
就是說,咱們鏈接的時候,若是沒有指定字符集,則取配置文件中的值;不然,取指定的字符集。
使用不一樣的字符集鏈接數據庫的時候,會影響下面 3 個變量
character_set_client
character_set_connection
character_set_results
複製代碼
好比,鏈接時若是沒有指定字符集,則會取配置 utf8mb4,這 3 個變量的值都是 utf8mb4;若是指定 字符集 utf8,則這 3 個變量的值都是 utf8。這 3 個變量是能夠動態修改的
SET character_set_client = utf8mb4;
SET character_set_connection = utf8mb4;
SET character_set_results = utf8mb4;
複製代碼
也可使用下面的命令代替,功能和上面三條語句一致
SET NAMES utf8mb4;
複製代碼
當這 3 個變量值與 數據庫的字符集不統一的時候,就有可能出錯或亂碼。
若是選擇數據庫 test_db 並設置以下
use test_db;
SET NAMES utf8;
複製代碼
執行
INSERT INTO article (title) VALUES ('😄');
複製代碼
報錯
Incorrect string value: '\xF0\x9F\x98\x84' for column 'title' at row 1
複製代碼
若是選擇數據庫 test_db 並設置以下
use test_db;
SET character_set_client = ascii;
SET character_set_connection = utf8mb4;
SET character_set_results = ascii;
複製代碼
執行
INSERT INTO article (title) VALUES ('😄');
複製代碼
咱們會發現存入的數據亂碼。
因此,在 PyMySQL 客戶端中,咱們須要指定編碼爲 utf8mb4,從而使腳本工做正常。
這 3 個變量是怎麼工做的呢?我簡單說明細下。
character_set_client
指客戶端請求內容的字符集,character_set_connection
指服務器處理內容的字符集,character_set_results
指服務器返回的響應的字符集。
咱們都知道計算機只認 0 和 1,因此數據庫服務器收到的請求或者返回的響應都是一串字節。服務器收到請求時,會將請求以 character_set_client
的字符集進行解碼,而後以 character_set_connection
的字符集進行編碼,而後丟給服務器處理。待處理結束後,又將結果使用 character_set_connection
的字符集進行解碼,而後使用 character_set_results
的字符集進行編碼返回。
PS:編碼能夠簡單的理解爲從字符串到字節串,解碼相反。
所以,當 character_set_connection
的字符集範圍大於 character_set_client
和 character_set_results
的範圍的時候,是有可能正常工做的。好比 character_set_connection
爲 utf8mb4
,而 character_set_client
和 character_set_results
均爲 utf8
,咱們執行
INSERT INTO article (title) VALUES ('你好');
複製代碼
可是這個前提是咱們請求的 SQL
的字符範圍不能超出 character_set_client
,好比咱們執行
INSERT INTO article (title) VALUES ('😄');
複製代碼
數據就會出現亂碼。
一樣,character_set_connection
的字符範圍 也不能超過數據庫的字符集的字符範圍,不然也會出現亂碼。
一般狀況下,咱們會將這 3 個變量的值統一,並設置成與數據庫的字符集一致,這樣配置更清晰一些。不過,是由客戶端去指定字符集仍是服務器配置,就得根據具體的場景去決定了。我以爲在服務器上配個 utf8 的默認字符集,而後客戶端鏈接時按需指定也是個好辦法。