3. 变更类型分类:表结构变更、索引变更、约束变更、存储过程/函数变更、视图变更
说实话,我刚带团队那会儿,最头疼的不是写不出SQL,而是搞不清楚什么变更该走什么流程。有一次半夜被电话叫醒,说线上订单查不出来了。我上去一看,好家伙,有人直接ALTER TABLE加了个字段,结果索引全失效了。嗯,从那以后,我逼着团队把所有变更按类型分门别类,每种类型定死了操作规范。
你想想看,数据库Schema变更就像给飞行中的飞机换零件。换颗螺丝和换发动机,风险能一样吗?所以,咱们得先把变更类型搞清楚。我个人习惯把它们分成五大类:表结构、索引、约束、存储过程/函数、视图。每一类的风险点、回滚策略、审批级别都不一样。
核心原则:变更类型决定了你的操作流程和风险等级。别把加索引和改表结构混为一谈,前者可能只是慢几分钟,后者搞不好要丢数据。
3.1 表结构变更:最危险,也最常见
表结构变更,说白了就是动表的骨架。加字段、删字段、改类型、改默认值,这些都算。我在项目中遇到过最惨的一次,是有人把VARCHAR(255)改成了VARCHAR(50),结果线上跑批任务直接截断数据,几万条订单地址变成了半截。从那以后,我定了个死规矩:改字段类型必须走灰度发布。
表结构变更的核心风险在哪?
- 锁表:MySQL 5.7 之前,
ALTER TABLE很多操作会锁全表。你想想看,大表锁个几十分钟,业务直接瘫痪。 - 数据丢失:
DROP COLUMN、MODIFY TYPE搞不好就丢数据。尤其是类型收缩,数据库不会给你弹窗确认。 - 回滚困难:加字段好办,删字段就麻烦了。数据没了就是没了,除非你有备份。
我的习惯:表结构变更必须写两份脚本——一份变更脚本,一份回滚脚本。回滚脚本在变更前就要准备好,并且要在测试环境验证过。别等到出事了再临时写,那时候手都是抖的。
常见的表结构变更操作,我整理了一张表,方便你对照着看风险等级:
| 操作类型 | 风险等级 | 锁表风险 | 回滚难度 | 推荐工具 |
|---|---|---|---|---|
| ADD COLUMN | 中 | 低(部分数据库无锁) | 低 | pt-online-schema-change |
| DROP COLUMN | 高 | 高 | 极高(数据不可恢复) | gh-ost |
| MODIFY COLUMN TYPE | 高 | 高 | 高 | pt-online-schema-change |
| RENAME COLUMN | 中 | 低 | 中 | 手动 + 应用层兼容 |
| CHANGE DEFAULT | 低 | 低 | 低 | 直接 ALTER |
3.2 索引变更:看似简单,实则暗藏杀机
很多人觉得加索引不就是一条CREATE INDEX吗?有什么好怕的。我告诉你,怕的地方多了去了。有一次我在生产环境加了个联合索引,结果查询计划全变了,原来走索引A的查询改走了索引B,响应时间从10毫秒飙到了3秒。
索引变更的坑,我踩过这么几个:
- 查询计划漂移:新索引可能导致优化器选择不同的执行计划,不一定都是好的。
- 写入性能下降:索引不是免费的。每多一个索引,写入操作就要多维护一棵B+树。我见过一张表建了十几个索引,写入TPS直接腰斩。
- 冗余索引:加索引之前不看已有的索引,结果建了一堆重复的。浪费磁盘空间不说,还拖慢写入。
我曾经踩过的坑:有一次在MySQL 5.7上给一张5000万行的表加索引,直接用了CREATE INDEX,结果锁表了20分钟。从那以后,我加索引必用pt-online-schema-change或者gh-ost,再也不敢裸跑了。
索引变更的最佳实践,我总结了几条:
- 先分析,后操作:用
EXPLAIN看查询计划,确认新索引真的有用。 - 检查冗余索引:用
pt-duplicate-key-checker或者查information_schema,把重复的索引清理掉。 - 在线DDL工具:大表索引变更,必须用在线工具。别省那点时间,出事了加班费都不够。
- 监控变更后的性能:索引加完后,观察24小时的慢查询日志和QPS/TPS曲线。
3.3 约束变更:牵一发而动全身
约束变更,包括主键、外键、唯一约束、检查约束。这类变更最麻烦的地方在于——它会影响数据的完整性。你加个外键约束,如果现有数据有孤儿记录,直接就报错了。
我记得有一次,业务方要求给一个历史表加唯一约束。我一看,好家伙,表里有几千条重复数据。要是直接ALTER TABLE ADD UNIQUE,数据库直接报错,变更就失败了。后来我写了个脚本,先把重复数据清理掉,再加约束。
约束变更的注意事项:
- 数据预检:加约束之前,先查一下现有数据是否满足约束条件。不满足的话,提前处理。
- 外键顺序:加外键约束时,先加父表,再加子表。删的时候反过来。
- 性能影响:外键约束会影响写入性能,每次插入/更新都要检查关联表。高并发场景下慎用。
- 唯一约束 vs 唯一索引:在MySQL里,唯一约束和唯一索引其实是一回事。但在Oracle里,两者有区别。搞清楚你用的数据库。
我的建议:约束变更最好放在业务低峰期执行。尤其是外键约束,如果关联表数据量很大,检查过程可能会很慢。我曾经在白天加外键,结果事务超时,回滚了三次才成功。
3.4 存储过程/函数变更:版本管理是老大难
存储过程和函数,说白了就是数据库里的"程序"。它们的变更不像表结构那样有明确的Schema版本,很多时候就是直接CREATE OR REPLACE。这就带来了一个问题——你怎么知道线上跑的是哪个版本?
我在项目中遇到过,开发改了一个存储过程,没通知DBA,直接部署到生产了。结果另一个依赖这个存储过程的应用没适配,线上报错报了一晚上。后来我强制要求:所有存储过程和函数的变更,必须走Git版本管理,每次变更都要生成一个带版本号的脚本。
存储过程/函数变更的要点:
- 版本号管理:在存储过程注释里写上版本号和变更日期。方便排查问题。
- 依赖分析:改一个存储过程之前,先查清楚哪些应用、哪些视图、哪些其他存储过程依赖它。
- 参数兼容:改参数列表的时候,要考虑调用方是否同步更新。我建议新增参数用默认值,保持向后兼容。
- 权限控制:不是谁都能
ALTER PROCEDURE的。权限要收口,变更要审批。
一个实用的做法:把存储过程的定义放在SQL文件中,文件名包含版本号,比如sp_get_order_v2.sql。每次变更都新建一个文件,旧文件保留。这样你随时可以回退到上一个版本。
3.5 视图变更:风险最低,但别掉以轻心
视图,说白了就是一张"虚拟表"。它不存数据,只是把查询逻辑封装起来了。视图变更的风险相对较低,因为不涉及数据迁移。但是——视图依赖的表结构变了,视图可能就失效了。
我遇到过这种情况:业务方改了底层表的一个字段名,结果上层好几个视图全报错了。因为视图定义里引用了旧的字段名。从那以后,我定了个规矩:改表结构之前,先查一下有哪些视图依赖这个表。
视图变更的注意事项:
- 依赖检查:改视图之前,查
information_schema.VIEWS或者pg_views,确认视图的定义。 - 级联影响:视图可以嵌套视图。改一个视图,可能影响一大片。
- 性能问题:物化视图(Materialized View)的刷新策略要慎重。全量刷新还是增量刷新?刷新频率是多少?这些都要想清楚。
- 权限管理:视图可以用来做行级权限控制。改视图的时候,别忘了检查权限设置。
我的习惯:每次改完视图,我都会跑一遍SELECT * FROM view_name LIMIT 1,确保视图能正常查询。别笑,我见过有人改完视图,语法错误都没发现,直接部署到生产了。
3.6 各类变更的对比总结
说了这么多,最后用一张表总结一下五类变更的核心差异。这样你以后做变更评估的时候,心里有个谱。
| 变更类型 | 风险等级 | 数据影响 | 回滚难度 | 审批级别 | 推荐工具 |
|---|---|---|---|---|---|
| 表结构变更 | 🔴 高 | 直接修改数据文件 | 高(数据可能丢失) | DBA + 技术负责人 | gh-ost, pt-online-schema-change |
| 索引变更 | 🟡 中 | 影响查询性能 | 中 | DBA | pt-online-schema-change, gh-ost |
| 约束变更 | 🟡 中 | 影响数据完整性 | 中 | DBA + 业务方 | 手动 + 数据预检脚本 |
| 存储过程/函数 | 🟢 低-中 | 影响业务逻辑 | 低(版本回退) | 开发负责人 | Git + CI/CD |
| 视图变更 | 🟢 低 | 影响查询结果 | 低 | 开发人员 | 手动 + 依赖分析 |
好了,这一章的内容就到这儿。记住一句话:变更类型决定了你的操作流程和风险等级。别把加索引和改表结构混为一谈,也别觉得视图变更就可以随便改。每种变更都有它的脾气,摸透了,你才能游刃有余。