在我们业务快速发展的过程中,数据量必然也会迎来突飞猛涨。那么当我们的数据量百倍、千倍、万倍、亿倍增长后,原有的单表性能就不能满足我们日常的查询和写入了,此时数据架构就不得不进行拆分,比如单表拆分成10张表、100张表、单个月分多张表等等。下面我们针对具体案例分析下这种情况。
一、现状分析
直播签到业务中,有如下两张表,签到表和用户签到记录表。其中用户签到记录表t_sign_in_record现有数据量达到了18亿,数据占用空间为233G,索引占用空间为310G,总占用空间为543G。单表数据量太大,导致数据查询和写入性能过低。如果某条sql查询未使用到索引,很容易就会将数据库打挂。所以对t_sign_in_record的单表拆分很迫切。
CREATE TABLE `t_sign_in` (`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增id',`sign_in_id` varchar(64) NOT NULL DEFAULT '' COMMENT '签到id',`shop_id` varchar(64) NOT NULL DEFAULT '' COMMENT '商家id',`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),KEY `idx_shop_sign` (`shop_id`,`sign_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='签到表';CREATE TABLE `t_sign_in_record` (`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增id',`sign_in_id` varchar(64) NOT NULL DEFAULT '' COMMENT '签到id',`shop_id` varchar(64) NOT NULL DEFAULT '' COMMENT '商家id',`user_id` varchar(64) NOT NULL DEFAULT '' COMMENT '用户id',`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),KEY `idx_shop_sign_user` (`shop_id`,`sign_id`,`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户签到记录表';
二、数据库设计
1、分表实例:依现有的存储空间做10倍的余量参考,所需空间大小为5.3T左右。而腾讯云MySQL最高支持存储空间上限是6TB,所以原有的腾讯云MySQL实例能满足需求。如果数据量更大,超过6TB的话,可以考虑将数据库迁移至TDSQL-C MySQL 版,最高支持至400TB。参考文档:https://cloud.tencent.com/document/product/1003/30488
2、分表数量:按照业务增长规模,每个月增量7千万-1亿,预计分10张表,平均单表数量在700万-1000万左右。
3、分表算法:根据签到表t_sign_in的created_at中的年月日做如下算法,能让数据较均匀的落入每个月的10个分表中。分表的10张表名为t_sign_in_record_0、t_sign_in_record_1......t_sign_in_record_9。
三、迁移方案
1、第一阶段:
双写数据,即将数据同时写入旧表t_sign_in_record和分表t_sign_in_record_0、t_sign_in_record_1、t_sign_in_record_2...中,此时需要去代码层创建和更新t_sign_in_record表的地方做处理,写入旧表的同时,写一份数据到新的分表。
2、第二阶段:
双写数据一致性校验,经过第一阶段的双写后,我们的新数据已经同时存在于新表和旧表中了,这个时候需要去check一下数据在旧表t_sign_in_record和分表t_sign_in_record_0...中是不是保持一致。有两个常用的检验方法:
(1)第一种是人工校验,即随机挑选一些签到,去check对应的签到记录条数和数据记录是否一致;
(2)另一种是写脚本去校验(全量数据或部分数据),将其中新老表中数据不匹配的记录输出到日志中,再去排查。
3、第三阶段:
写脚本,将旧表t_sign_in_record的存量数据都刷到分表t_sign_in_record_0...中。
4、第四阶段:
跟第二阶段的方式一样,去check第三阶段刷入的分表存量数据是否和旧表一致。
5、第五阶段:
切读,将现网读 t_sign_in_record 表的地方都改为读新分表 t_sign_in_record_0、t_sign_in_record_1、t_sign_in_record_2...
6、第六阶段:
确认旧表 t_sign_in_record没有新的读请求。可通过DBA审计的方式。
7、第七阶段:
停止双写,即将代码改为只往新分表 t_sign_in_record_0、t_sign_in_record_1、t_sign_in_record_2... 里面写数据。
如上所述,迁移方案大概分为7个阶段,其中有三个阶段都是check数据一致性,在实际迁移时可以和其它步骤同时进行。拆分的整体时间周期一般为半个月到一个月之间,主要是其中为了确保数据一致性,需要灰度比较久。
上面就是我们工作中常用的数据库表迁移方案了,有同学可能会甩锅给最初设计库表的同学,为啥当初设计的时候不考虑下分表呢?其实,这个是很难预估的,谁能知道最初一年只有几万数据的签到业务,现在每年会新增十来亿呢?这个主要取决于公司的发展了。