说到MySQL报错,1267 - Illegal mix of collations这个错误,我估计很多老开发都见过。它最烦人的地方在于:明明数据看着没问题,SQL语句也检查了好几遍,但一执行就报错,而且报错信息里夹着一堆看着眼熟但又说不清含义的术语。
我第一次遇到这个错是在做一个数据汇总需求的时候,需要把几个历史项目的用户表数据合并到一张新表里,UNION一上去直接红屏。当时第一反应是百度,搜出来一堆“ALTER TABLE改一下就行”的答案,结果照着操作,线上表直接锁了十几分钟,差点出事。后来把这个报错的原理彻底搞明白之后才发现,1267这个错误说穿了就是一句话:MySQL认为自己没法在两个不同的排序规则之间做比较运算。
这篇就把我从报错到彻底解决的全过程拆开来讲,包括报错原理、定位方法、五种常见触发场景、四种根治手段,以及怎么在建表阶段就避免踩坑。
1. 报错1267:它其实不是一个“字符乱码”错误
1.1 先看完整报错长什么样
绝大多数人看到的是简化版报错:
text复制ERROR 1267 (HY000): Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and (utf8mb4_unicode_ci,IMPLICIT) for operation 'UNION'
但是完整报错里藏着大量信息,至少包括三块:
- 错误码
1267,对应ER_CANT_AGGREGATE_2COLLATIONS,意思是“不能用两种不同collation做聚合或比较”。 - 括号里先是两个冲突的collation:
utf8mb4_general_ci和utf8mb4_unicode_ci。 - 括号后面还有
IMPLICIT字样,末尾跟着的是触发操作名,比如UNION、JOIN、IN、ORDER BY等。
注意:IMPLICIT这个词很关键,它表示这个collation不是你在SQL里主动指定的,而是字段或表结构自带的。既然是自带属性,那问题大概率出在表结构设计上,而不是查错SQL语法。
1.2 字符集和排序规则,为什么MySQL要分两层?
很多人会把character set和collation混为一谈,实际上它们解决的问题完全不同。
- 字符集(character set):决定一个字符在数据库里怎么存储,以什么二进制字节序列保存。比如
utf8mb4是一个能存4字节Unicode字符的字符集,存emoji、生僻字都没问题;latin1是单字节字符集,只能存西欧字符。 - 排序规则(collation):决定同一字符集下的字符串怎么排序和比较。比如英文里“a”和“A”是否相等,中文拼音排序还是按Unicode编码排序,都是collation管的事。
打个比方:字符集是国际语言,collation是口音偏好。两个人都说中文,但一个按普通话发音,一个按粤语发音,在“某个字读音是否相同”的问题上可能产生分歧。MySQL的排序规则,解决的就是这种“分歧”。所以每一种字符集,默认都会搭配一个或多个可选collation。比如:
utf8mb4可以配utf8mb4_general_ci、utf8mb4_unicode_ci、utf8mb4_0900_ai_ci等。latin1可以配latin1_swedish_ci、latin1_german1_ci等。utf8在MySQL 5.7及以下版本,可以理解成utf8mb3的别名,它的collation和utf8mb4是两套体系。
每个collation名字里的ci是case insensitive的缩写,表示大小写不敏感。ai表示accent insensitive,即不区分重音符号。不同collation带来的直接后果是:字符串“abc”和“ABC”在一种规则下被认为是相等的,在另一种规则下可能就是不相等。MySQL要做UNION去重、做JOIN关联、做WHERE过滤的时候,必须把两边的字符串判定成“可比较的同一套规则”,判定不了就直接甩出1267。
1.3 为什么同样的数据在不同表里会不匹配
一个经典场景:系统刚上线时用了MySQL 5.6,建表时随手写了DEFAULT CHARSET=utf8,一切正常。后来团队做了架构升级,新库用了MySQL 8.0,建表语句里写的DEFAULT CHARSET=utf8mb4,默认排序规则也变成了8.0特有的utf8mb4_0900_ai_ci。老表里的字段是utf8_general_ci或者utf8mb4_general_ci,新表里的字段是utf8mb4_0900_ai_ci。一旦有跨这两类表的SQL产生关联或合并,MySQL不知道该按谁的规则来,就会报1267。
还有一种情况是在同一个库里,不同表由不同人创建,有人写了DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci,有人用了默认的utf8mb4_general_ci,两张表肉眼看着都是中文和英文字段,实际内部规则完全不同。
所以这个报错本质上是一个元数据不一致的问题,不是数据内容脏,而是定义数据的“规则”互相冲突。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 别急着改表,先把出错位置精确锁定
2.1 报错语句里通常藏着关键信息
我见过很多人在处理这个报错时,第一反应就是执行ALTER TABLE ... CONVERT TO CHARACTER SET ...,把整张表改掉。这种操作风险很大,尤其是大表,会触发全表重建、索引重建,线上业务直接就卡住了。
更稳妥的做法是:先把报错语句里的信息拆出来,弄清楚是哪个操作触发的。还是拿这条报错看:
text复制Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and (utf8mb4_unicode_ci,IMPLICIT) for operation 'UNION'
- 操作范围:
UNION。说明问题出在UNION两侧的查询结果上。 - 冲突双方:一个字段的collation是
utf8mb4_general_ci,另一个是utf8mb4_unicode_ci。 - 冲突来源:都是
IMPLICIT,即都是从表字段定义继承来的。
那接下来就可以很明确地先检查:UNION左右两侧的SELECT语句,各自查的是哪些字段,这些字段分别属于哪些表,再去查表的列定义。
如果是JOIN报错,报错信息里同样会写for operation 'JOIN'。如果是WHERE子句里的子查询比较,会写for operation '='这类运算符。把这一段报错看懂,定位范围就缩小了一大半。
2.2 用information_schema一键查出所有列的collation
把范围缩小到一张或几张表之后,可以直接用information_schema.COLUMNS把可疑表的字符集和排序规则摊开看:
sql复制SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'your_db'
AND TABLE_NAME IN ('t_old_user', 't_new_user')
AND CHARACTER_SET_NAME IS NOT NULL
ORDER BY TABLE_NAME, COLUMN_NAME;
如果报错涉及UNION操作,还可以用这条SQL排查所有表的默认排序规则:
sql复制SELECT TABLE_NAME, TABLE_COLLATION
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_db'
ORDER BY TABLE_COLLATION;
执行完之后,哪些表是utf8mb4_general_ci,哪些是utf8mb4_unicode_ci,哪些是utf8mb4_0900_ai_ci,一眼就能看明白。然后再对照报错语句,找到具体是哪个字段出现在冲突双方里。
这里有个小经验:不要只查字符型字段(CHAR、VARCHAR、TEXT、ENUM、SET)。如果字段类型是INT、BIGINT、DATE这类非字符串类型,它们没有字符集和collation属性,不会是1267的源头。所以排查时主要关注字符串类型字段即可。
2.3 隐式规则:为什么两个看起来一样的字段会冲突
MySQL的collation和字符集在比较运算里有一个“可 coercibility(可强制转换性)”的优先级机制。这个机制决定了,当两边规则不同时,MySQL是能自动转换还是直接报错。
简化版规则是这样的:
- 如果两边collation完全相同,正常比较。
- 如果一边显式指定了
COLLATE(比如col1 COLLATE utf8mb4_unicode_ci),另一边是字段自带的隐式collation,那MySQL会优先使用显式指定的那个,这叫显式指定优先级最高。 - 如果两边都是隐式的(
IMPLICIT),且collation不同,MySQL无法判断该偏向谁,直接报错1267。
这也是为什么很多人写SQL时,明明觉得“同样的字段”,一比较就报错。因为在MySQL眼里,双方的权利是平等的,谁也不服谁,那就只能报错让程序员来做决定。
所以,遇到1267之后,正确操作顺序是:
- 看报错后面的
for operation,确认是哪种操作。 - 找到该操作涉及的所有表和字段。
- 通过
information_schema确认各自collation。 - 再决定是临时改SQL,还是彻底改表结构。
3. 常见触发场景和现场处理方案
3.1 UNION操作:最常见的报错现场
UNION是重灾区。因为UNION本身要求所有SELECT结果集的列“按位置”一一匹配,并且会做去重,去重就需要比较每一行的值。如果两个SELECT分支的同一位置字段使用了不同collation,MySQL直接报错。
比如:
sql复制SELECT name FROM t_old_user
UNION
SELECT name FROM t_new_user;
如果t_old_user.name是utf8mb4_general_ci,t_new_user.name是utf8mb4_unicode_ci,就会报:
text复制Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and (utf8mb4_unicode_ci,IMPLICIT) for operation 'UNION'
现场处理办法是在SQL里手动统一,最保险的是对两个分支都显式指定同一个collation:
sql复制SELECT name COLLATE utf8mb4_unicode_ci FROM t_old_user
UNION
SELECT name COLLATE utf8mb4_unicode_ci FROM t_new_user;
这样MySQL就会按utf8mb4_unicode_ci去比较两边的值。这种做法适合一次性查询、临时取数、小批量数据导出,不用动表结构,风险最低。
需要注意:如果SQL里有UNION ALL,严格来说不需要去重,但因为最终还是要把两个结果集合并成一个结果集,MySQL依然会检查两边字段的collation是否一致。所以UNION ALL遇到这个报错也很常见,处理方式一样。
3.2 JOIN关联:肉眼看不见的字段差异
JOIN报错比UNION更隐蔽,因为很多人的直觉是“JOIN条件里两个字段类型一样不就行了”。但MySQL在JOIN时,如果关联字段是字符串类型,除了字段类型,还会去比较字符集和collation是否兼容。
举个例子:
sql复制SELECT a.*, b.*
FROM t_order a
JOIN t_user b ON a.user_no = b.user_no;
如果t_order.user_no是utf8mb4_unicode_ci,t_user.user_no是utf8mb4_general_ci,MySQL在计算a.user_no = b.user_no时就会报错。
处理办法也很直接,在JOIN条件上显式指定collation:
sql复制SELECT a.*, b.*
FROM t_order a
JOIN t_user b ON a.user_no = b.user_no COLLATE utf8mb4_unicode_ci;
也可以两边都加:
sql复制JOIN t_user b ON a.user_no COLLATE utf8mb4_unicode_ci = b.user_no COLLATE utf8mb4_unicode_ci;
前者写起来更简洁,MySQL在计算时会把等号另一边的隐式collation向显式collation靠拢。这里要注意:如果使用a.user_no = b.user_no COLLATE utf8mb4_unicode_ci,实际上是把b.user_no转换成utf8mb4_unicode_ci去和a.user_no比较,但如果a.user_no本身是别的collation,这个等值判断可能产生“看起来相等,实际不相等”的隐患。所以我个人更推荐两边都显式指定,虽然SQL长一点,但行为完全确定。
另外,JOIN字段如果走的是索引,在字段上加COLLATE可能会让MySQL放弃使用索引,因为索引是按字段原有collation排序的,强制转换后顺序可能对不上。所以更彻底的办法还是改表结构,让两边的collation一致。
3.3 WHERE子查询与ORDER BY排序
除了UNION和JOIN,WHERE子句里的子查询比较也会触发:
sql复制SELECT * FROM t_user
WHERE name IN (SELECT name FROM t_blacklist);
如果两边name字段一个是utf8mb4_general_ci,另一个是utf8mb4_unicode_ci,同样会报1267,报错信息里的操作符通常是'='或'IN'。
另一种情况是ORDER BY:
sql复制SELECT name FROM t_a
UNION ALL
SELECT name FROM t_b
ORDER BY name;
虽然这里两个SELECT分支各查各的,但最终结果集要按name排序,MySQL需要在整个临时结果集里确定一个统一的排序规则。如果两个字段collation不一致,排序阶段照样报错。
处理方式是先统一再排序:
sql复制SELECT name COLLATE utf8mb4_unicode_ci AS name FROM t_a
UNION ALL
SELECT name COLLATE utf8mb4_unicode_ci AS name FROM t_b
ORDER BY name;
总结一下规律:凡是MySQL需要把两个字符串字段放在一起比较大小、判断相等、排序、去重的地方,都可能触发1267。包括但不限于:=、<>、IN、NOT IN、LIKE、ORDER BY、GROUP BY、DISTINCT、UNION、JOIN。
3.4 视图、临时表、存储过程返回值等暗坑
除了直接在表字段上冲突,还有几个比较隐蔽的触发场景,不遇到的人可能永远不会想到。
第一个是视图(View)。视图本质上是一条保存的SELECT语句,视图里的列会继承基表字段的collation。如果你在视图里用了CONCAT、CASE WHEN、IFNULL之类的表达式,生成的表达式列collation需要单独查看。如果视图内部字段来自不同collation的表,视图创建时可能不会报错,但查询视图时就会随机触发1267。
第二个是临时表(Temporary Table)。MySQL内部创建临时表时,对于GROUP BY、ORDER BY、DISTINCT操作,临时字段的collation会沿用源字段的。但如果你手动创建临时表,临时表字段的collation又忘了显式指定,和源表做关联时就会冲突。
第三是存储过程或函数返回值。比如你写了一个函数返回varchar,函数内部是从某张表里取的字符串。如果函数返回值的collation和调用处字段不一致,在比较时也可能报错。
遇到这些隐藏场景,我的建议是:先直接查看创建语句,用SHOW CREATE VIEW v_name;、SHOW CREATE PROCEDURE p_name;检查定义里有没有不同collation的表达式混用。如果确实存在,优先在定义内部统一,而不是在外部查询里手动转换。因为外部转换只能解决当次查询,内部定义不改,以后每个调用的人都会踩坑。
4. 根治方案:从SQL层到表结构层的完整路线
4.1 SQL查询中临时指定COLLATE
适用场景:临时取数、远程排查、数据修复前做验证。优点是零改表、零风险,缺点是要在每一条SQL里写清楚,维护成本高,而且如果SQL里还有别的字段没覆盖到,还会继续报错。
常见的写法有几种:
sql复制-- 1. 字段级指定:把字段转成指定collation
SELECT name COLLATE utf8mb4_unicode_ci FROM t_user;
-- 2. 字符串字面量指定:比较时把常量转换成目标collation
SELECT * FROM t_user WHERE name = '张三' COLLATE utf8mb4_unicode_ci;
-- 3. 整表字段统一转换后再比较
SELECT CONVERT(name USING utf8mb4) COLLATE utf8mb4_unicode_ci FROM t_user;
第三种CONVERT(name USING utf8mb4)会把字段从原来的字符集转换成utf8mb4,再指定collation。这种写法尤其适合老表还在用utf8或latin1的情况。因为你直接写name COLLATE utf8mb4_unicode_ci时,如果字段当前字符集是latin1,MySQL不允许直接指定utf8mb4的collation,必须先转换字符集再指定排序规则。
我在处理历史数据时最常用的就是这条SQL:
sql复制SELECT CONVERT(name USING utf8mb4) COLLATE utf8mb4_unicode_ci AS name
FROM t_old_latin1_table;
它能绕开字符集层面的不一致,直接把字段“翻译”成目标字符集和规则,用于数据比对和修复前预览非常方便。
4.2 ALTER TABLE修改列级别的collation
如果你确认某张表的字段应该统一到某一套规则,可以改字段定义。最常用的是这几种写法:
sql复制-- 修改单列定义
ALTER TABLE t_user MODIFY COLUMN name VARCHAR(64)
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 整表转换:转换所有字符型列
ALTER TABLE t_user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CONVERT TO CHARACTER SET ... COLLATE ...会遍历表里所有字符型列,把它们全部转成目标字符集。如果整张表的字段都想统一,用这个更方便。但要注意:执行这个操作时,MySQL会把整张表重建一遍,数据量大时耗时很长,而且会占用大量磁盘空间,因为原表和新表会同时存在。在锁表策略上,MySQL 8.0的默认算法是INPLACE,但CONVERT操作通常需要重建表,所以依然要注意业务低谷期执行。
更安全的做法是先备份、再在从库上验证、最后在主库的低峰期执行。如果表特别大,我建议先做数据快照或走工具(如gh-ost、pt-online-schema-change)来做在线变更,避免长时间锁表。
改完字段之后要记得执行:
sql复制SHOW CREATE TABLE t_user;
确认字段定义里的DEFAULT CHARSET和COLLATE都符合预期。有时你只改了字段,但表的默认属性还是旧的,后续新建字段可能会继承旧的collation,留下再次冲突的隐患。
4.3 库级和实例级的统一设置
如果你不想每张表都单独指定,可以在数据库级别把默认值改掉。这个操作只影响新创建的表和未显式指定字符集的字段,不会重建已有表。
sql复制ALTER DATABASE your_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
实例级别则需要在配置文件里设置:
ini复制[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
修改后重启MySQL,再确认:
sql复制SHOW VARIABLES LIKE 'character_set_server';
SHOW VARIABLES LIKE 'collation_server';
这里有一个容易被忽略的时间点:即使你修改了实例默认值,已存在的表还是保留原来的字符集和排序规则;只有新建的表或新建的字段才会用新的默认值。所以改完实例配置不等于历史数据自动更新。要做历史数据治理,还是需要把旧的表重新转换一遍。
另外,连接层也需要关注。很多应用用JDBC连接MySQL时,连接串里如果写了characterEncoding=UTF-8,这只在客户端和服务器之间做字符集转换。真正影响collation比较的,是字段定义和表达式中实际的collation。如果JDBC连接串里配置了类似connectionCollation=utf8mb4_unicode_ci的选项,它会在连接建立时执行SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci,对连接会话内的字面量collation有影响,但对表字段的collation无影响。想直接从连接层统一的话,可以在客户端连接成功后手动执行:
sql复制SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
这样可以避免SQL里写的字符串字面量和表字段collation冲突的问题。
4.4 MySQL 8.0与5.7的差异:默认collation带来的迁移坑
MySQL 8.0的默认字符集是utf8mb4,默认排序规则是utf8mb4_0900_ai_ci。MySQL 5.7的默认排序规则通常还是utf8mb4_general_ci。这两个名称都不同,更不用说排序细节差异。
如果你从5.7迁移到8.0,主从复制时可能会出现字符集或排序规则不兼容的情况。比如:
- 5.7主库表是
utf8mb4_general_ci。 - 8.0从库同步时,如果设置了
character_set_server=utf8mb4但没指定collation-server,从库新建表的默认collation变成了utf8mb4_0900_ai_ci。 - 后续做数据回迁或跨主从查询时,新旧collation混在一起,
UNION、JOIN就开始报1267。
在8.0里还有一个概念需要留意:utf8mb4_0900_ai_ci是基于Unicode 9.0的规则,并不支持所有老规则下的写法。比如有些老项目里写了utf8mb4_unicode_ci,在8.0里依然可以创建,但要确认排序行为是否符合业务预期。
所以做版本升级前,我建议先把所有查询语句里COLLATE关键字扫一遍,确认没有显式依赖旧collation写法的地方;然后把建表语句统一改成新的标准,比如:
sql复制DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci
如果业务上不关心大小写排序细节,也可以继续统一用utf8mb4_unicode_ci,因为它兼容性更好,5.7、8.0都支持,而且不会在后续迁移时因为“0900”这个版本特性再踩一次坑。
5. 预防措施:让这种报错从项目里消失
5.1 建库建表前的统一约定
1267这个错最让人头疼的地方是它属于“结构性缺陷”,不是改一行SQL就能一劳永逸的。所以真正干净的解决方案是在项目初期就把字符集和排序规则约定好。
推荐约定如下:
- 库、表、字段统一使用
utf8mb4字符集。 - 排序规则统一使用
utf8mb4_unicode_ci(兼容性好)或utf8mb4_0900_ai_ci(MySQL 8.0新项目可选)。 - 禁止再建
utf8(即utf8mb3)表,因为utf8mb3最多只能存3字节,遇到emoji就存不进去。 - 在项目统一建表模板里,把
DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci直接写进去。
举个例子,新项目建表语句最好长这样:
sql复制CREATE TABLE `t_user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL COMMENT '用户名',
`nickname` varchar(64) DEFAULT NULL COMMENT '昵称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
如果项目里已经有用到latin1的老表,建议在新老数据交互的边界层先做一次显式转换,不要让不同字符集的字段直接裸奔到SQL里。
5.2 连接层和服务端参数的配合
服务端参数上,建议在MySQL配置文件中强制指定:
ini复制[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
init_connect='SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci'
init_connect会在每个新连接建立时自动执行指定的SET语句,这样即使客户端连接串里没写字符集参数,也会自动统一到目标collation。不过要注意,init_connect不会对拥有SUPER权限的账号生效,所以管理员账号连接时的行为可能需要单独确认。实际使用中,我更推荐在应用层就写好连接参数。
Java JDBC连接串可以这样写:
text复制jdbc:mysql://127.0.0.1:3306/your_db?characterEncoding=UTF-8&connectionCollation=utf8mb4_unicode_ci
Python的SQLAlchemy里可以用connect_args:
python复制engine = create_engine(
"mysql+pymysql://user:pass@127.0.0.1/your_db",
connect_args={"charset": "utf8mb4", "collation": "utf8mb4_unicode_ci"}
)
这样应用连接建立后,会话层的collation就是统一的,SQL里直接写'张三'这类字符串字面量,也会使用连接层的collation参与比较,不容易和表字段产生冲突。
5.3 上线前的SQL巡检与自动化检查
对于存量项目或者多人协作的项目,靠自觉是不太现实的,最好能在上线前加一道自动化检查。简单做法是在SQL审核流程里加一个脚本,扫描建表语句和表结构变更语句,检查是否有:字符型字段未显式指定字符集或排序规则、同一张表里混用不同collation的字段、跨库JOIN时两边字段collation不一致等情况。
其中一条比较实用的巡检SQL是查库里是否存在“同类型但排序规则不同”的字段:
sql复制SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLLATION_NAME,
COUNT(*) OVER (PARTITION BY TABLE_NAME, COLUMN_NAME) AS cnt
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
AND DATA_TYPE IN ('char', 'varchar', 'text', 'mediumtext', 'longtext')
ORDER BY TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME;
做完字段层巡检,再配合已有的慢查询日志、错误日志,一旦线上出现1267,就能在几秒内定位到具体表字段,而不是临时去翻一堆SQL。
另外,如果项目里有从外部导入的数据,比如Excel导出的CSV、第三方系统同步过来的数据,导入前要统一做一次字符集转换。我遇到过很多次,Navicat导入CSV时默认把字段建成latin1或utf8mb4_general_ci,和业务表里已有的utf8mb4_unicode_ci字段一关联就出错。导入前看清楚工具里选的目标字符集,远比事后改表省事。
最后再分享一点个人体会:遇到1267这类报错,心烦意乱的时候最容易做出危险操作,比如直接对线上大表执行ALTER TABLE。我的习惯是,先花两分钟看报错信息,再用information_schema确认涉及字段,最后才决定改SQL还是改表结构。如果是历史数据量很大的表,哪怕最终确定要改表和字段,我也会先在临时环境里模拟一遍变更,确认耗时和锁表时间在可接受范围内,再动线上。数据库这种基础设施,每次变更都值得多一份谨慎。
