在我们的项目中,有定时任务的扫表功能,定时任务我们用了分片任务,为了让多个机器扫到的数据不重复,所以我们采用了以下方式来分片:
int shardIndex = XxlJobHelper.getShardIndex();
int shardTotal = XxlJobHelper.getShardTotal();
//计算出本机需要扫描的所有的用户的尾号
List<String> buyerIdTailNumberList = new ArrayList<>();
for (int i = 0; i <= MAX_TAIL_NUMBER; i++) {
if (i % shardTotal == shardIndex) {
buyerIdTailNumberList.add(StringUtils.leftPad(String.valueOf(i), 2, "0"));
}
}
//遍历这些需要关心的尾号,然后进行查询。
buyerIdTailNumberList.forEach(buyerIdTailNumber -> {
try {
int currentPage = 1;
Page<TradeOrder> page = orderReadService.pageQueryTimeoutOrders(currentPage, PAGE_SIZE, buyerIdTailNumber);
orderTimeoutBlockingQueue.addAll(page.getRecords());
forkJoinPool.execute(this::executeTimeout);
while (page.hasNext()) {
currentPage++;
page = orderReadService.pageQueryTimeoutOrders(currentPage, PAGE_SIZE, buyerIdTailNumber);
orderTimeoutBlockingQueue.addAll(page.getRecords());
}
} finally {
orderTimeoutBlockingQueue.add(POISON);
LOG.debug("POISON added to blocking queue ,buyerIdTailNumber is {}", buyerIdTailNumber);
}
});
这里查询方式的实现如下:
select * from trade_order where gmt_create < "2024-09-07 00:00:00" and order_state = "CREATE" and buyer_id like "%12";
这里相当于用用户的尾号进行分片了,每台机器只扫描不同尾号的用户,这样就能避免重复。
但是,这个 SQL 存在一个问题,那就是他没办法走到buyer_id的索引(因为不符合最左前缀匹配),最多只能用到gmt_create和order_state的索引,而gmt_create还是个范围查询,效率并不高。
当表中数据量很大的时候,这个SQL就是个慢 SQL, 超过1s 很正常,甚至可能会达到几秒钟甚至10秒钟都有可能。
我们的解决方案是:把用户id 逆序冗余在订单表中,然后按照这个逆序的用户id 查询。
- 增加一个reverse_buyer_id的字段,表示用户 ID 的逆序,如果用户 ID 是1234,那么这个字段就存储4321。
- 同时创建一个联合索引,把(
reverse_buyer_id,order_state,gmt_create) 放在一起作为联合索引。 - 历史数据初始化:update trade_order set
reverse_buyer_id= REVERSE(buyer_id);