
数据库 Schema 变更,真正难的从来不是“怎么改”,而是“怎么在业务不停机的情况下改”。
大家好,我是 Echo_Wish。
做运维久了,你会发现一个特别有意思的现象:
代码上线,大家已经越来越习惯灰度、回滚、监控、限流。
但一说到数据库变更,很多团队还是:
“改个字段嘛,停一下服务不就行了?”
然后凌晨两点,业务群突然开始疯狂弹消息:
“数据库连接超时了。”
“接口怎么全部 500 了?”
“谁在执行 ALTER TABLE?”
最后发现,罪魁祸首就是一条看起来平平无奇的 SQL:
ALTER TABLE user_info
ADD COLUMN phone VARCHAR(20);这篇文章就聊一个非常现实的话题:
数据库运维自动化,到底应该怎么做 Schema 在线变更,以及如何保证新旧代码兼容?
很多人理解数据库变更,思路特别简单:
修改表结构
↓
发布代码
↓
完成但在线业务真正面对的是:
旧代码 ───────┐
├──→ 数据库
新代码 ───────┘也就是说,在发布过程中,数据库面对的可能不是一套代码,而是两套甚至多套代码同时运行。
比如原来的表:
CREATE TABLE user_info (
id BIGINT PRIMARY KEY,
name VARCHAR(50)
);现在产品要求增加手机号。
最直接的方案:
ALTER TABLE user_info
ADD COLUMN phone VARCHAR(20) NOT NULL;看起来没问题。
但如果线上还有旧版本代码,它执行:
INSERT INTO user_info(id, name)
VALUES (1001, '张三');数据库就可能直接报错。
问题来了:
明明只是增加一个字段,为什么旧代码也挂了?
因为数据库 Schema 本质上也是一个 API。
这一点,我认为很多团队都低估了。
代码接口有兼容性要求:
API v1
API v2数据库同样应该有:
Schema v1
Schema v2而且数据库往往比 HTTP API 更难改。
在线 Schema 变更有一个非常重要的思想:
先扩展,再迁移,最后收缩。
也就是我们经常说的:
Expand → Migrate → Contract
这个思想看起来简单,但它几乎可以解决大量数据库发布事故。
比如我们准备增加:
phone第一步不要急着:
ALTER TABLE user_info
ADD phone VARCHAR(20) NOT NULL;而应该优先:
ALTER TABLE user_info
ADD phone VARCHAR(20) NULL;为什么允许 NULL?
因为:
旧代码根本不知道这个字段存在。
旧代码继续:
INSERT INTO user_info(id, name)
VALUES (1001, '张三');依然可以工作。
与此同时,新代码已经可以开始使用:
SELECT id, name, phone
FROM user_info;这就形成了一个非常重要的状态:
数据库:
┌────────────────────┐
│ id │
│ name │
│ phone │ ← 新字段
└────────────────────┘
旧代码 → 只认识 id/name
新代码 → 认识 id/name/phone数据库已经升级,但旧代码仍然能够正常运行。
这就是兼容性的核心。
字段加好了,并不意味着工作结束。
假设之前已经有:
1000 万用户现在新增 phone 字段。
你如果直接:
UPDATE user_info
SET phone = 'xxx';那线上数据库可能直接开始“喘气”。
尤其是大表。
所以数据迁移也必须在线化。
例如:
UPDATE user_info
SET phone = ...
WHERE id > 100000
AND id <= 110000;然后循环执行:
10万
10万
10万
10万
...而不是一次性:
UPDATE user_info
SET phone = ...;这是我比较想强调的一点。
很多所谓的“数据库自动化平台”,本质上就是:
输入 SQL
↓
执行 SQL
↓
返回结果这其实不叫数据库运维自动化。
最多只能叫:
SQL 自动执行器。
真正的数据库自动化应该知道:
这张表有多大?
当前 QPS 多高?
有没有长事务?
有没有锁等待?
当前连接数多少?
这个 SQL 会不会锁表?
执行预计影响多少行?
当前是不是业务高峰?
失败以后怎么回滚?比如我们提交:
ALTER TABLE orders
ADD COLUMN delivery_address VARCHAR(500);系统至少应该自动检查:
表名:orders
数据量:1.2 亿
当前 QPS:8600
活跃事务:37
长事务:2
数据库负载:72%
DDL 风险:中
建议:
❌ 当前不建议执行这才叫真正意义上的:
数据库运维自动化。
这是 Schema 兼容里面非常经典的一招。
例如原来只有:
username现在准备改成:
user_name千万不要直接:
ALTER TABLE user_info
CHANGE username user_name VARCHAR(50);因为旧代码还在使用:
SELECT username
FROM user_info;直接改名:
旧代码瞬间报错。
更稳妥的方式是:
ALTER TABLE user_info
ADD COLUMN user_name VARCHAR(50);然后新代码开始:
写 username
写 user_name也就是双写:
user.setUsername(username);
user.setUserName(username);读取的时候,新代码优先读取:
String name = user.getUserName();
if (name == null) {
name = user.getUsername();
}经过一段时间以后:
旧代码
↓
username
新代码
↓
user_name
数据逐渐完成迁移最终才删除旧字段。
把整个过程串起来,其实特别清晰。
ALTER TABLE user_info
ADD COLUMN user_name VARCHAR(50);username
↓
┌───────────────┐
│ username │
│ user_name │
└───────────────┘UPDATE user_info
SET user_name = username
WHERE user_name IS NULL
LIMIT 10000;循环执行。
读取 user_name
写入 user_nameusername ← 不再使用最后才:
ALTER TABLE user_info
DROP COLUMN username;这时候,旧代码已经彻底退出。
删除字段才是最后一步,而不是第一步。
很多开发喜欢这样操作:
ALTER TABLE user_info
DROP COLUMN old_field;然后代码:
user.getOldField();线上直接:
Unknown column 'old_field'所以数据库 Schema 变更里面有一个特别重要的原则:
新增字段通常容易兼容,删除字段通常最危险。
因为:
ADD属于扩展。
而:
DROP
RENAME
TYPE CHANGE往往属于破坏性变更。
因此数据库自动化平台最好能够自动识别:
ADD COLUMN → 低风险
ADD INDEX → 中风险
MODIFY COLUMN → 高风险
DROP COLUMN → 极高风险
RENAME COLUMN → 极高风险然后采用不同审批策略。
很多线上事故不是因为字段,而是:
加索引。
开发发现:
SELECT *
FROM orders
WHERE user_id = 10001;很慢。
于是:
CREATE INDEX idx_user_id
ON orders(user_id);然后:
线上开始抖。
因为在某些数据库和版本、表规模、执行方式下,创建索引可能产生明显的 I/O、CPU 或锁竞争。
所以自动化系统不能只判断:
SQL 是 CREATE INDEX
↓
执行而应该:
CREATE INDEX
↓
检查数据库类型
↓
检查版本
↓
检查表规模
↓
检查当前负载
↓
判断是否支持 Online DDL
↓
生成执行计划
↓
执行
↓
监控例如 MySQL 环境下,可以结合具体版本和存储引擎评估:
ALTER TABLE orders
ADD INDEX idx_user_id(user_id),
ALGORITHM=INPLACE,
LOCK=NONE;但这里也不要形成一个误区:
看到
LOCK=NONE就认为绝对不会阻塞。
数据库的实际行为还受到具体版本、操作类型、存储引擎、并发事务等因素影响。
所以:
Online DDL ≠ 零风险 DDL。
我一直觉得:
数据库自动化最重要的能力,不是油门,而是刹车。
比如有人提交:
ALTER TABLE big_table
MODIFY COLUMN amount DECIMAL(10,2);平台发现:
表数据量:8.7 亿
当前 QPS:12000
数据库 CPU:82%
存在长事务:3
DDL 类型:字段类型修改
风险等级:高这时候最好的自动化结果不是:
正在执行……而应该:
❌ 拒绝自动执行
原因:
1. 超大表
2. 高峰期
3. 字段类型发生变化
4. 存在长事务
建议:
低峰期执行
或采用在线 Schema 变更工具能够拒绝危险操作,本身就是自动化能力。
如果团队规模再大一点,我建议直接把数据库变更纳入发布流水线。
例如:
Git
│
├── application
│
└── migrations
│
↓
Schema Analyzer
│
↓
Compatibility Check
│
↓
Risk Assessment
│
├── LOW ─────→ 自动执行
│
├── MEDIUM ──→ 人工审批
│
└── HIGH ────→ 禁止自动执行
│
↓
DBA数据库变更文件也可以进入 Git:
db/
├── V001__create_user.sql
├── V002__add_phone.sql
├── V003__add_user_name.sql
└── V004__create_user_index.sql这样数据库就不再是:
“谁在生产库上手工改了什么?”而变成:
“Git 里记录了这次 Schema 演进。”这才是真正可审计、可追踪的数据库运维。
如果让我给团队制定规则,我会简单粗暴地定下面几条。
不要:
开发人员
↓
Navicat
↓
生产数据库而应该:
Git
↓
Review
↓
自动检查
↓
审批
↓
执行优先:
ADD COLUMN谨慎:
MODIFY COLUMN最后:
DROP COLUMN发布过程中:
旧代码 + 新 Schema必须能够运行。
同时:
新代码 + 旧 Schema最好也能够运行,至少要通过明确设计避免发布窗口中的不兼容。
不要:
UPDATE 1亿条数据;应该:
分批
↓
限速
↓
监控
↓
异常暂停
↓
继续例如:
LOW
└── ADD NULL COLUMN
MEDIUM
└── ADD INDEX
HIGH
├── MODIFY COLUMN
├── RENAME COLUMN
└── 大表 DDL
CRITICAL
└── DROP COLUMN不同等级使用不同审批机制。
数据库运维发展到现在,真正高级的能力已经不是:
“我会写 SQL。”
而是:
“我能让数据库在业务不停机的情况下安全地演进。”
这两句话,看起来差不多,实际上差得非常远。
以前我们习惯:
数据库是基础设施
代码依赖数据库
数据库由 DBA 管现在越来越应该把数据库 Schema 当成:
代码的一部分它需要:
版本管理
自动检测
兼容性分析
灰度发布
风险控制
审计
监控
回滚策略尤其是微服务、Kubernetes、DevOps 体系越来越成熟以后,应用可以秒级发布,数据库却不能再靠人工凌晨两点改表。
所以我认为未来真正成熟的数据库运维平台,不应该只是帮你“执行 SQL”。
它应该在你提交 SQL 的时候告诉你:
这条 SQL 能不能执行。
在执行之前告诉你:
现在是不是执行的好时机。
执行过程中告诉你:
数据库有没有出现异常。
执行完成以后告诉你:
Schema、代码和数据是不是已经完成迁移。
甚至在你准备删除字段的时候直接拦住:
“等等,还有 3 个服务正在使用这个字段。”
这才是我理解的:
数据库运维自动化。
说到底,Schema 变更不是一次 SQL 操作,而是一次线上系统的“渐进式升级”。
真正靠谱的团队,不是从来不改数据库。
而是:
数据库天天改,业务照样跑;代码持续发布,线上依然稳。
这才是数据库运维自动化真正应该追求的终点。
Echo_Wish|专注云原生、DevOps、数据库与 AI 运维实践
别把数据库当成“存数据的地方”。 它其实是整个业务系统最不能随便动的核心基础设施。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。