根本原因是客户端与服务端编码不一致,需在create_engine的URL中加?charset=utf8mb4,并传入connect_args={'charset': 'utf8mb4'},同时确保MySQL服务端my.cnf中[client]、[mysqld]、[mysql]均配置default-character-set=utf8mb4。

SQLAlchemy连接MySQL时中文显示为问号或乱码
根本原因是客户端与服务端编码不一致,常见于MySQL默认用latin1而Python用utf8。即使数据库表和字段设为utf8mb4,若连接层没指定编码,SQLAlchemy仍会按服务端默认(如latin1)解码,导致SELECT结果中中文变???或报UnicodeDecodeError。
- 在
create_engine的URL末尾显式加?charset=utf8mb4(注意不是utf8,MySQL的utf8实际是utf8mb3,不支持emoji等四字节字符) - 同时传入
connect_args={'charset': 'utf8mb4'}更稳妥,尤其当URL被中间件重写或含特殊字符时 - 确保MySQL服务端配置
my.cnf中[client]、[mysqld]、[mysql]三处都设了default-character-set = utf8mb4和collation-server = utf8mb4_unicode_ci
使用pyodbc连接SQL Server读取中文乱码
SQL Server本身用UTF-16(Windows下常映射为UCS-2),但pyodbc默认用系统ANSI代码页解码,导致中文字段返回b'\xff\xfe\x8b\x67'之类字节或直接乱码。
- 连接字符串中必须包含
Driver={ODBC Driver 17 for SQL Server};...;CharacterSet=UTF8;(注意:仅较新驱动支持CharacterSet参数;旧驱动需改用unicode=True+ 手动.encode('utf-16-le'),不推荐) - 确认驱动版本:运行
pyodbc.drivers(),选带ODBC Driver 17/18的条目;若只有SQL Server Native Client,基本无法可靠支持UTF-8传输 - 避免在
text()查询中拼接中文字符串,应全用参数化:session.execute(text("SELECT * FROM t WHERE name = :n"), {"n": "张三"}),否则SQL注入风险+编码不可控
SQLAlchemy执行raw SQL后中文字段类型为bytes而非str
典型现象:用engine.execute("SELECT name FROM user")拿到的结果里name列值是b'\xe5\xbc\xa0\xe4\xb8\x89'而不是"张三",说明底层DBAPI没正确声明列编码,SQLAlchemy不敢自动decode。
- 检查DBAPI是否支持
encoding参数:如pymysql需在create_engine中加connect_args={'charset': 'utf8mb4'};psycopg2则需connect_args={'client_encoding': 'UTF8'} - 临时补救:对单个字段手动解码——
row[0].decode('utf8'),但这是权宜之计,源头没配好迟早出问题 - 验证是否生效:执行
engine.connect().connection.character_set_name()(MySQL)或engine.connect().connection.info.client_encoding(PostgreSQL),输出应为utf8mb4或UTF8
Flask-SQLAlchemy中配置编码容易忽略的细节
很多人只改SQLALCHEMY_DATABASE_URI,却忘了Flask-SQLAlchemy默认禁用连接池自动重连,导致长连接后编码状态错乱,或并发请求间编码参数被覆盖。
-
SQLALCHEMY_ENGINE_OPTIONS必须显式设置:{"connect_args": {"charset": "utf8mb4"}},不能只靠URI里的?charset= - 若用
pool_pre_ping=True,要确保预检SQL(如SELECT 1)不触发编码切换,MySQL下建议配合pool_recycle=3600防超时 - 环境变量注入URI时,
&可能被shell截断,务必用引号包裹整个URI,例如export DATABASE_URL="mysql+pymysql://u:p@h/d?charset=utf8mb4"
真正麻烦的不是加几个参数,而是不同层级(服务端、驱动、连接池、ORM)的编码配置相互覆盖。一个地方漏掉,前面全白配。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











