3. 变更类型分类:表结构变更、索引变更、约束变更、存储过程/函数变更、视图变更

说实话,我刚带团队那会儿,最头疼的不是写不出SQL,而是搞不清楚什么变更该走什么流程。有一次半夜被电话叫醒,说线上订单查不出来了。我上去一看,好家伙,有人直接ALTER TABLE加了个字段,结果索引全失效了。嗯,从那以后,我逼着团队把所有变更按类型分门别类,每种类型定死了操作规范。

你想想看,数据库Schema变更就像给飞行中的飞机换零件。换颗螺丝和换发动机,风险能一样吗?所以,咱们得先把变更类型搞清楚。我个人习惯把它们分成五大类:表结构、索引、约束、存储过程/函数、视图。每一类的风险点、回滚策略、审批级别都不一样。

核心原则:变更类型决定了你的操作流程和风险等级。别把加索引和改表结构混为一谈,前者可能只是慢几分钟,后者搞不好要丢数据。

Schema 变更类型分类全景 Schema 变更 表结构变更 索引变更 约束变更 存储过程/函数 视图变更 ADD COLUMN DROP COLUMN MODIFY TYPE CREATE INDEX DROP INDEX REBUILD PRIMARY KEY FOREIGN KEY UNIQUE CREATE PROC ALTER PROC DROP PROC CREATE VIEW ALTER VIEW ⚠ 不同变更类型对应不同的风险等级、审批流程和回滚策略 表结构变更风险最高,视图变更风险最低,索引变更需关注性能影响

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,再也不敢裸跑了。

索引变更的最佳实践,我总结了几条:

  1. 先分析,后操作:用EXPLAIN看查询计划,确认新索引真的有用。
  2. 检查冗余索引:用pt-duplicate-key-checker或者查information_schema,把重复的索引清理掉。
  3. 在线DDL工具:大表索引变更,必须用在线工具。别省那点时间,出事了加班费都不够。
  4. 监控变更后的性能:索引加完后,观察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
视图变更 🟢 低 影响查询结果 低 开发人员 手动 + 依赖分析

好了,这一章的内容就到这儿。记住一句话:变更类型决定了你的操作流程和风险等级。别把加索引和改表结构混为一谈,也别觉得视图变更就可以随便改。每种变更都有它的脾气,摸透了,你才能游刃有余。