大表新增字段

1. 问题

如果直接插入字段,会对这张表加元数据锁,导致整张表被锁住,所有请求会阻塞,可能会导致服务器 OOM 问题,甚至会导致雪崩。

2. 解决

2.1. MySQL 5.6 之前

2.1.1. 主从切换

  1. MySQL 主库 A,从库 B;
  2. 关闭主从同步,负载均衡让 A 全部承载读写操作;
  3. B 新增字段,新增完成之后,恢复主从复制;从库 B 追赶主库 A 的数据;
  4. 追赶完成后,从库 B 升级为主库,进行主从切换,此后由主库 B 承载读写操作;
  5. 从库 A 进行字段新增,新增完成后;
  6. 切换回原有的主从结构。
  • 此过程看起开复杂,实际一点也不简单,存在巨大的风险;
  • 主从结构在这个过程中遭到破坏,数据库读写压力需要预估;
  • 操作复杂。

2.1.2. 幽灵表

  1. 在原表的基础上复制出一张新的表结构(幽灵表);
  2. 在幽灵表中新增字段;
  3. 将原表数据同步到幽灵表;
    • 增量同步,binlog 监听、触发器
    • 全量同步,批量分块
  4. 同步完成后,通过原子操作修改表名
  • 数据同步过程对数据库造成压力较难预估;
  • 更改表名的操作会阻塞一段时间的请求。

2.1.3. 拓展表(JOIN)

新建一张表,在新表中添加字段,不会阻塞原表,新表和原表进行主键的连接查询,风险极低。

  • 在应用层通过JOIN进行联表查询、保证一致性等操作,对性能可能会造成影响;
  • 新表中添加字段可能还会导致本文所论述的问题;

2.1.4. 拓展字段

建表时添加拓展字段,或JSON类型字段;

  • 维护难,需写好注释以及相关文档。

2.1.5. 压力产品

代码较难实现且风险较大,跟产品沟通好,放在其他存储介质中。

2.2. MySQL 5.6 之后

2.2.1. Online DDL

Online DDL是 MySQL 5.6 之后的新特性,可以在不大幅度影响业务读写的情况下,动态改变表结构。

  1. 准备阶段:短暂持有元数据锁;
    • 防止其他 DDL 改动
  2. 执行阶段:执行 DDL 语句;
    • COPY:原表的基础上复制出新表,原表加锁,阻塞写操作,写操作存储在日志缓存,读操作变慢(竞争 IO);
    • INPLACE(5.7 默认):原表基础上直接新增字段,row_log 记录增删改,查询仍然可以正常进行;
    • INSTANT(8.0+):只修改表的元数据信息而不会动物理信息,查询数据时动态生成默认值;
  3. 第三阶段:短暂持有元数据锁
    • COPY:原子操作,删除原表,把日志的缓存同步到新表中;
    • INPLACE:设置元数据字段指针指向位置,同步缓存中的写操作;
    • INSTANT:只需确认一致性。

评论