思路练习:mysql 三亿条大表如何不影响业务下增加一新列

条件:

  1. 3亿数据
  2. 服务不能停,就是不能影响业务进行

常规操作增加一列,
ALTER TABLE 你的表 ADD COLUMN 新列 varchar(128);
常规的办法会导致锁表,服务暂停

所以大致思路为

简易过程如下:

  1. 新建一个和 Table1 完全同构的 Table2
  2. 在表 Table2 上执行 ALTER TABLE 你的表 ADD COLUMN 新列 char(128)
  3. 将 Table1 中的数据拷贝到 Table2,最好用程序一点点拷贝,避免跑死
  4. 程序执行完毕,应该还有部分新数据,没有被同步,做一个新数据即时同步机制
  5. 将 Table1 重命名为 Table3, 将Table2 重命名为Table1

下面是个详细的过程,有兴趣深入研究的朋友可以查看

修改大数据表的方法:

  1. 被修改的表 Table A 需要有一个记录时间戳的字段, 这个时间戳就是每次数据更新,都会更新的字段, 这个字段需要有索引,在django里可以使用 auto_now=True
  2. 创建一个新的临时表 Table B, 不是tmp_table, 是一个新的表,但是是临时使用的。 这个表和要修改的表拥有一模一样的数据结构, 加上你要修改的部分, 比如增加的字段;
  3. 记录下Table A 的索引
  4. 删除 Table B 的全部索引
  5. 把Table A 的数据全部复制到Table B, 是不是执行 INSERT INTO B(field1, field2) SELECT field1, field2 FROM A? 当然不是, 这么做不还是锁死了Table A 么, 这里的迁移就是一个需要细分的地方,需要写一个脚本, 让程序每次读取比如5000条数据出来, 插入到Table B里面, 因为Table B 是没有索引的, 所以要当心不要使用多进程来做; 如果是多进程, 要确保插入到B的时候是不会有重复数据的; 如果是1000万的数据,每次5000条, 假设这个操作需要500ms, 那么 2000*200ms = 16 分钟。 这只是一个估值, 具体情况和服务器当时的情况有关, 不好细说。 另外, 我们要记录这个迁移开始的时间点,记为t1;
  6. 那么这个时候Table A 的数据是不是都进入了Table B 呢, 应当说差不多大部分都进入了, 但5中说, 这大概需要16分钟, 这么长的时间里, 可能有新的数据进入了, 也有可能已有的数据发生了更新, 所以我们要把Table A 中在t1 之后发生变化的数据查找出来, 然后更新到Table B 中, 我们的做法是:
    记录这个操作对应的时间点 t2
    BEGIN;
    DELETE FROM B WHERE updated_time > t1;
    INSERT INTO B(field1, field2) SELECT field1, field2 FROM A WHERE updated_time >t1;
    COMMIT;
  7. 现在A 和 B 差不多该同步了吧? 差不多了, 但是6 执行完之后, A仍然在写, 子子孙孙无穷尽也 … , 但这个时候 A 和 B 的差异已经非常非常小了, 所以在下一步,我们在一个transaction 里执行下面的操作:
    BEGIN;
    DELETE FROM B WHERE updated_time > t2;
    INSERT INTO B(field1, field2) SELECT field1, field2 FROM A WHERE updated_time >t2;
    ALTER TABLE A RENAME TO C;
    ALTER TABLE B RENAME TO A;
    COMMIT;
  8. Done
    PS: 如果A表没有时间戳, 实际上没有先见之明的人大概不会想到要预留一个时间戳的字段, 那么几乎是没有办法快速比较两个表的差异的, 这个时候我选择的做法就是放弃修改的数据, 只管新的数据了, 所以只要把t1, t2 换成id 就可以了, 这样delete 语句也省略了, 因为没啥好delete的;
    千万不要想着根据ID 来JOIN 然后更新B表的字段来补齐新的数据, 如果能把两个千万级别的表JOIN起来, 内存有多大呢?
  9. 上面的解决方案是我们第二次尝试之后犯下了一个巨大的错误,这个错误导致网站瘫痪了大概20分钟, 如果你和我一样没有发现问题,那么这就是悲剧的地方。 问题就在于我是根据上面的PS来操作的, 然后B就华丽地变成了A. B 表至今身上是没有索引的, 立即悲剧。 所以应当在第5步之后按照A的索引为B建立索引, 待索引全部好了之后, 再继续6。 如果不是走PS这条路, 而是有时间戳的字段的话, 在6的时候会发现这个问题, 因为那条Delete 慢的超出想像, 会明白这里是有问题的
  10. 新手, 请在本地练习之后, 再实际操作; 可以多操作几次, 写一个脚本,服务器上直接执行脚本.

参考资料
https://juejin.cn/post/6844903458722545677
https://zhuanlan.zhihu.com/p/162073721

延伸资料:千万级别大表如何优化
https://www.zhihu.com/question/19719997/answer/865063773


This entry was posted in 数据库 and tagged , . Bookmark the permalink.
月小升QQ 2651044202, 技术交流QQ群 178491360
首发地址:月小升博客https://java-er.com/blog/how-to-add-mysql-new-colum/
无特殊说明,文章均为月小升原创,欢迎转载,转载请注明本文地址,谢谢
您的评论是我写作的动力.

One Response to 思路练习:mysql 三亿条大表如何不影响业务下增加一新列

  1. 不错,必须顶一下!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*