做MySQL开发和运维的人,早晚都会遇到改表结构的时刻:业务跑了大半年,varchar(20)不够用了;订单金额从十万涨到百万,DECIMAL(10,2)开始告警;甚至建表时手滑把状态字段设计成了VARCHAR,要改成TINYINT。这些小改动看起来就是一条ALTER TABLE的事,但改完之后要么报Data too long for column,要么直接在千万级大表上跑了半小时锁表,把线上的读请求全堵了。这篇内容我从实际运维角度,把MySQL中修改字段长度、修改字段类型的语法、边界条件、坑和完整操作方案讲透,适合正在处理线上表结构调整的开发同学,也适合想系统搞懂DDL行为的DBA新人。
1. 动手之前:先判断你的表能不能安全改字段
很多新手拿到需求就直接执行ALTER TABLE,这在小表上确实无所谓,但到了生产环境,大表上的一条ALTER可能直接把业务干趴。改字段之前,先花五分钟确认三件事:数据量多大、这个字段被谁引用、当前MySQL会用什么方式执行这次DDL。
1.1 先查数据量和行数,别等锁表了才后悔
数据量不同,修改方案完全不同。一百万的表,ALTER直接跑也就几秒到几十秒;几千万上亿的表,直接执行大概率要几分钟甚至几十分钟,期间DML或者在等待锁,或者被阻塞。
判断数据量不能只靠感觉,用information_schema快速看:
SELECT TABLE_ROWS, ROUND(DATA_LENGTH/1024/1024, 2) AS data_mb, ROUND(INDEX_LENGTH/1024/1024, 2) AS index_mb, ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'user_info';注意TABLE_ROWS是估算值,InnoDB不可能给你精确行数,但量级判断足够了。如果表超过千万行,不要直接上ALTER,跳到第4章按大表流程处理。
还要顺手确认磁盘余量。修改字段类型多数情况会触发COPY算法,也就是重建整张表,临时表会吃掉额外的磁盘空间,通常需要表本身1到2倍的冗余空间。曾经见过一个生产事故:330GB的表在剩余磁盘只有150GB时直接ALTER,跑了十五分钟报磁盘满,表结构处于中间状态,恢复起来极其狼狈。别赌,先看df -h。
1.2 字段的上下游影响:索引、视图、存储过程和业务代码
改字段长度和类型,表面上是改一个列,实际上这个字段可能牵一发动全身。我习惯在操作前把所有依赖查一遍。
字段是否在索引中:直接改字段类型,相关索引会自动重建,成本升高。更隐蔽的问题是缩短varchar长度后,可能导致复合索引的字节长度超限,或者索引选择性发生变化。查询方式:
SELECT INDEX_NAME, SEQ_IN_INDEX, COLUMN_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'user_info' AND COLUMN_NAME IN ('nickname', 'phone');字段是否被视图、存储过程、触发器引用:视图不直接给警告,存储过程也不报错,但改完之后线上可能突然报错。检查方式:
-- 查视图定义 SHOW CREATE VIEW your_view_name; -- 查存储过程/函数/触发器定义里是否包含字段名 SELECT ROUTINE_NAME, ROUTINE_TYPE FROM information_schema.ROUTINES WHERE ROUTINE_DEFINITION LIKE '%nickname%';业务代码的类型映射:这个很容易漏。数据库里INT改成BIGINT,Java的Integer映射到Long没问题;反过来BIGINT改INT,超过21亿的数据直接溢出。VARCHAR改成TEXT后,一些ORM框架对TEXT类型有特殊处理:不能直接排序,不能有默认值,某些框架甚至拒绝把它作为查询条件。所以改类型前,最好和研发确认一下有没有哪个接口在拿这个字段做等值匹配或索引扫描。
1.3 理解DDL执行算法:INSTANT、INPLACE、COPY的区别
MySQL 8.0里执行DDL,算法大致分三种:INSTANT、INPLACE、COPY。
| 算法 | 是否重建表 | 是否允许并发DML | 是否需要额外磁盘 | 典型场景 |
|---|---|---|---|---|
| INSTANT | 否 | 是 | 极小 | 8.0.12+ ADD COLUMN(部分场景)、部分VARCHAR长度扩展 |
| INPLACE | 部分重建/仅重建索引 | 大多数支持 | 有一定需求 | 增加字段(部分)、增加索引、VARCHAR长度扩展(部分) |
| COPY | 是 | 否(独占锁) | 约等于整表大小 | 修改字段类型、缩短长度、大部分跨类型转换 |
重点理解COPY:MySQL会创建一个带新结构的新表,把老表数据一行行复制过去,期间对表加锁阻塞写入,完成后用新表替换老表。这既是"慢"的来源,也是"磁盘占用多"的来源。
修改字段长度和修改字段类型,在MySQL 8.0中的执行路径有区别。VARCHAR长度从较小值扩到较大值且不超过255字节,8.0.12以后部分场景可以走INSTANT或INPLACE;但缩短长度、修改数据类型(比如INT改BIGINT、VARCHAR改TEXT、DECIMAL改DOUBLE),基本上都要走COPY重建表。别听网上说MySQL 8.0什么都能INSTANT,没那么美。
想确认操作具体用哪种算法,直接指定算法执行,不支持会立刻报错给你看:
ALTER TABLE user_info MODIFY COLUMN nickname VARCHAR(100) NOT NULL DEFAULT '' COMMENT '昵称', ALGORITHM=INSTANT;如果支持会成功,不支持会提示需要INPLACE或COPY,这一步最多浪费一两秒,但能让你对代价有明确预期。
2. MODIFY 还是 CHANGE:字段修改的两条核心语法
修改字段最核心的就是ALTER TABLE下的MODIFY COLUMN和CHANGE COLUMN。这两条看起来很像,但用途有明确分工,搞混了容易出事。
2.1 MODIFY COLUMN:改类型、改长度,名称不动
这是最常用的语法,适用于不改列名、只改类型、长度、默认值、注释、位置等属性。
ALTER TABLE table_name MODIFY [COLUMN] column_name column_definition [FIRST | AFTER column_name];**关键点:column_definition必须写完整。**这是绝大多数人踩坑的地方。MySQL 8.0里,MODIFY会用你给的这段定义整体替换掉原来的定义,没写的属性直接丢。
举个例子,原表结构是:
SHOW CREATE TABLE user_info; -- nickname varchar(50) CHARACTER SET utf8mb4 NOT NULL DEFAULT '新用户' COMMENT '昵称'你只写:
ALTER TABLE user_info MODIFY COLUMN nickname VARCHAR(100);执行完再看,NOT NULL没了,DEFAULT没了,COMMENT也没了,字段变成允许NULL,默认NULL。这种"只想改长度,结果把约束全改没了"的情况,几乎每个团队都发生过。
正确写法是把原定义全部带上再修改:
ALTER TABLE user_info MODIFY COLUMN nickname VARCHAR(100) CHARACTER SET utf8mb4 NOT NULL DEFAULT '新用户' COMMENT '用户昵称';所以无论如何,改之前先SHOW CREATE TABLE把完整定义拿出来,基于原定义去改,而不是凭记忆写。
2.2 CHANGE COLUMN:要改字段名时的唯一选择
CHANGE COLUMN既可以改字段名,也可以同时改类型、长度和属性。语法上多了一个新旧列名的参数。
ALTER TABLE table_name CHANGE [COLUMN] old_col_name new_col_name column_definition [FIRST | AFTER column_name];比如把email改名成contact_email,同时长度从varchar(100)改成varchar(200):
ALTER TABLE user_info CHANGE COLUMN email contact_email VARCHAR(200) CHARACTER SET utf8mb4 NOT NULL DEFAULT '' COMMENT '联系邮箱';注意,CHANGE即使不想改类型,也必须把column_definition完整写一次。曾经有同事以为CHANGE可以不写类型只改名,直接报语法错误,然后吐槽MySQL反人类。其实规则很简单:CHANGE的语义是"把旧列调整成新列",新列的定义必须完整。
两条语句的对比如下:
| 对比项 | MODIFY COLUMN | CHANGE COLUMN |
|---|---|---|
| 是否可改名 | 不可 | 可以(唯一方式) |
| 是否可改类型/长度 | 可以 | 可以 |
| 是否可改位置 | 可以(FIRST/AFTER) | 可以 |
| 必须写完整定义 | 是 | 是 |
| 语法风险 | 属性丢失 | 属性丢失、列名错误 |
另外补一句,绝大多数场景优先用MODIFY,字段名最好别随便改。改名字意味着代码里所有SQL、ORM映射、报表、监控脚本全部要跟着改,牵涉面比改类型大得多。
2.3 实际案例:把一张业务表的字段改到位
假设user_info表有一批字段需要调整,放在一个ALTER语句里批量执行,会比逐条ALTER高效得多。MySQL允许一条ALTER里包含多个MODIFY子句,批量操作时表只重建一次,开销远小于分多次执行。
-- 1. nickname从varchar(20)扩到varchar(50),保留原约束 ALTER TABLE user_info MODIFY COLUMN nickname VARCHAR(50) CHARACTER SET utf8mb4 NOT NULL DEFAULT '新用户' COMMENT '用户昵称'; -- 2. age从TINYINT改成SMALLINT(考虑到业务可能存年龄之外的数字) ALTER TABLE user_info MODIFY COLUMN age SMALLINT NOT NULL DEFAULT 0 COMMENT '年龄'; -- 3. balance从DECIMAL(10,2)扩到DECIMAL(12,2) ALTER TABLE user_info MODIFY COLUMN balance DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '账户余额';也可以合并成一条:
ALTER TABLE user_info MODIFY COLUMN nickname VARCHAR(50) CHARACTER SET utf8mb4 NOT NULL DEFAULT '新用户' COMMENT '用户昵称', MODIFY COLUMN age SMALLINT NOT NULL DEFAULT 0 COMMENT '年龄', MODIFY COLUMN balance DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '账户余额';注意一点,一个ALTER里多个操作只要有一个不支持当前的ALGORITHM,MySQL会用最保守的方式执行整个操作,甚至直接报错。批量操作之前,小表无所谓,大表建议拆分,或者先用ALGORITHM测试一下。
3. 字段类型转换的边界与报错根因
语法会了,还得知道修改边界在哪里。这里说的边界包括:长度缩短时哪些情况会失败、跨类型转换时会丢什么数据、改完之后索引和默认值的连锁反应。
3.1 缩短长度:严格模式下直接报错
把varchar(255)缩短成varchar(20),最典型的错误是:
ERROR 1406 (22001): Data too long for column 'nickname' at row 1这个错误的直接原因是已有数据里存在超过20个字符的值。但背后的机制值得展开。MySQL的DML行为受sql_mode控制,严格模式(含STRICT_TRANS_TABLES或STRICT_ALL_TABLES)下,超长数据直接报错、语句失败;非严格模式下会截断并产生warning,数据被静默截掉。
线上环境几乎都是严格模式,所以直接执行大概率报错。别慌,这是好事,比静默丢数据强。修改前先预检超长行:
-- 查看该字段最大字符数(按字符数而不是字节数) SELECT MAX(CHAR_LENGTH(nickname)) FROM user_info; -- 查看超过目标长度的具体行 SELECT id, nickname, CHAR_LENGTH(nickname) AS len FROM user_info WHERE CHAR_LENGTH(nickname) > 20;这里额外提醒:CHAR_LENGTH按字符计算,一个emoji是一个字符,但在utf8mb4下占4字节。如果varchar(100)的100是字符数,那么存满100个emoji需要400字节。索引中如果涉及这个字段,还要注意字节长度,尤其在使用utf8mb4时,InnoDB索引键长度的上限是3072字节。
如果预检发现确实有超长行,需要先跟业务方确认处理策略:截断、保留、还是拒绝修改。然后先UPDATE数据再执行ALTER,绝对不要在数据没确认的状态下直接改。
3.2 跨类型转换:隐式转换和数据失真
跨类型转换是另一个重灾区,尤其是字符串和数字之间的转换。
VARCHAR改成INT:如果字段里存的是纯数字,没问题。只要有非数字字符,严格模式下报错:
ERROR 1366 (HY000): Incorrect integer value: 'abc123' for column 'age' at row 1非严格模式下,'abc123'可能转成123或者0,取决于具体内容和MySQL的解析规则,这种静默丢失最危险。
INT改成VARCHAR:相对安全,数字转字符串不会多也不会少。但要注意控制长度,INT最大到2147483647,转成VARCHAR至少给11位,防止未来数据接近上限时溢出。同时原INT字段不会保留前导零,比如手机号如果用INT存储,改VARCHAR后13800138000超过INT范围根本存不进去——这个不是转换的坑,是存储设计的坑。
DECIMAL精度调整:从DECIMAL(10,2)扩到DECIMAL(12,2)安全,因为只是放大了整数部分位数。从DECIMAL(12,2)缩小到DECIMAL(10,2),整数部分超长会报错,小数部分会被四舍五入。
DATETIME改成DATE:时间部分会被截掉。反之DATE改成DATETIME,时间部分默认补00:00:00。
VARCHAR改成TEXT:类型不兼容的点很隐蔽——TEXT/BLOB列在MySQL 8.0.13之前不允许有DEFAULT值,所以如果原字段有DEFAULT,直接转TEXT会报错;8.0.13之后虽然支持表达式默认值,但普遍做法还是先去掉DEFAULT再转。另外TEXT类型不能直接加普通索引,必须指定前缀长度,比如INDEX idx_content(content(100))。
我在实操中会先做一个"类型转换预检验证",把目标转换逻辑放到SELECT里看看会不会出问题:
-- 模拟VARCHAR转INT,找出所有不能安全转换的行 SELECT id, status FROM user_info WHERE status REGEXP '[^0-9]' OR status = '' OR status IS NULL;3.3 语义变化:索引、排序规则和默认值的连锁反应
字段类型改了,最容易被忽略的是排序规则和字符集的变化。
同一个表里,两个字段做JOIN时如果字符集不同,会报Illegal mix of collations错误。改字段时如果显示的CHARACTER SET和原字段不一致,比如从utf8mb4改成utf8mb3,JOIN时就会出现这种问题。修改前检查表默认字符集和字段字符集是否一致:
SELECT TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_NAME = 'user_info'; SELECT COLUMN_TYPE, CHARACTER_SET_NAME, COLLATION_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME = 'user_info' AND COLUMN_NAME = 'nickname';字段类型把INT改成BIGINT后,如果这个字段上有自增属性,注意自增列必须是索引的一部分,通常要有主键或唯一索引。改成BIGINT后自增上限大幅提升,但AUTO_INCREMENT的当前值不会自动跳变,只是上限变了。
另外,修改字段类型后,之前建立在字段上的生成列、CHECK约束(8.0.16+)、ON UPDATE CURRENT_TIMESTAMP都可能失效或报错。尤其经常踩的是DATETIME字段上的ON UPDATE CURRENT_TIMESTAMP,用MODIFY重定义时如果不把这个属性带回去,自动更新时间就丢了。所以再次强调,改之前SHOW CREATE TABLE,改之后再次SHOW CREATE TABLE对比,这是最稳的习惯。
4. 生产环境大表字段修改的完整操作方案
前面讲的是语法和边界,这章解决实战问题:千万行级别的表,怎么安全地修改字段长度和类型。
4.1 先用SELECT完成数据预检
大表操作之前,数据预检比语法重要得多。我通常按这个顺序执行:
-- 1. 确认表的基本情况 SELECT TABLE_ROWS, ROUND(DATA_LENGTH/1024/1024,2), ROUND(INDEX_LENGTH/1024/1024,2) FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'user_info'; -- 2. 预检字段最大值(以缩短varchar为例) SELECT MAX(CHAR_LENGTH(nickname)) FROM user_info; -- 3. 预检NULL比例,评估是否要把NOT NULL改成NULL或反之 SELECT COUNT(*) AS total_cnt, COUNT(nickname) AS not_null_cnt FROM user_info; -- 4. 如果打算改成唯一索引或修改类型,先检查重复值 SELECT nickname, COUNT(*) AS cnt FROM user_info GROUP BY nickname HAVING COUNT(*) > 1 LIMIT 10;预检结果直接决定后续方案。比如发现最大长度已经接近目标长度了,就说明长度给得太紧,需要跟业务重新确认。如果NULL比例很高,加NOT NULL约束前要先把NULL值UPDATE掉:
UPDATE user_info SET nickname = '' WHERE nickname IS NULL;4.2 低峰期执行:备份、监控、收尾一个不能少
预检通过后,不能随便什么时间点就执行。我的经验是选择业务低峰期,并且在执行前完成备份。
备份至少保证能回滚。小表用mysqldump单表导出,大表用物理备份工具(如xtrabackup)或者确认binlog有足够保留时长。注意,ALTER TABLE执行过程中如果因为错误中断,MySQL内部会回滚,但大表的回滚同样耗时,不是"失败立即恢复原样"那么简单。
执行前看几个关键指标,执行期间每隔几秒刷一次:
SHOW PROCESSLIST:有没有长时间运行的长事务阻塞DDLSHOW STATUS LIKE 'Threads_running':运行线程数是否异常上涨- 主从延迟:如果开启了主从,注意Seconds_Behind_Master
- 磁盘空间:
df -h,确认临时表空间足够
执行完不要立刻走人,MySQL可能统计信息还没更新。建议手动更新统计信息:
ANALYZE TABLE user_info;4.3 大表专用工具:pt-osc 不锁表方案
如果表实在太大,业务又不允许长时间阻塞写入,推荐用Percona Toolkit里的pt-online-schema-change,这是行业里改动在线表结构的标准方案之一。
原理不复杂:创建一个结构一致的新表,在旧表上建立三个触发器(INSERT、UPDATE、DELETE),把修改后的结构应用到新表,然后分批把旧表数据复制到新表,复制完成后用原子性RENAME TABLE切换新旧表。整个过程业务端基本无感,只有最后一瞬间的切换会短暂锁表。
基本用法:
pt-online-schema-change \ --alter "MODIFY COLUMN nickname VARCHAR(100) NOT NULL DEFAULT '' COMMENT '昵称'" \ --host=127.0.0.1 \ --user=root \ --password=your_password \ D=your_db,t=user_info \ --execute使用前提比较重要:表必须有主键或者唯一索引(触发器需要),不能有其他表通过外键引用它(否则会报错)。执行前建议先加--dry-run跑一遍检查,确认无误再--execute。
另外还有一个gh-ost工具,GitHub开源的,原理类似但没有用触发器,而是通过binlog解析同步数据。两者选哪个看团队熟练度,pt-osc更老牌稳定,gh-ost在8.0环境兼容性也不差。
4.4 主从环境下的执行顺序与延迟控制
线上基本都有主从复制。在主库执行ALTER TABLE,binlog会把SQL同步到从库,从库也要重建表,一样耗时。区别在于从库执行期间可能拖慢复制,导致主从延迟飙升,然后读写分离架构里的读流量读到旧数据。
我的一般策略是:
- 先在临时环境或从库上跑一遍同一套语句和预检SQL,确认耗时和报错。很多坑在从库暴露出来,比如某个从库的数据和主库不一样,主库能成功从库却失败。
- 在业务低峰期对主库执行。如果用了pt-osc,可以加
--max-lag参数控制复制延迟:
pt-online-schema-change \ --alter "MODIFY COLUMN nickname VARCHAR(100)" \ --host=127.0.0.1 --user=root --password=xx \ --max-lag=10 \ --check-interval=5 \ D=your_db,t=user_info \ --execute- 如果主库表特别大,并且架构允许,冷门方案是先在从库执行,然后做主从切换,再改原主库,等于把DDL的压力在从库消化。但这个切换涉及的业务链路很多,不是所有团队都能这么干,属于灰度方案里的高级玩法。
我的态度是:大多数生产环境,大表直接用pt-osc或gh-ost,避免原生ALTER锁表。宁可工具多花一两个小时,让业务无感知,也不要用一次原生ALTER去赌业务不会在阻塞窗口发起写入。
5. 高频翻车场景与排查复盘
这一章把实际运维中高频遇到的报错和图形化工具坑拿出来逐条复盘,每个问题都给了完整的排查链路,不是直接甩答案。
5.1 错误“Data too long for column 'xxx'”的定位链路
这个报错前面提过,但完整复盘一次。场景:某个晚上产品说要把用户昵称限制从30个字符调整到20个字符,DBA直接执行:
ALTER TABLE user_info MODIFY COLUMN nickname VARCHAR(20) NOT NULL DEFAULT '' COMMENT '昵称';几秒后报错Data too long for column 'nickname' at row 1。这个时候新手容易慌,其实排查链路很明确:
Step 1:先定位最大长度。不要猜,直接查:
SELECT MAX(CHAR_LENGTH(nickname)) AS max_len FROM user_info;Step 2:找出超过目标长度的具体行,看看到底影响多少数据:
SELECT id, nickname, CHAR_LENGTH(nickname) AS len FROM user_info WHERE CHAR_LENGTH(nickname) > 20 ORDER BY len DESC;Step 3:跟业务方确认处理策略。三种可能:业务上允许截断,那就先UPDATE再ALTER;不允许截断,只能调整目标长度;如果只有极个别脏数据,清理掉再ALTER。
Step 4:确认数据没问题后再执行。
这一步为什么重要?因为直接缩短长度失败,表结构还是老的,数据一条没丢,属于"有惊无险"。但如果你在非严格模式(比如sql_mode没开STRICT_TRANS_TABLES)下执行,MySQL会静默截断超长数据,那才是真丢数据。你们环境什么sql_mode,执行一下SELECT @@sql_mode;看清楚,建议生产环境保持严格模式。
5.2 错误“Incorrect integer value”的脏数据排查
这个报错经常出现在VARCHAR改成INT或TINYINT的场景。比如状态字段设计成VARCHAR(10),后来觉得不爽要改成TINYINT:
ALTER TABLE order_info MODIFY COLUMN status TINYINT NOT NULL DEFAULT 0;报错Incorrect integer value: 'pending' for column 'status' at row 1。定位链路:
Step 1:找到所有非纯数字行。用正则匹配最快:
SELECT id, status FROM order_info WHERE status NOT REGEXP '^-?[0-9]+$';Step 2:看空字符串和NULL。空字符串在严格模式下也会导致转换失败:
SELECT id, status FROM order_info WHERE status = '' OR status IS NULL;Step 3:确认业务映射。比如'pending'对应0,'paid'对应1,'failed'对应2,先写UPDATE把所有字符串映射成数字,再执行ALTER。
UPDATE order_info SET status = CASE status WHEN 'pending' THEN 0 WHEN 'paid' THEN 1 WHEN 'failed' THEN 2 ELSE 0 END;这里有个关键点:如果业务代码里有直接拿状态字符串做判断的地方(比如Java代码里"pending".equals(status)),改完类型后应用端也要同步改,否则查询条件里的字符串和数据库数字类型做比较,又踩一次隐式转换的坑。
5.3 图形化工具的隐藏坑:Navicat 保存时到底干了什么
很多人喜欢用Navicat右键设计表,界面里改一改长度、类型,点保存。坦白说这个操作本质上也是生成了ALTER TABLE语句,但有几个坑需要注意。
Navicat的表设计器里,如果原字段有注释、默认值、字符集,你在界面上不一定能完整看到。保存时它生成的MODIFY语句可能没带上这些属性,导致保存后注释、DEFAULT全丢了。踩过一次:运维用Navicat把某个字段长度从50改成100,第二天业务方发现字段默认值没了,线上insert全部报错。原因是Navicat默认情况下,你只是改长度,它生成的语句不保留原DEFAULT。
应对办法:不要直接在图形界面里改大表结构。要么先查看SQL预览,确认它生成的语句是不是符合你的预期;要么直接在查询窗口手动写前面讲的ALTER语句,执行完用SHOW CREATE TABLE对比。DBeaver、SQLyog这些工具底层逻辑类似,都有生成SQL不完整的问题,关键策略都一样:以SHOW CREATE TABLE的输出为准,改完之后再对比一次。
另外,图形化工具连接大表时,如果表数据量很大,光是用界面打开表结构都可能触发limit查询,更别说直接改了。生产环境大表结构变更,老老实实用命令行或脚本,别把图形工具当作首选入口。
我自己后来养成了一个习惯,任何字段变更前都把变更SQL和SHOW CREATE TABLE输出贴到变更单里,执行后把新的SHOW CREATE TABLE再贴一份作为对比,全程留痕。这样就算出了幺蛾子,翻记录就能定位是哪个变更导致的。
还有一个技巧:改完字段后,对涉及这个字段的慢查询日志扫一遍。因为类型变了,数据分布变了,优化器的执行计划可能完全不一样。曾经有张表的主键从INT改成BIGINT后,某个关联查询的驱动表选择变了,一条原本0.1秒的SQL变成2秒,最后通过EXPLAIN调整索引顺序才解决。多花五分钟看下线上有没有新出现的慢SQL,能省掉后面很多告警电话。