前言
在日常开发中,我们经常需要为数据库表加字段。对于一张只有几千行的小表来说,一句 alter table 瞬间完成,几乎没有任何感知。
但当这张表的数据量达到千万级甚至上亿级时,事情就变得复杂起来了。
你可能会问:“我只是加个字段,又不是删数据,至于大动干戈吗?”
至于。非常至于。
因为一旦你在大表上执行alter table,数据库可能会长时间锁表,导致:
- 读写阻塞:所有查询和写入请求被挂起
- 业务中断:订单无法提交、支付失败、页面卡顿
- 主从延迟加剧:主库执行 ddl 时,从库复制延迟飙升,影响报表、备份甚至高可用切换
- 连接堆积:应用连接池耗尽,服务雪崩
假设有一个用户中心的核心表,数据量超过 5000 万。如果直接执行 alter table 加字段,可能会导致服务中断近几分钟。这期间可能会导致各种订单流失、客服报警,于是老板震怒……
大表结构变更,必须慎之又慎。
为什么大表alter table会这么慢?
要解决问题,先理解原理。
mysql 的 alter table 操作本质上是重建表(rebuild table)的过程:
- 创建一个临时的新结构表
- 将原表数据逐行拷贝到新表
- 删除原表,重命名新表
- 重建索引
这个过程在早期版本(如 mysql 5.5 及以前)中是全程锁表的(lock=exclusive),意味着整个操作期间,任何 dml(insert/update/delete)都会被阻塞。
虽然 mysql5.6 引入了online ddl,允许部分ddl操作在不锁表的情况下进行,但仍然存在性能影响和兼容性限制。
主流解决方案对比
针对大表加字段的问题,业界有几种成熟方案。下面我们逐一分析其原理、优缺点及适用场景。
方案一:低峰期直接alter table(适用于小表)
最简单粗暴的方式:
alter table user add column new_flag tinyint default 0;
适用场景:
- 表数据量较小(< 100 万)
- 业务容忍短暂不可用
- 无主从延迟要求
优点:
- 操作简单,无需额外工具
- 成本最低
缺点:
- 锁表时间不可控,大表风险极高
- 无法做到“无感变更”
建议:仅用于测试环境或极小表。
方案二:使用mysql online ddl(推荐用于中等表)
从 mysql5.6 开始,支持online ddl,允许在执行ddl时不阻塞dml操作。
关键语法:
alter table user add column new_flag tinyint default 0, algorithm=inplace, lock=none;
algorithm=inplace:使用原地修改算法,避免全表重建 lock=none:表示不加锁,允许并发读写
支持情况(以加字段为例):
| mysql版本 | 是否支持inplace | 备注 |
|---|---|---|
| < 5.6 | 不支持 | 全表重建,锁表 |
| 5.6-5.7 | 部分支持 | 支持末尾加字段 |
| 8.0+ | 增强支持 | 支持更复杂的ddl |
注意:即使lock=none,也并非完全无影响。拷贝数据期间仍会占用i/o和cpu,可能影响性能。
优点:
- 原生支持,无需外部工具
- 真正实现不停机变更
缺点:
- 不支持所有ddl类型(如修改列类型仍需重建)
- 大表执行时间长,仍可能引发主从延迟
- 需要足够磁盘空间(临时文件)
建议:适用于100万~100万行的中等表,且使用mysql 5.7+。
方案三:使用pt-osc(percona toolkit)——生产环境首选
pt-osc(pt-online-schema-change) 是percona 提供的开源工具,专为大表在线 ddl 设计。
工作原理:
- 创建一个新表tbl_new,结构包含新字段
- 在原表上创建三个触发器(insert/update/delete),同步变更到新表
- 分批将原表数据拷贝到新表(每次只拷几百条,减少压力)
- 数据同步完成后,原子性重命名:rename table tbl to tbl_old, tbl_new to tbl
- 删除旧表
示例命令:
pt-online-schema-change \ --host=localhost \ --user=root \ --password=your_password \ --alter="add column membership_level tinyint default 0 comment '会员等级'" \ d=ecdb,t=user \ --chunk-size=5000 \ --max-load="threads_running=50" \ --critical-load="threads_running=100" \ --sleep=0.5 \ --execute
参数说明:
- --chunk-size:每次拷贝的数据量
- --max-load:负载上限,超过则暂停
- --critical-load:致命负载,超过则终止
- --sleep:每批拷贝后休眠时间,降低压力
优点:
- 几乎不影响线上业务
- 支持精细控制资源占用
- 成熟稳定,被大量互联网公司采用
缺点:
- 需要安装 percona toolkit
- 需要额外磁盘空间(双表并存)
- 触发器带来轻微性能开销(通常 < 5%)
- 不支持有外键引用的表(除非用 --alter-foreign-keys-method)
建议:千万级大表的首选方案。
方案四:手动模拟 pt-osc(无工具时的备选)
如果你无法使用 pt-osc(如安全限制、权限问题),可以手动实现类似流程。
-- 1. 创建新表
create table user_new like user;
alter table user_new add column new_flag tinyint default 0;
-- 2. 分批迁移数据
insert into user_new select *, 0 from user where id between 1 and 100000;
-- 循环执行,逐步迁移
-- 3. 数据追平后,创建触发器同步变更
delimiter $$
create trigger user_insert_trg after insert on user
for each row begin
insert into user_new values (new.*, 0);
end$$
-- 同样创建 update 和 delete 触发器
delimiter ;
-- 4. 短暂停机,切换表名(秒级)
rename table user to user_old, user_new to user;
-- 5. 验证无误后删除旧表
drop table user_old;优点:
- 不依赖外部工具
- 完全可控
缺点:
- 手动操作易出错
- 切换瞬间仍有短暂锁表(rename 是原子操作,但需独占表名)
- 需精确控制触发器逻辑
建议:仅作为 pt-osc 不可用时的备选方案。
实战案例
需求背景
电商平台用户表user,数据量 6200 万,需添加membership_level字段用于会员体系升级。
技术选型
- mysql 5.7.30
- 使用pt-osc 实现在线变更
- 选择凌晨2:00 执行(业务低峰)
执行步骤
1.前置检查
- 磁盘剩余空间 ≥ 1.5 倍原表大小(约 120gb)
- 备份表结构与数据(mysqldump + binlog)
- 准备回滚脚本
2.执行变更
pt-online-schema-change \ --alter="add column membership_level tinyint default 0 comment '会员等级'" \ d=ecdb,t=user \ --chunk-size=10000 \ --max-load="threads_running=40" \ --critical-load="threads_running=80" \ --sleep=0.2 \ --print \ --execute
3.实时监控
- show processlist; 查看拷贝进度
- 监控 cpu、i/o、主从延迟
- 应用层监控错误率、响应时间
mysql 8.0的新变化
mysql8.0对ddl进行了重大优化:
原子性 ddl:ddl 操作支持事务回滚(如失败可自动清理) 更快的加字段:新增字段默认为“即时添加”(instant add column),仅修改元数据,几乎瞬间完成 支持更多 inplace 操作
例如:
alter table user add column new_col varchar(50) default null, algorithm=instant;
注意:instant算法仅支持在表末尾添加字段,且不能是主键或not null无默认值的字段。
📌 建议:如果使用 mysql 8.0+,优先尝试 algorithm=instant,性能极佳。
最佳实践总结
| 步骤 | 建议 |
|---|---|
| 1.评估影响 | 确认表大小、qps、主从架构、业务容忍度 |
| 2.选择方案 | < 100万:直接 alter;100万~1000万:online ddl;> 1000万:pt-osc |
| 3.低峰操作 | 尽量在凌晨或流量低谷期执行 |
| 4.做好备份 | ddl 前必须备份表结构和数据 |
| 5.控制节奏 | 使用--chunk-size、--max-load控制资源占用 |
| 6.监控与回滚 | 实时监控数据库状态,准备回滚预案 |
| 7.文档记录 | 记录操作时间、命令、负责人、结果 |
补充建议
1.避免 not null 无默认值的字段
加 not null 字段需全表初始化,代价极高。建议先加 default null 或带默认值。
2.尽量在表末尾加字段
有助于触发 instant 算法(mysql 8.0+)
3.慎用外键
外键会增加 pt-osc 的复杂度,建议业务层维护一致性
4.考虑影子表(shadow table)模式
对于极端敏感的系统,可采用双写影子表 + 流量切换的方式,实现零停机变更
5.替代工具推荐 gh-ost(github 开源):基于 binlog 同步,无需触发器,更安全 alisql online ddl:阿里云优化版本,支持更多场景
技术无小事,细节定成败。
选择合适的方案,做好充分准备,才能真正做到“变更无感,业务无忧”。
到此这篇关于浅谈千万级大表如何新增字段的文章就介绍到这了,更多相关千万级大表新增字段内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论