千万级大表加字段竟如此危险?这些翻车案例你一定要知道

作者:佚名 时间:2025-11-11 01:19

字号

身为CQITer里的科技观察者,我留意到,于数据库运维范畴,大表结构变更一直都是技术团队面临着的棘手难题,特别是处于业务快速迭代的互联网环境当中,怎样去平衡系统稳定性以及业务需求,这成为了考验技术团队的关键环节。

原生Online DDL方案

MySQL 5.6版本中引入的Online DDL功能,给表结构变更予以了新的选择,此方案准许在开展ALTER TABLE操作之际,维持表的读写功能,2023年实测得到的数据表明,针对500GB大小的表开展字段新增,要预留1TB磁盘空间。

-- 会话1:执行DDL操作
ALTER TABLE user ADD COLUMN age INT;
-- 会话2:尝试查询(被阻塞)
SELECT * FROM user WHERE id=1; -- 等待DDL完成

锁表时间 ≈ 表数据量 / 磁盘IO速度

没错,尽管Online DDL的确是减低了锁表的风险,然而在特定的场景之际,它依旧是会触发出排他锁的。那就是在添加全文索引,或者是修改列数据类型之时,这将会致使表被完全锁定。而在从库的环境当中,这是因为单线程SQL回放机制的存在,所以是有可能造成极为严重的复制延迟问题的。

停机维护方案

存在这样一种情况,针对核心业务表,有部分团队选取了计划内停机维护的方式,而这种方式是需要在业务处于低峰期的时候去开展的,一般是安排在凌晨0点到6点这个时间段之内并且有着相关的时间界定,据2024年某电商平台所进行的实践显示,在对8000万行用户表去新增三个字段时,实际所产生的停机时间达到了42分钟。

ALTER TABLE user 
ADD COLUMN age INT,
ALGORITHM=INPLACE, 
LOCK=NONE;

此方案明文要求予以前提发布中断服务的公告,且须筹备完备的回退预案。技术团队得去协同开发、测试以及运维等多个部门,以此确保变更进程能够处于可控状态。虽说存在着对业务连续性造成影响的情况,然而数据的一致性达成了充裕的保障 。

PT-OSC工具方案

Percona公司所开发的pt - online - schema - change工具借由触发器机制达成在线变更,该工具于原表之上创建三个触发器, 它们分别对应INSERT操作、UPDATE操作以及DELETE操作,借助创建临时表连同逐步同步数据,达成表结构变更。

在工具执行的进程当中,会始终持续监控系统负载,一旦发现主从延迟超出阈值之时,便会自动暂停。在2023年,某银行系统运用该工具,成功地为1.2亿条交易记录增添字段,整个这一过程持续了5小时,而业务没有察觉到。

逻辑迁移与双写方案

# 安装工具
sudo yum install percona-toolkit
# 执行迁移(添加age字段)
pt-online-schema-change \
--alter "ADD COLUMN age INT" \
D=test,t=user \
--execute

在2024年,某支付系统进行升级时,技术团队针对10亿级账户表采用了一种常常确保数据安全的方案,此方案是逻辑迁移加双写方案,该方案首先要创建新的表结构,之后还要借助数据迁移工具把历史数据进行全量同步 。

双写的时候,要求应用层面同时朝着新旧表格写入数据,以此来保证数据的一致性。历经7天的数据校验期限之后,逐渐把读请求转换到新的表格。最终在业务处于低高峰时期达成表格切换,整个流程达成零数据丢失。

-- 创建包含新字段的副本表
CREATE TABLE user_new (
    id BIGINT PRIMARY KEY,
    name VARCHAR(50),
    -- 新增字段
    age INT DEFAULT 0,
    -- 增加原表索引
    KEY idx_name(name)
) ENGINE=InnoDB;

gh-ost方案实践

binlog同步机制被GitHub开源的gh - ost工具所采用,其触发器是不使用的,该工具借助模拟从库这种方式以获取二进制日志,进而达成增量数据同步的目的,在2023年的时候于某社交平台开展实践,针对于2亿用户表新增字段的情况,仅仅造成了毫秒级的锁表现象。

// 数据写入服务
public class UserService {
    @Transactional
    public void addUser(User user) {
        // 写入原表
        userOldDAO.insert(user);
        // 写入新表(包含age字段)
        userNewDAO.insert(convertToNew(user));
    }
    
    private UserNew convertToNew(User old) {
        UserNew userNew = new UserNew();
        userNew.setId(old.getId());
        userNew.setName(old.getName());
        // 新字段处理(从其他系统获取或默认值)
        userNew.setAge(getAgeFromCache(old.getId()));
        return userNew;
    }
}

支持暂停机制的gh-ost,借助文件信号来把控切换时机,该工具给出包含剩余时间、已处理行数等在内的详尽监控指标,这些特性让其对高负载生产环境下的大表结构变更而言格外适配 。

-- 分批迁移脚本
SET @start_id = 0;
WHILE EXISTS(SELECT 1 FROM user WHERE id > @start_id) DO
    INSERT INTO user_new (id, name, age)
    SELECT id, name, 
        COALESCE(age_cache, 0) -- 从缓存获取默认值
    FROM user
    WHERE id > @start_id
    ORDER BY id
    LIMIT 10000;
    
    SET @start_id = (SELECT MAX(id) FROM user_new);
    COMMIT;
    -- 暂停100ms避免IO过载
    SELECT SLEEP(0.1); 
END WHILE;

分区表滑动窗口方案

针对那种依照时间来进行分区的大表,能够采用滑动窗口这样的方案去优化字段新增,此方案仅仅是针对最新的分区来施行结构变更,而历史分区维持不变,有某一个物联网平台在2024年运用了这个方案,成功地给每日都增添百万记录的设备表添加了字段。

操作之际,得先要停下来不着写入数据,跟着修改最新的分区定义,完了之后再一个接着一个地处理历史分区,整个流程是按阶段来开展的,此方式明显削减单次操作所涉及的数据量,降低系统承受度以及业务方面的风险。

在您依循系统架构进行演进的进程当中,当面临千万级数量的数据表所具有的结构变更之际,您究竟更为偏向于去挑选哪一种方案,以此来达成业务上各项需求以及技术领域风险之间的平衡呢?欢迎来到评论区域,踊跃分享您所亲身具备的实战阅历情形以及见解内容假设觉得这一篇文章对于您而言是具备助益属性的请予以点赞给予支持并且分享给更多存在需求的技术方面团队 。

责任编辑:CQITer新闻报料:400-888-8888   本站原创,未经授权不得转载
继续阅读
热新闻
推荐
关于我们联系我们免责声明隐私政策 友情链接