讓 MySQL 支持存儲 emoji 表情

先重現下問題,建立數據庫 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='文章表'
複製代碼

因爲數據庫的字符集和排序規則分別是 utf8utf8_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_clientcharacter_set_results 的範圍的時候,是有可能正常工做的。好比 character_set_connectionutf8mb4,而 character_set_clientcharacter_set_results 均爲 utf8,咱們執行

INSERT INTO article (title) VALUES ('你好');
複製代碼

可是這個前提是咱們請求的 SQL 的字符範圍不能超出 character_set_client,好比咱們執行

INSERT INTO article (title) VALUES ('😄');
複製代碼

數據就會出現亂碼。

一樣,character_set_connection 的字符範圍 也不能超過數據庫的字符集的字符範圍,不然也會出現亂碼。

一般狀況下,咱們會將這 3 個變量的值統一,並設置成與數據庫的字符集一致,這樣配置更清晰一些。不過,是由客戶端去指定字符集仍是服務器配置,就得根據具體的場景去決定了。我以爲在服務器上配個 utf8 的默認字符集,而後客戶端鏈接時按需指定也是個好辦法。

相關文章
相關標籤/搜索