先重现下问题,建立数据库 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 的默认字符集,而后客户端链接时按需指定也是个好办法。