日志通常来说记录只是为了查询问题时可以查到该条记录。所以一般查询该日志表,只会查询最近一段时间的。因此,日志分表不需要通过userId或其它字段进行取模分表。
那日志怎么分表呢?这里一共提供两个分表方法。一个是根据时间分表;一个是根据表数据总量分表。
一、根据时间分表
某个表插入时间只能在某个时间范围内,比如说,login_log分成3个表,login_log_1,login_log_2,login_log_3。login_log_1第一次插入时间是2020/07/29,我们设置30天分一次表,那么等到2020/08/29插入时,就要插入到表login_log_2了,等到2020/09/29插入时,就要插入到表login_log_3了。因为我们一共初始化了3张表,所以以后不管什么时候插入数据,都要插入到表login_log_2。
实现方法:初始化创建3个日志表 login_log_1,login_log_2,login_log_3;创建日志插入信息表login_log_insert_info,此表记录当前插入日志表的第一次插入时间(此表只保留一条数据);配置文件配置初始化日志表数 login.log.count=3。
插入日志表时,先查询login_log_insert_info,
1.如果不存在,则插入 login_log_1,同时login_log_insert_info增加log_1的首次插入时间;
2.如果存在,假设查询到的当前插入表号为2,则获取当前插入表首次插入时间距今是否大于30天,
2.1如果小于等于30天,直接插入login_log_2,
2.2如果大于30天,判断查询出来的当前插入表号2是否大于等于配置 login.log.count=3,如果大于等于,继续插入login_log_2;如果小于,则插入login_log_3,并更新login_log_insert_info的表号为3及首次插入时间为当前时间。
二、根据表数据总量分表
逻辑同根据时间分表,不过 日志插入信息表login_log_insert_info 改为 日志插入数据总数统计表 login_log_insert_count,然后判断是否分表时,判断当前表总数是否大于设置的每个表最大插入量。
以下代码为根据时间分表的部分逻辑
/**
* 〈登录日志接口实现类〉
*
* @author jump
* @date 2019/10/12 0012
*/
@Service
public class LoginLogServiceImpl implements LoginLogService {
/**
* 数据库中维护的登录日志表表名号最大值
*/
@Value("${login.log.table.no.max}")
private Integer loginLogTableMax;
/**
* 每多长时间换个表插入登录日志(单位 月)
*/
@Value("${login.log.table.interval}")
private Integer loginLogTableInterval;
private static final String MABP_BASE_LOGIN_LOG_PRE = "mabp_base_login_log_";
@Autowired
LoginLogRepository loginLogRepository;
@Autowired
private SnowflakeGenerator snowflakeGenerator;
@Override
public void insertLogInLog(InsertLogInLogRequestDTO requestDTO) {
String tableNo = "0";
//0新增;1更新;2不变
String updateInsertInfoType = "2";
String typeNew = "0";
String typeUpdate = "1";
//获取登录日志插入信息
LoginLogInsertInfoPO insertInfoPO = loginLogRepository.queryLoginInsertInfo();
//获取登录日志表表名后缀
if (insertInfoPO != null) {
Date firstInsertTime = insertInfoPO.getFirstInsertTime();
Date now = new Date();
int interval = getMonthInterval(firstInsertTime, now);
if (interval > loginLogTableInterval) {
int insertNo = Integer.valueOf(insertInfoPO.getTableNo());
if (insertNo + 1 <= loginLogTableMax){
tableNo = String.valueOf(insertNo + 1);
updateInsertInfoType = typeUpdate;
}
} else {
tableNo = insertInfoPO.getTableNo();
}
} else {
updateInsertInfoType = typeNew;
}
//新增登录日志
LoginLogPO loginLogPO = new LoginLogPO();
BeanUtils.copyProperties(requestDTO, loginLogPO);
loginLogPO.setTableName(MABP_BASE_LOGIN_LOG_PRE + tableNo);
loginLogPO.setId(String.valueOf(snowflakeGenerator.nextId()));
loginLogRepository.insertLoginLog(loginLogPO);
if (typeNew.equals(updateInsertInfoType)) {
//新增登录日志插入信息
LoginLogInsertInfoPO insertInfoParamPo = new LoginLogInsertInfoPO();
insertInfoParamPo.setId(String.valueOf(snowflakeGenerator.nextId()));
insertInfoParamPo.setTableNo(tableNo);
loginLogRepository.insertLoginInsertInfo(insertInfoParamPo);
} else if (typeUpdate.equals(updateInsertInfoType)) {
//更新登录日志插入信息
LoginLogInsertInfoPO insertInfoParamPo = new LoginLogInsertInfoPO();
insertInfoParamPo.setId(insertInfoPO.getId());
insertInfoParamPo.setTableNo(tableNo);
loginLogRepository.updateLoginInsertInfo(insertInfoParamPo);
}
}
private int getMonthInterval(Date fromDate, Date toDate) {
Calendar from = Calendar.getInstance();
from.setTime(fromDate);
Calendar to = Calendar.getInstance();
to.setTime(toDate);
int fromYear = from.get(Calendar.YEAR);
int fromMonth = from.get(Calendar.MONTH);
int toYear = to.get(Calendar.YEAR);
int toMonth = to.get(Calendar.MONTH);
int month = toYear * 12 + toMonth - (fromYear * 12 + fromMonth);
return month;
}
}
题外话:当前插入表号及首次插入时间可缓存到redis,先读redis再读数据库;该表新增/更新时新增/更新redis数据。
通用表根据时间分表
最近又想到一个好的根据时间分表方法,这个方法不只是适用于日志分表,其它经常查询的表也可用该方法。
首先设置一个起始时间202009,分表间隔6个月;然后就可以根据id分表了。id生成规则:年月日时分秒+数字,例如:2021041117462512345。
计算id前6位即202104距离202009是6个月的几倍(此处计算整数倍数),例:202103距离202009正好是6个月的1倍,202104计算的倍数也是1倍。然后判断该倍数年月的表是否已建,如果未建,则新建表并保存数据;如果已经建立,则直接保存到对应表内。表名取table_name_202009整数倍的年月。注:可以将当前已建年月表单独保存到一张表中,以此来判断是否建过该年月表。
该方法缺点:分表间隔无法灵活改动,因为如果改动分表间隔的话,历史数据查询就有可能查不到了。如果要改分表间隔的话,可以通过数据重新初始化的方法(这个方法太费劲,数据量大的话估计谁也不愿意这么干)。还有一个办法,便是每次查询的时候,判断id如果是改动日期前的id则走之前找表逻辑;如果是改动日期后的id则走现在的找表逻辑。当然,如果分表间隔改动的话,起始时间和分表间隔都需要重新配置。
该分表方法写的可能有点乱,不过看懂的话,这个绝对对你有很大帮助的,适用场景还是很多的。(ps:临时想出来,然后就写了,所以没有代码实现,没有文本排版。)