背景
由于公司业务使然,使一张表的数据膨胀到148G,其实数据本身大概就50G(4亿多条数据),只不过有两个联合索引,才导致这个表占这么大的存储,此时对这个表进行CRUD,一是更新查询时间长,二是特别消耗CPU资源。DBA老是发送慢SQL警告,运维也发服务器CPU消耗太大警告,在不影响线上业务的情况下,只能进行快速分表。但是由于历史原因,调研的sharding-jdbc与mycat都不能很好的融合到自己现有的项目中(PS:是自己太菜了,不知道怎么改造原有的框架底层那一套MySQL配置),所以决定手动分表。
过程
结合具体业务
该表是定时任务产生明天相应的数据,每天晚上会定时更新当天的数据状态,经过调研查询大多数场景查昨天今天明天的数据,所以决定用原有的数据表作为热点数据表,只存储昨天今天明天的数据,其他的表路由存储昨天之前的数据。-
结合具体技术
原有框架使用mybatis,很多场景用底层接口进行相应的增删改查,所以结合mybatis的特性,在其实体类上进行配置,默认路由到热点表,如果其他表需要规则路由到相应的表,针对自定义mapper方法需要手动传参路由表;
实体类路由的表名称
mapper方法手动传参 进行数据归类
3.1 代码逻辑:由于原表有两个联合索引且有很大的数据量,所以从原表读不要删除,索引重构是很耗时间的,然后路由到不同的逻辑表里;
3.2 SQL语句:
insert into 库.逻辑表
(order_id, `date`,version, create_time,update_time)
select order_id, `date`,version, create_time,update_time
from 库.原始表 b where b.date > '2022-02-15';
- 发布逻辑代码
在这里发现了id只是作为主键不参与任何业务的好处,对迁移表数据影响较小,之前我用orderId求hashCode然后求余决定路由到逻辑表,如下:
/**
* 路由结果
*/
@UtilityClass
@Slf4j
public class ResultTableNameRouteUtils {
/**
* 根据orderId hashCode获取对应的数据表名称
*
* @param orderId 订单id
* @return 数据表名
*/
public String getTableNameByOrderId(String orderId) {
String tableName = "表_" + Math.abs(orderId.hashCode()) % 10;
return tableName;
}
/**
* 获取所有的数据表名称
*
* @return 数据表名集合
*/
public List<String> getTableNames() {
List<String> tableNames = new ArrayList<>();
for (int i = 0; i < 10; i++) {
tableNames.add("表_" + i);
}
return tableNames;
}
/**
* 根据orderId取模 获取对应的数据表名与对应的orderId集合
*
* @param orderIds 集合
* @return 对应的数据表名与对应的orderId集合
*/
public Map<String, List<String>> getTableNameAndOrderIdsMap(List<String> orderIds) {
Map<String, List<String>> map = new HashMap<>();
for (String orderId : orderIds) {
String tableName = getTableNameByOrderId(orderId);
List<String> list = map.getOrDefault(tableName, new ArrayList<>());
list.add(orderId);
map.put(tableName, list);
}
return map;
}
}
同事说根据,hashCode以后根据orderId用SQL查的时候,无法知道路由到哪张表,建议用其最后一个字符作为逻辑路由表,于是就脸上笑嘻嘻,心里MMP建议改成其,如下:
public String getTableNameByOrderId(String orderId) {
String tableName = "表_" + orderId.substring(orderId.length() - 1);
return tableName;
}
-
观察后续情况
先处理数据,后上分表逻辑代码,处理数据时,DBA反映,线上数据表不要占用那么大的存储空间,于是我们只能先读取原表,然后分到不同的逻辑表后,验证数据的一致性,没问题后再将原表直接改为history备份表,然后新建原表,将备份表昨天今天明天的数据同步过去,此刻完成。
完成后
总结
- 应该更早之前去解决这个问题,但是由于忙于业务迭代开发就拖延了,到了最后不得不解决,导致数据无法备份,其实风险很大的;
- 此种方法不具备扩展性,只能作为临时解决方案,对之前的业务分组逻辑处理的SQL没有考虑到位,导致出现了bug,这个锅我背;
- 多与同事交流,以避免返工,比如orderId HashCode问题。