3. Schema变更的常见陷阱:锁表、数据丢失、应用兼容性、回滚困难
Schema变更,说白了就是在给飞驰的汽车换轮胎。你想想看,数据库还在线上跑着,业务请求一个接一个,这时候你要改表结构——加字段、删索引、改类型……稍有不慎,就是一场事故。
我做了十几年数据库,见过太多因为Schema变更翻车的案例。今天咱们就把这四个最常见的陷阱掰开揉碎了讲清楚。
3.1 锁表:你以为只是慢,其实是停服
很多新手DBA觉得,加个字段而已,几秒钟的事。嗯,这里要注意——MySQL 5.6之前,ALTER TABLE会锁住整张表,所有读写操作全部排队等着。你以为只是慢?不,是停服。
我在项目中遇到过一家电商公司,大促前夜要加一个优惠券字段。DBA直接执行了ALTER TABLE,结果订单表被锁了整整8分钟。那8分钟里,用户下不了单,客服电话被打爆。最后复盘时发现,那张表有2亿行数据。
- DDL阻塞DML:MySQL的DDL操作在大多数存储引擎下会锁表
- 数据量越大,锁越久:千万级以上的表,ALTER可能耗时数十分钟
- 主从延迟:主库执行DDL时,从库也会被阻塞
怎么破?我个人的习惯是:
- 用pt-online-schema-change:Percona Toolkit的工具,通过触发器同步数据,几乎不锁表
- 或者用gh-ost:GitHub开源的方案,更安全,支持暂停和恢复
- 业务低峰期操作:凌晨3点,你懂的
3.2 数据丢失:删掉的不是字段,是钱
数据丢失是Schema变更里最要命的陷阱。你以为删掉的是冗余字段,实际上可能是业务逻辑的依赖。
举个例子:
-- 看起来没用的字段
ALTER TABLE orders DROP COLUMN temp_flag;
-- 但某个定时任务还在读这个字段
SELECT * FROM orders WHERE temp_flag = 'pending';
字段删了,查询直接报错。更可怕的是,有些数据是隐式丢失的——比如你修改字段类型,MySQL会自动做隐式转换,精度丢了,数据变了,你都不知道。
| 操作类型 | 数据丢失风险 | 典型案例 |
|---|---|---|
| DROP COLUMN | 高 | 删除字段后,依赖该字段的代码全部报错 |
| MODIFY COLUMN | 中 | DECIMAL(10,2) 改成 DECIMAL(8,2),小数位被截断 |
| CHANGE COLUMN | 高 | 重命名字段后,旧代码写入的数据丢失 |
| DROP INDEX | 低 | 索引删除后,查询变慢,但数据不丢 |
3.3 应用兼容性:新Schema和旧代码打架
这是最容易被忽略的陷阱。你改了数据库,但应用还没更新。新字段旧代码不认识,旧字段新代码找不到——两边一打架,线上就崩。
我记得有一次,一个团队在用户表加了phone_verified字段,类型是TINYINT。但旧代码还在用phone字段做验证,结果新数据写进去,旧代码读不到验证状态,用户全被踢下线。
为什么会这样?因为Schema变更和应用发布是异步的。你不可能让所有服务同时更新。所以必须考虑向前兼容和向后兼容。
- 只加不减:新字段只允许NULL或默认值,旧代码不会报错
- 只增不删:索引、约束只加不删,除非确认无依赖
- 分步发布:先改数据库,再更新应用,最后清理旧字段
你想想看,如果必须删字段,怎么办?我建议分三步走:
- 第一步:应用层停止写入旧字段,但数据库保留
- 第二步:确认所有服务都不再依赖旧字段,删除应用代码
- 第三步:数据库执行DROP COLUMN
每一步之间,至少间隔一个发布周期。别急,急就出事。
3.4 回滚困难:变更一时爽,回滚火葬场
这是最让人头疼的。Schema变更不像代码回滚,git revert一下就完事。数据库是有状态的,你改了表结构,数据已经写进去了,想回滚?没那么简单。
举个例子:
-- 变更前:表结构
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100)
);
-- 变更后:加了phone字段
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- 数据已经写入了
INSERT INTO users VALUES (1, '张三', 'zhang@example.com', '13800138000');
-- 想回滚?DROP COLUMN phone 可以,但数据丢了
回滚的难点在于:
- 数据不可逆:新写入的数据包含新字段,回滚后这些数据就丢了
- 依赖链复杂:其他表可能已经引用了新字段
- 时间窗口长:变更后运行越久,回滚代价越大
怎么避免?我个人的习惯是:
- 变更前先设计回滚方案:不是等出问题了再想,而是变更前就写好回滚脚本
- 用版本号管理Schema:每个变更都对应一个版本,回滚就是执行上一个版本的DDL
- 保留变更前的快照:用mysqldump或pt-archiver备份数据
3.5 一张图看懂四个陷阱
下面这张图,把四个陷阱的关系和应对策略串起来了。我建议你保存下来,每次做Schema变更前看一眼。
3.6 总结一下
四个陷阱,每个都能让你线上翻车。但说白了,只要记住几个原则:
- 锁表:用在线DDL工具,别裸跑ALTER
- 数据丢失:先备份,再操作,最后验证
- 应用兼容性:只加不减,分步发布
- 回滚困难:变更前就写好回滚脚本
嗯,这些经验都是用血泪换来的。希望你能少踩几个坑。