常用分表方法 根据时间分表 根据数据总量分表

  日志通常来说记录只是为了查询问题时可以查到该条记录。所以一般查询该日志表,只会查询最近一段时间的。因此,日志分表不需要通过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:临时想出来,然后就写了,所以没有代码实现,没有文本排版。)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值