场景:
公司一张表在前期设计时没有考虑数据量以及代码设计上的不足,导致数据库存量达到三千万。导致后台系统无法操作,只能走脚本来操作,在修改代码不足,后考虑对数据库进行数据删除及备份,保存14天数据,备份30天(累计存储44天)
前期工作:
原表:simple_ac_mq_message
备份表:simple_ac_mq_message_back
配置文件
#定时归档任务规则
BACKUP_NUMBER=20|1000
定时任务说明
利用Rocket延迟队列设置定时任务,没30分钟执行一次方法
数据操作说明
原表数据量3千万+,从原表中抽出30天数据进行备份,原表存14天数数据
具体操作分为三步:
1、删除原表44天前的数据,
2、将原表14天到44天的数据进行备份
3、删除备份表中44天后的数据
为了提高效率,在备份数据时java中将事务提交改为自动提交,每操作1000条数据提交一次事务,每个定时任务循环20遍
代码实现
时间工具类
用户调用方法传入指定天数,即可获得多少天前对应日期"yyyy-MM-dd HH:mm:ss"格式的时间戳
public class TimeUtil {
/**
*
* @Description: 根据传入的参数获取指定时间
* @author xxs
* @date 2019年12月19日 下午2:15:20
* @param timeType
* 时间类型 1:分钟 2:天 3:月4:年
* @param time
* 需要获取到多长时间
* @return
*/
public static String getTime(String timeRang) {
Calendar instance = Calendar.getInstance();
String time = BackUpTimeEnum.getTimeRangeEnum(timeRang).getValue();
// 获取指定的分钟
instance.add(Calendar.DATE, Integer.valueOf("-" + time));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = simpleDateFormat.format(instance.getTime());
return format;
}
}
数据备份java代码实现
数据备份时,因为事务的调用属于内部调用,在spring boot 中事务回滚是通过代理模式实现的,只有调用代理对象才会触发事务,一个没有事务的方法调用该类有事务的方法,直接this.method(),是不产生代理事务的,所以事务不起作用。而解决的方法就是创建本类的代理对象,再调用代理对象,使事务生效。
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import com.sgcc.controller.AcOperatorController;
import com.sgcc.dao.AcMqJsonMessageBackDao;
import com.sgcc.dao.AcMqJsonMessagePersistenceService;
import com.sgcc.model.activity.SimpleAcMqMessage;
import com.sgcc.utils.LogUtil;
import com.sgcc.utils.TimeUtil;
/**
*
* @author 作者
*
* @version 创建时间:2019年
*
* 类说明 : 数据备份
*
*/
@Component
@RefreshScope
public class BackUpDataServiceImpl implements BackUpDataService {
private final static LogUtil logger = new LogUtil(AcOperatorController.class);
@Autowired
private AcMqJsonMessagePersistenceService acMqJsonMessagePersistenceServiceimpl;
@Autowired
private AcMqJsonMessageBackDao acMqJsonMessageBackDao;
@Autowired
private BackUpDataServiceImpl backUpDataServiceImpl;
@Value("${BACKUP_NUMBER:20|1000}")
private String backUpNumber;
@Override
public void backUpData() {
try {
logger.info("【备份请求信息】任务开始");
String timeRang1 = TimeUtil.getTime("14"); // 备份
String timeRang2 = TimeUtil.getTime("44");// 删除
String[] split = backUpNumber.split("\\|");
Integer number = Integer.valueOf(split[0]);
String backUpNumber = split[1];
// 第一大步删除44天前的数据
logger.info("【备份请求数据】时间节点是:"+timeRang1+"【删除请求数据】时间节点是:" + timeRang2 + "遍历次数:"+number+"每次删除条数:" + backUpNumber);
long currentTimeMillis1S = System.currentTimeMillis();
for (int i = 0; i < number; i++) {
Map<String, Object> map = new HashMap<>();
map.put("timeRang2", timeRang2);
map.put("backUpNumber", backUpNumber);
try {
long singleStart = System.currentTimeMillis();
Integer deleteNumbers = acMqJsonMessagePersistenceServiceimpl.deleteOldData(map);
logger.info("单次删除simple_ac_mq_message数据--耗时:"+(System.currentTimeMillis() - singleStart)+"删除"+deleteNumbers+"条数据");
if (deleteNumbers == null || deleteNumbers == 0) {
break;
}
} catch (Exception e) {
logger.error("【删除请求表数据】出现异常", e);
}
}
logger.info("删除simple_ac_mq_message--数据--耗时:" + (System.currentTimeMillis() - currentTimeMillis1S));
// 第二大步备份44到14天的数据
long currentTimeMillis2S = System.currentTimeMillis();
for (int i = 0; i < number; i++) {
Map<String, Object> map2 = new HashMap<>();
map2.put("timeRang1", timeRang1);
map2.put("timeRang2", timeRang2);
map2.put("backUpNumber", backUpNumber);
List<SimpleAcMqMessage> selectOldData = acMqJsonMessagePersistenceServiceimpl.selectOldData(map2);
if(CollectionUtils.isEmpty(selectOldData)){
logger.info("【请求表需要备份的数据】查询出的条数是:"+0);
continue;
}
logger.info("【请求表需要备份的数据】查询出的条数是:"+selectOldData.size());
backUpDataServiceImpl.transData(selectOldData);
}
long currentTimeMillis2E = System.currentTimeMillis();
logger.info("迁移老数据用时:" + (currentTimeMillis2E - currentTimeMillis2S) + "ms");
// 第三大步,删除备份表中44天前的数据
long currentTimeMillis3S = System.currentTimeMillis();
for (int i = 0; i < number; i++) {
Map<String, Object> map3 = new HashMap<>();
map3.put("timeRang2", timeRang2);
map3.put("backUpNumber", backUpNumber);
try {
long start = System.currentTimeMillis();
Integer deleteBackNumbers = acMqJsonMessageBackDao.deleteBackData(map3);
logger.info("单次删除simple_ac_mq_message_back_up数据--耗时:"+(System.currentTimeMillis() - start)+"删除"+deleteBackNumbers+"条数据");
if (deleteBackNumbers == null || deleteBackNumbers == 0) {
break;
}
} catch (Exception e) {
logger.error("【删除备份表老数据】出现异常", e);
}
}
logger.info("删除simple_ac_mq_message_back_up--数据--耗时:" + (System.currentTimeMillis() - currentTimeMillis3S));
logger.info("数据归档整体--耗时:" + (System.currentTimeMillis() - currentTimeMillis1S));
} catch (Exception e) {
logger.error("【数据归档】--异常:",e);
}
}
@Transactional(readOnly = true, rollbackFor = Exception.class)
public void transData(List<SimpleAcMqMessage> item) {
long start1 = System.currentTimeMillis();
acMqJsonMessageBackDao.insertBackOldData(item);
long start2 = System.currentTimeMillis();
logger.info("插入simple_ac_mq_message_back_up 耗时:"+(start2 - start1));
acMqJsonMessagePersistenceServiceimpl.deleteBackOldData(item);
logger.info("删除simple_ac_mq_message 耗时:"+(System.currentTimeMillis() - start2));
}
}
在备份数据时,先在原表中查询14天到44天的数据,将其插入备份表中,再做删除操作,中间若出现异常,则事务回滚
Mapper实现
删除原表44天前数据(一次删1000条)
<delete id="deleteOldData" parameterType="map">
delete from SIMPLE_AC_MQ_MESSAGE t
where to_char(CREATE_TIME,'yyyy-mm-dd HH24:MI:SS') <
#{timeRang2,jdbcType=VARCHAR} and rownum <#{backUpNumber}
</delete>
分页查询14天到44天之间的数据(查1000条)
<select id="selectOldData" resultMap="BaseResultMap"
parameterType="map">
select * from (select id as "id", user_id as "userId", province_code as
"provinceCode",serial_no
as "serialNo", sub_serial_no as "subSerialNo",
tag as "tag", buss_type
as
"bussType", status as "status", ext1 as
"ext1", ext2 as "ext2",
to_char(create_time,'yyyy-mm-dd hh24:mi:ss') as
"createTime",dbms_lob.substr(JSON_STR) as "jsonStr",
power_user_id as
"powerUserId"
from SIMPLE_AC_MQ_MESSAGE
where 1=1
<if test="timeRang1 != null and timeRang1 != ''">
and to_char(CREATE_TIME,'yyyy-mm-dd HH24:MI:SS') <![CDATA[<]]>
#{timeRang1,jdbcType=VARCHAR}
</if>
<if test="timeRang2 != null and timeRang2 != ''">
and to_char(CREATE_TIME,'yyyy-mm-dd HH24:MI:SS') <![CDATA[>=]]>
#{timeRang2,jdbcType=VARCHAR}
</if>
order by create_time desc) where rownum
<#{backUpNumber,jdbcType=VARCHAR}
</select>
将查到的数据插入到备份表
<insert id="backUpOldData" parameterType="java.util.List">
insert into SIMPLE_AC_MQ_MESSAGE_BACK
(
<include refid="Base_Column_List"></include>
)
<foreach collection="list" item="item" index="index" open="("
close=")" separator="union">
<![CDATA[
select
#{item.id,jdbcType=VARCHAR}, #{item.userId,jdbcType=VARCHAR}, #{item.provinceCode,jdbcType=VARCHAR},
#{item.serialNo,jdbcType=VARCHAR}, #{item.subSerialNo,jdbcType=VARCHAR}, #{item.createTime,jdbcType=VARCHAR},
#{item.jsonStr,jdbcType=CLOB},#{item.tag,jdbcType=VARCHAR},#{item.bussType,jdbcType=VARCHAR},#{item.status,jdbcType=VARCHAR},
#{item.ext1,jdbcType=VARCHAR},#{item.ext2,jdbcType=VARCHAR},#{item.powerUserId,jdbcType=VARCHAR}
from dual
]]>
</foreach>
</insert>
删除已经备份的数据
<delete id="deleteBackOldData" parameterType="java.util.List">
delete from SIMPLE_AC_MQ_MESSAGE t
where 1=1 and t.ID in
<foreach collection="list" index="index" item="item" open="("
separator="," close=")">
#{item.id}
</foreach>
</delete>
删除备份表中40天前的数据(一次删除1000条)
<delete id="deleteBackData" parameterType="map">
delete from SIMPLE_AC_MQ_MESSAGE_BACK t
where to_char(CREATE_TIME,'yyyy-mm-dd HH24:MI:SS') <
#{timeRang2,jdbcType=VARCHAR} and rownum <#{backUpNumber}
</delete>