数据库迁移策略
为备战双11,需要将数据库中的相关表(历史订单)进行归档,以便腾出更多的空间迎接订单的暴增。作者经过尝试,得出自认为最优的解决方案。下面给出数据库归档策略及示例代码。
现有条件:
1.现有两个数据库:db-A 以及 db-B;
2.两个库中有字段相同的表:tba(表中只有字段订单id–rx_id(long型) 有索引);
3.归档库的tba中还有17年整年的归档数据。
4.由于单量隐藏性,rx_id 并不是按照下单时间依次递增的,但是大致趋势是递增的。即:保证今天的最大单号比昨天的最大单号的值大。
5.需要迁移的单量大约300w条。
我们注意到,
1.数据量庞大,在查询的sql中最好是能够利用索引。
2.数据库可利用的索引只有1个。
解决思路:
1.首先,作者通过拿到当归档库表中最大的订单号:rx_id_start。
2.其次,拿到结束时间当天最大的订单号:rx_id_end。
3.通过两个单号,分页查询两个订单号范围内的订单。
4.按分页的数量分批插入归档表。
5.做一个定时job 定时执行。
好处:
1.最大化并且巧妙的利用了索引,加大了检索速度。
2.可做成定时job每天,执行一次,归档的工作可持续。
完整代码:
private static String archive4RxInfoJob = "archive4RxInfoJob";
/**
* 缓存结束时间的最大rxId
*/
public static Map<String, Long> currentMaxRxIdMap = new ConcurrentHashMap<>();
//封装的线程池
private ThreadPoolService threadPoolService;
private static int pageSize = 100;
//可定时执行job
public JsfResult<Boolean> archive4RxInfoJob() {
final Long start = System.currentTimeMillis();
JsfResult<Boolean> result = new JsfResult<>();
//开始ump监控
CallerInfo umpCall = umpUtil.start("medicine-b2c-man.method.DataArchiveExportServiceImpl.archive4RxInfoOneTime");
try {
final Date endTm = getEndDay();
//异步执行
this.threadPoolService.asynExecuteTask(new Runnable() {
@Override
public void run() {
int i = 0;
while (true) {
if (i == 10) {
break;
}
//依次拿到归档表中最大的rx_id
Long rxId = dataArchiveDao.getRxInfoByRxIdAndEndTmPage();
//拿到生产库表中截止时间的rx_id,本地缓存记录一下
Long maxRxIdEndTm = currentMaxRxIdMap.get(archive4RxInfoJob + endTm.getTime());
if (maxRxIdEndTm == null) {
maxRxIdEndTm = rxReadDao.getMaxRxIdEndDay(endTm);
currentMaxRxIdMap.put(archive4RxInfoJob + endTm.getTime(), maxRxIdEndTm);
}
//查询1000条数据
logger.info("archive4RxInfoJob->start->rxId:{},date:{}", rxId, DateUtil.formatDate(endTm, "yyyy-MM-dd HH:mm:ss"));
List<RxInfo> resList = rxReadDao.getRxInfoByRxIdPage(rxId, pageSize / 10, maxRxIdEndTm);
if (resList == null || resList.size() == 0) {
logger.info("archive4RxInfoJob->resList size is 0");
return;
}
//批量插入
dataArchiveDao.batchAddRxInfoArchive(resList );
logger.info("archive4RxInfoJob->after->rxId:{},date:{},last:{}", rxId, DateUtil.formatDate(endTm, "yyyy-MM-dd HH:mm:ss"), System.currentTimeMillis() - start);
i++;
}
}
});
result.setMsg(DataArchiveErrorCode.SUCCESS.getDescription());
result.setCode(DataArchiveErrorCode.SUCCESS.getCode());
return result;
} catch (Exception e) {
result.setCode(BusinessErrorCode.UNKNOWN_ERROR.getCode());
result.setMsg(BusinessErrorCode.UNKNOWN_ERROR.getDescription());
return result;
}
}
private static Date getEndDay() throws Exception {
//时间范围确定,只导半年之前的数据
long current = System.currentTimeMillis();//当前时间毫秒数
long zero = current / (1000 * 3600 * 24) * (1000 * 3600 * 24) - TimeZone.getDefault().getRawOffset();//今天零点零分零秒的毫秒数
Date endTm = DateUtil.addDays(DateUtil.parseDate(new Timestamp(zero).toString(), "yyyy-MM-dd HH:mm:ss.S"), -180);
return endTm;
}