alter table ... convert to character set 会触发表重建,需加x锁阻塞dml,因需逐行重编码确保数据一致性;而仅改默认字符集不重建表、不锁表。

ALTER TABLE ... CONVERT TO CHARACTER SET 会触发表重建
MySQL在执行ALTER TABLE ... CONVERT TO CHARACTER SET时,不是简单改个元数据,而是真实重写整张表:逐行读取原始数据、按新字符集重新编码、写入新页结构。这个过程必须加X(排他)锁,阻塞所有DML(INSERT/UPDATE/DELETE)和大部分DDL,只允许SELECT(取决于存储引擎和隔离级别)。
原因很直接:如果允许并发写入,新旧编码混杂会导致数据损坏或校验失败。InnoDB必须确保整个转换过程原子、一致。
-
CONVERT TO是最重的操作,强制重建;而仅用ALTER TABLE ... CHARACTER SET utf8mb4只改表默认字符集,不触发表重建,也不锁表(但新字段会用新字符集,已有字段不变) - MyISAM 引擎下该操作更危险——它会先拷贝全表到临时文件,期间原表完全不可写,且无事务回滚能力
- 大表(比如 >1GB)执行时长可能达分钟级,期间应用写请求全部堆积或超时
为什么没走索引的UPDATE/DELETE也会被误认为“改字符集引发锁”
很多线上事故里,DBA看到慢查询日志里有UPDATE卡住,又查到近期改过字符集,就归因为“字符集导致锁”。其实真正原因是:字段字符集和参数字符集不一致(如SQL里传'中文'是latin1,但字段是utf8mb4),MySQL无法使用索引,被迫全表扫描——而全表扫描的DML在InnoDB中会升级为表级锁(准确说是对所有聚簇索引记录加X锁,效果等同于锁表)。
- 典型错误现象:
SHOW PROCESSLIST中状态为Updating或Waiting for table metadata lock,但INNODB_TRX里看不到对应事务,说明是隐式锁升级而非显式DDL - 验证方法:执行
EXPLAIN FORMAT=TRADITIONAL SELECT * FROM t WHERE name = 'xxx';,看key列是否为NULL,再比对CHARACTER_SET_CLIENT与字段字符集 - 修复不是改字符集,而是统一客户端连接字符集(如设
SET NAMES utf8mb4)或显式CAST参数:WHERE name = CONVERT('xxx' USING utf8mb4)
如何安全地修改大表字符集而不锁死服务
没有零停机方案,但可以大幅降低影响窗口。核心思路是:避免CONVERT TO,改用在线变更工具+分步迁移。
- 优先用
pt-online-schema-change(Percona Toolkit):它创建影子表、同步增量、原子切换,全程不锁原表DML(但DDL期间会有短暂元数据锁) - 若必须手写,可分三步:
ADD COLUMN name_utf8mb4 VARCHAR(...) CHARACTER SET utf8mb4→UPDATE ... SET name_utf8mb4 = CONVERT(name USING utf8mb4)(分批+WHERE限制)→DROP COLUMN name; RENAME COLUMN name_utf8mb4 TO name; - 务必避开业务高峰;提前在从库验证转换结果(特别是emoji、生僻字、排序行为);校对规则(
COLLATE)变更会影响ORDER BY和UNIQUE约束,不能只看字符集
容易被忽略的关键点:collation变更比charset更危险
很多人只盯着utf8mb4,却忘了COLLATE。比如把utf8mb4_general_ci换成utf8mb4_0900_as_cs,不仅影响大小写敏感性,还会让唯一索引失效——原来'A'和'a'算重复,现在不算了,但已有索引未重建,INSERT可能报Duplicate entry或静默覆盖。
更隐蔽的是:某些collation变更会导致WHERE条件匹配结果变化,而优化器仍沿用旧统计信息,可能选错执行计划。所以CONVERT TO后,别忘了ANALYZE TABLE。











