在我們業務快速發展的過程中,數據量必然也會迎來突飛猛漲。那么當我們的數據量百倍、千倍、萬倍、億倍增長后,原有的單表性能就不能滿足我們日常的查詢和寫入了,此時數據架構就不得不進行拆分,比如單表拆分成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數據一致性,在實際遷移時可以和其它步驟同時進行。拆分的整體時間周期一般為半個月到一個月之間,主要是其中為了確保數據一致性,需要灰度比較久。
上面就是我們工作中常用的數據庫表遷移方案了,有同學可能會甩鍋給最初設計庫表的同學,為啥當初設計的時候不考慮下分表呢?其實,這個是很難預估的,誰能知道最初一年只有幾萬數據的簽到業務,現在每年會新增十來億呢?這個主要取決于公司的發展了。