千万级业务表新增字段低风险方案

43 阅读3分钟

方案:影子表 + 数据迁移方案(适用于所有数据库,低风险)

适用场景:

  • 数据库不支持在线 DDL(如 MySQL 5.5 及以下、部分老版本 Oracle);
  • 不想依赖第三方工具(如pt-online-schema-change),希望自主控制迁移过程;
  • 新增字段是核心字段,需频繁查询,不能用扩展表(避免关联)。

核心思路:

创建一个 “影子表”(复制原表结构 + 新增字段),逐步迁移数据,最后切换流量到影子表,原表作为备份,全程无锁表。

实操步骤:

  1. 创建影子表(复制原表结构 + 新增字段)

sql

-- 1. 复制原表结构(含索引、约束,不含数据)CREATE TABLE t_order_shadow LIKE t_order;-- 2. 给影子表新增字段ALTER TABLE t_order_shadow 
ADD COLUMN `order_ext_info` VARCHAR(128) NULL,ADD COLUMN `is_vip_order` TINYINT(1) NULL DEFAULT 0;-- 3. 给影子表添加原表的所有索引(确保查询性能一致)ALTER TABLE t_order_shadow ADD INDEX idx_user_id (`user_id`);ALTER TABLE t_order_shadow ADD INDEX idx_create_time (`create_time`);
  1. 数据迁移(分批次 + 双写同步)

  • 阶段 1:批量迁移历史数据(低峰期执行,无锁):
  • sql
-- 分批次插入历史数据(每次1万条,避免大事务)INSERT INTO t_order_shadow 
SELECT *, NULL, 0 FROM t_order 
WHERE order_id > (SELECT COALESCE(MAX(order_id), 0) FROM t_order_shadow)LIMIT 10000;
  • 循环执行以上 SQL,直到所有历史数据迁移完成(千万级数据约需 1-2 小时,低峰期可接受)。
  • 阶段 2:双写同步(确保新数据一致):业务代码修改为 同时写入原表和影子表(用事务保证原子性),持续 1-2 天,确保双写无异常:
  • java
  • 运行
@Transactional(rollbackFor = Exception.class)public void createOrder(OrderDTO orderDTO) {// 1. 写入原表(兼容旧逻辑)
    tOrderMapper.insert(orderDTO);// 2. 写入影子表(含新增字段)
    tOrderShadowMapper.insert(convertToShadowDTO(orderDTO));}
  • 阶段 3:校验数据一致性:对比原表和影子表的核心字段(order_idamountstatus),确保无差异:
  • sql
-- 校验数据量一致SELECT COUNT(*) FROM t_order;SELECT COUNT(*) FROM t_order_shadow;-- 校验随机数据一致SELECT * FROM t_order WHERE order_id IN (123, 456, 789);SELECT * FROM t_order_shadow WHERE order_id IN (123, 456, 789);
  1. 切换流量(毫秒级)

  • 业务低峰期,修改应用配置,将所有读写操作切换到影子表:
  • yaml
# Spring Boot配置示例:将订单表名改为影子表order:table-name: t_order_shadow
  • 切换后观察 1-2 小时,无异常则删除原表(或重命名为t_order_old保留 1 个月)。

优缺点:

优点缺点
全程无锁表,迁移过程可控,适合所有数据库流程复杂,需分 3 个阶段,耗时较长(1-3 天)
数据一致性有保障(双写 + 校验)双写阶段会增加数据库写入压力(可通过分库分表分摊)
切换后性能与原表一致(无关联查询)需业务代码配合修改(双写、配置切换)