LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

数据库千万级的大表如何新增字段?

freeflydom
2025年8月1日 9:48 本文热度 89

前言

线上千万级的大表在新增字段的时候,一定要小心,我见过太多团队在千万级大表上执行DDL时翻车的案例。

很容易影响到正常用户的使用。

本文将深入剖析大表加字段的核心难点,并给出可落地的解决方案。

希望对你会有所帮助。

1.为什么大表加字段如此危险?

核心问题:MySQL的DDL操作会锁表

当执行ALTER TABLE ADD COLUMN时:

  1. MySQL 5.6之前:全程锁表(阻塞所有读写)
  2. MySQL 5.6+:仅支持部分操作的Online DDL

通过实验验证锁表现象:

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

锁表时间计算公式:

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

对于1000万行、单行1KB的表,机械磁盘(100MB/s)需要100秒的不可用时间!

如果在一个高并发的系统中,这个问题简直无法忍受。

那么,我们要如何解决问题呢?

2.原生Online DDL方案

在MySQL 5.6+版本中可以使用原生Online DDL的语法。

例如:

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

实现原理

致命缺陷

  1. 仍可能触发表锁(如添加全文索引)
  2. 磁盘空间需双倍(实测500GB表需要1TB空闲空间)
  3. 主从延迟风险(从库单线程回放)

3.停机维护方案

适用场景

  • 允许停服时间(如凌晨3点)
  • 数据量小于100GB(减少导入时间)
  • 有完整回滚预案

4.使用PT-OSC工具方案

Percona Toolkit的pt-online-schema-change这个是我比较推荐的工具。

工作原理:

操作步骤:

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

5.逻辑迁移 + 双写方案

还有一个金融级安全的方案是:逻辑迁移 + 双写方案。

适用场景

  • 字段变更伴随业务逻辑修改(如字段类型变更)
  • 要求零数据丢失的金融场景
  • 超10亿行数据的表

实施步骤

1. 创建新表结构

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

2. 双写逻辑实现(Java示例)

// 数据写入服务
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;
    }
}

3. 数据迁移(分批处理)

-- 分批迁移脚本
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;

4. 灰度切换流程

这套方案适合10亿上的表新增字段,不过操作起来比较麻烦,改动有点大。

6.使用gh-ost方案

gh-ost(GitHub's Online Schema Transmogrifier)是GitHub开源的一种无触发器的MySQL在线表结构变更方案

专为解决大表DDL(如新增字段、索引变更、表引擎转换)时锁表阻塞、主库负载高等问题而设计。

其核心是通过异步解析binlog,替代触发器同步增量数据,显著降低对线上业务的影响。

与传统方案对比

  • 触发器方案(如pt-osc)
    在源表上创建INSERT/UPDATE/DELETE触发器,在同一事务内将变更同步到影子表。
    痛点

    • 触发器加重主库CPU和锁竞争,高并发时性能下降30%以上
    • 无法暂停,失败需重头开始
    • 外键约束支持复杂
  • gh-ost方案

    • 伪装为从库:直连主库或从库,拉取ROW格式的binlog,解析DML事件(INSERT/UPDATE/DELETE)
    • 异步应用:将增量数据通过独立连接应用到影子表(如REPLACE INTO处理INSERT事件),与主库事务解耦
    • 优先级控制:binlog应用优先级 > 全量数据拷贝,确保数据强一致

关键流程:

  • 全量拷贝:按主键分块(chunk-size控制)执行INSERT IGNORE INTO _table_gho SELECT ...,避免重复插入
  • 增量同步
    • INSERT → REPLACE INTO
    • UPDATE → 全行覆盖更新
    • DELETE → DELETE
  • 原子切换(Cut-over)
    1. 短暂锁源表(毫秒级)
    2. 执行原子RENAME:RENAME TABLE source TO _source_del, _source_gho TO source
    3. 清理旧表(_source_del

典型命令示例:

gh-ost \
--alter="ADD COLUMN age INT NOT NULL DEFAULT 0 COMMENT '用户年龄'" \
--host=主库IP --port=3306 --user=gh_user --password=xxx \
--database=test --table=user \
--chunk-size=2000 \       # 增大批次减少事务数
--max-load=Threads_running=80 \ 
--critical-load=Threads_running=200 \
--cut-over-lock-timeout-seconds=5 \  # 超时重试
--execute \               # 实际执行
--allow-on-master         # 直连主库模式

2. 监控与优化建议

  • 进度跟踪
echo status | nc -U /tmp/gh-ost.sock  # 查看实时进度
  • 延迟控制
    • 设置--max-lag-millis=1500,超阈值自动暂停
    • 从库延迟过高时切换为直连主库模式
  • 切换安全
    使用--postpone-cut-over-flag-file人工控制切换时机

7.分区表滑动窗口方案

适用场景:

  • 按时间分区的日志型大表
  • 需要频繁变更结构的监控表

核心原理:
通过分区表特性,仅修改最新分区结构。

操作步骤

修改分区定义:

-- 原分区表定义
CREATE TABLE logs (
    id BIGINT,
    log_time DATETIME,
    content TEXT
) PARTITION BY RANGE (TO_DAYS(log_time)) (
    PARTITION p202301 VALUES LESS THAN (TO_DAYS('2023-02-01')),
    PARTITION p202302 VALUES LESS THAN (TO_DAYS('2023-03-01'))
);
-- 添加新字段(仅影响新分区)
ALTER TABLE logs ADD COLUMN log_level VARCHAR(10) DEFAULT 'INFO';

创建新分区(自动应用新结构):

-- 创建包含新字段的分区
ALTER TABLE logs REORGANIZE PARTITION p202302 INTO (
    PARTITION p202302 VALUES LESS THAN (TO_DAYS('2023-03-01')),
    PARTITION p202303 VALUES LESS THAN (TO_DAYS('2023-04-01'))
);

历史数据处理:

-- 仅对最近分区做数据初始化
UPDATE logs PARTITION (p202302) 
SET log_level = parse_log_level(content);

8.千万级表操作注意事项

  1. 主键必须存在(无主键将全表扫描)
  2. 磁盘空间监控(至少预留1.5倍表空间)
  3. 复制延迟控制
SHOW SLAVE STATUS; 
-- 确保Seconds_Behind_Master < 10
  1. 灰度验证步骤

    • 先在从库执行
    • 检查数据一致性
    • 低峰期切主库
  2. 字段属性选择

    • 避免NOT NULL(导致全表更新)
    • 优先使用ENUM代替VARCHAR
    • 默认值用NULL而非空字符串

9.各方案对比

以下是针对千万级MySQL表新增字段的6种方案的对比。

方案锁表时间业务影响数据一致性适用场景复杂度
原生Online DDL秒级~分钟级中(并发DML受限)强一致<1亿的小表变更
停机维护小时级高(服务中断)强一致允许停服+数据量<100GB
PT-OSC毫秒级(仅cut-over)中(触发器开销)最终一致无外键/触发器的常规表
逻辑迁移+双写0低(需改代码)强一致金融级核心表(10亿+)
gh-ost毫秒级(仅cut-over)低(无触发器)最终一致高并发大表(TB级)中高
分区滑动窗口仅影响新分区分区级一致按时间分区的日志表

总结

  1. 常规场景(<1亿行)

    • 首选 Online DDLALGORITHM=INSTANT,MySQL 8.0秒级加字段)
    • 备选 PT-OSC(兼容低版本MySQL)
  2. 高并发大表(>1亿行)

    • 必选 gh-ost(无触发器设计,对写入影响<5%)
  3. 金融核心表

    • 双写方案 是唯一选择(需2-4周开发周期)
  4. 日志型表

    • 分区滑动窗口 最优(仅影响新分区)
  5. 紧急故障处理

    • 超百亿级表异常时,考虑 停机维护 + 回滚预案

给大家一些建议

  • 加字段前优先使用 JSON字段预扩展ALTER TABLE user ADD COLUMN metadata JSON
  • 万亿级表建议 分库分表 而非直接DDL
  • 所有方案执行前必须 全量备份mysqldump + binlog
  • 流量监测(Prometheus+Granfa实时监控QPS)

在千万级系统的战场上,一次草率的ALTER操作可能就是压垮骆驼的最后一根稻草。

​转自https://www.cnblogs.com/12lisu/p/19008591


该文章在 2025/8/1 9:48:49 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved