用Python从MySQL里取数据,明明数据库客户端里看是正常的中文,一到脚本里打印出来就变成了一串问号或者类似"杩欐槸涓枃"这样的乱码,这是很多人第一次用Python操作MySQL时都会遇到的情况。问题的核心往往出在连接层:数据库本身的数据是完好的,但连接通道没有指定正确的字符集,导致客户端和服务器之间用了不同的编码方式来解释同一串字节。本文会从原理到实操,把charset参数的配置方法和完整的排查思路讲清楚。

乱码产生的根本原因是什么
要理解乱码,先要明白一个前提:数据在数据库中存储的是字节,在屏幕上显示的是字符,字节到字符的转换依赖字符集。MySQL服务端有服务器的默认字符集,每个数据库、每张表、甚至每个字段都可以有自己的字符集,而客户端连接进来时,还会带一个连接字符集(character_set_client、character_set_connection、character_set_results这三者合称连接字符集)。
查询中文出现乱码,绝大多数情况是这样一个过程:数据库里的数据以utf8或utf8mb4存储,Python这边通过连接读取时,如果连接字符集被设置成了latin1(这是很多老版本MySQL的默认值),服务器会把utf8存储的数据"翻译"成latin1编码的字节再发给客户端,Python拿到这批字节后按错误的方式解码,显示出来自然就是乱码。反过来,写入数据时如果连接字符集和表字符集不一致,也会造成存进去就是坏的。
可以用下面的SQL在MySQL命令行里确认当前连接的字符集状态:
-- 查看连接相关的三个字符集变量 SHOW VARIABLES LIKE 'character_set_%'; -- 重点看这三个: -- character_set_client 客户端发送语句使用的字符集 -- character_set_connection 连接层使用的字符集 -- character_set_results 查询结果返回给客户端的字符集 -- 查看具体表的字符集 SHOW CREATE TABLE your_table;
如果发现character_set_results是latin1而表是utf8,乱码的原因就基本锁定了。解决方向很明确:让连接字符集与数据存储字符集保持一致。
pymysql中charset参数的正确写法
pymysql是目前Python生态里最常用的MySQL驱动之一,它在建立连接时支持一个charset参数,专门用来设置连接字符集。这个参数的值必须是MySQL认识的名字,比如utf8、utf8mb4、gbk等,注意要小写,写成utf-8是会报错的,这一点和Python自身的编码名称写法不一样,是新手常踩的坑。
下面是一个标准的连接写法:
import pymysql
# 建立连接时显式指定charset
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='your_password',
database='test_db',
charset='utf8mb4', # 注意是utf8mb4,不是utf-8
cursorclass=pymysql.cursors.DictCursor
)
try:
with conn.cursor() as cursor:
cursor.execute("SELECT name FROM users WHERE id = %s", (1,))
result = cursor.fetchone()
print(result['name']) # 正常输出中文
finally:
conn.close()
charset参数设置之后,pymysql会在握手阶段执行SET NAMES操作,把character_set_client、character_set_connection、character_set_results一次性都设置成指定值,等于替你完成了一次编码声明。如果不想改代码,也可以在拿到连接后手动执行SET NAMES utf8mb4,效果一样,但显式写在connect参数里更清晰,不容易遗漏。
另外提一句mysql-connector-python这个官方驱动的写法,它的参数同样叫charset,用法几乎一致:
import mysql.connector
conn = mysql.connector.connect(
host='127.0.0.1',
user='root',
password='your_password',
database='test_db',
charset='utf8mb4'
)
如果用的是SQLAlchemy这类ORM框架,charset要写进连接字符串里,例如mysql+pymysql://user:password@127.0.0.1:3306/test_db?charset=utf8mb4,漏掉这个query参数时乱码问题照样会出现,因为SQLAlchemy底层还是调pymysql建连接。
utf8和utf8mb4怎么选
很多人配置charset时纠结写utf8还是utf8mb4。这里需要澄清一个容易混淆的概念:MySQL里的utf8并不是真正完整的UTF-8,它最多只支持3个字节的字符,而标准的UTF-8最长4个字节。像emoji表情、一些生僻汉字、特殊符号,都需要4个字节才能表示。
如果在连接和建表时都用了utf8,插入一个emoji时可能会直接报错,或者被截断成问号。utf8mb4才是MySQL中真正意义上的UTF-8,MySQL 8.0开始默认字符集已经改成了utf8mb4,所以新项目直接统一使用utf8mb4是最稳妥的做法。
统一编码还涉及数据库和表的层面。如果已有的表是utf8想转成utf8mb4,可以执行转换语句,但要注意转换前备份数据,并确认字段长度,因为同一字符占用的字节数可能变化:
-- 修改数据库默认字符集 ALTER DATABASE test_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 修改表的字符集 ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
配置的原则就一句话:库、表、连接三层字符集保持一致,全部统一到utf8mb4,乱码基本就没有生存空间了。
配置正确还是乱码的排查清单
如果charset参数已经写了utf8mb4,查询结果仍然是乱码,说明问题在别的环节,可以按下面的清单逐项排查。
第一,确认数据本身是不是好的。用MySQL命令行客户端或者Navicat、DBeaver等图形工具查看同一条记录,如果工具里显示也是乱码,说明数据在写入阶段就坏了,属于历史遗留问题,需要修复数据本身,单纯改连接参数救不回来。这种情况下可以先尝试把错误编码的数据读出来重新编码,比如以latin1读出utf8存储的内容,再用Python做一次先encode再decode的转换修复。
第二,检查终端的输出编码。Windows下cmd默认是GBK编码,就算Python内部字符串正确,print到cmd窗口也可能显示异常。可以执行chcp 65001把控制台切成UTF-8,或者先写到一个UTF-8编码的文件里确认内容是否正确,别让显示环境的锅背到MySQL头上。
第三,检查Python源文件编码和读取方式。如果代码里硬编码了中文字符串参与查询,源文件应保存为UTF-8,Python 3默认如此一般没问题。如果是从文件读取数据再入库,open函数要显式指定encoding='utf-8',否则Windows下会按GBK解码,写入的数据就是坏的:
# 读取文件时显式指定编码,避免依赖系统默认值
with open('data.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
# 修复历史脏数据的一种思路:按错误编码还原再正确解码
raw = '杩欐槸涓枃' # 乱码字符串
try:
fixed = raw.encode('latin1').decode('utf-8')
print(fixed)
except (UnicodeEncodeError, UnicodeDecodeError):
print('编码链路对不上,需确认原始编码')
第四,如果中间还隔着代理、连接池或者读写分离组件,注意有些连接池在复用连接时会重置字符集设置,必要时在获取连接后重新执行一次SET NAMES,确保每一次操作都在正确的编码环境下进行。
总结一下:Python连接MySQL出现乱码,第一反应应该是检查charset参数是否显式设置为utf8mb4,然后核对库、表、连接三层的字符集是否一致,最后再排查终端显示和数据写入环节。把这三层都统一之后,中文和emoji就能在整条链路上畅通无阻了。
Python MySQL乱码charset编码配置pymysql修改时间:2026-09-05 08:14:34