alter table ... convert to character set 不转换已有数据字节流,仅修改表定义;正确做法是先转为 varbinary 冻结字节,再转为目标字符集。
mysql 里 alter table ... convert to character set 为什么改完还是乱码
改字符集不等于数据重编码,它默认只改表定义和列定义,不转换已有数据的字节流。如果原数据是 latin1 存的中文,直接 convert to utf8mb4,mysql 会把每个 latin1 字节当做一个 utf8mb4 字符去解释,结果就是一堆问号或 mojibake(如 “æ‘们”)。
真正安全的做法是两步走:
- 先用
ALTER TABLE ... CONVERT TO CHARACTER SET binary把字段转成VARBINARY,强制“冻结”原始字节,不作任何解释 - 再用
ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,让 MySQL 按新字符集重新解释这些字节
中间不能跳步,也不能省略 binary 这一环。尤其对 TEXT 或 MEDIUMTEXT 字段,漏掉这步几乎必乱码。
phpMyAdmin 界面点“操作 → 排序/排序方式”改字符集根本没用
那个下拉菜单改的是 Collation(排序规则),不是 CHARACTER SET(字符集)。比如选 utf8mb4_unicode_ci,底层字符集仍是 utf8mb4,但如果你的表当前是 utf8 或 latin1,这个操作连字符集都不碰,纯属无效点击。
界面真正能改字符集的地方只有两个:
- 建表时在“字段”页每列手动设
Collation(影响该列字符集) - 表结构页点“操作”→ 底部“表选项”→ 手动修改
Collation(影响整张表默认字符集,但不批量改已有列)
也就是说:phpMyAdmin 界面没有“一键全表字符集迁移”的按钮。想批量改,必须写 SQL 或用命令行工具。
执行 ALTER DATABASE ... CHARACTER SET = utf8mb4 后新表还是 utf8
这条语句只改数据库的默认字符集,不影响已存在表,也不影响后续创建表的列级设置——因为 MySQL 创建表时,优先继承列定义里的字符集,其次才是表定义,最后才是数据库默认值。
所以即使数据库设成了 utf8mb4,只要建表时没显式写 CHARACTER SET utf8mb4 或列上没设 COLLATE utf8mb4_unicode_ci,新表仍可能用 utf8(尤其是老版本 MySQL 默认值)。
保险做法是建表时明确声明:
CREATE TABLE t ( id INT, name VARCHAR(100) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
或者全局改 MySQL 配置文件,确保 init_connect='SET NAMES utf8mb4' 和 character-set-server=utf8mb4 都生效,但配置重启才生效,对已有连接无效。
Python 读取 MySQL 数据后 str.encode('utf-8') 报 UnicodeEncodeError
这不是 Python 编码问题,而是 MySQL 连接层没告诉驱动“我传给你的是 utf8mb4”。常见于没在连接字符串里加 charset=utf8mb4 或没设 client_flag=CLIENT.MULTI_STATEMENTS(某些旧驱动需要)。
以 pymysql 为例,必须这样连:
conn = pymysql.connect(
host='localhost',
user='root',
password='xxx',
charset='utf8mb4', # 必须显式指定
cursorclass=pymysql.cursors.DictCursor
)
漏掉 charset 参数,驱动默认用 latin1 解包返回的数据,哪怕表是 utf8mb4,Python 拿到的已经是错误解码后的 str,再怎么 encode 都晚了。
另一个坑是:Django 用户容易只改了 settings.py 的 OPTIONS['charset'],却忘了同步改 mysql 命令行客户端的 ~/.my.cnf,导致本地调试时 SQL 文件导入又出乱码。
字符集统一最麻烦的从来不是命令怎么敲,而是每个环节都得对齐:存储层、连接层、应用层、终端显示层。少一个,就卡在某个环节上,还很难一眼看出是哪一层错了。











