Oracle 历史数据表迁移方案

当业务运行一段时间后,会出现有些表数据量很大,可能对系统性能产生不良的影响,常见的如订单表、登录log表等,这些数据很有时效性,比如我们一般很少去查上个月前的订单,最多也就是报表统计会涉及到。

解决方案:创建一个存储过程,每月定时调用,查出上个月前的数据保存到新建的表中,新表命名以月份为后缀,删除主表中上个月前的数据。

存储过程

CREATE OR REPLACE 
procedure operate_log_proc(return_code OUT VARCHAR2,
                                             return_msg  OUT VARCHAR2) 
authid current_user
is
  err_index           NUMBER;
  log_table_name      VARCHAR2(20);
  current_month_start DATE;
  create_table_cursor NUMBER(10);
  create_table_sql    VARCHAR2(1000);
  insert_data_sql     VARCHAR2(1000);
  delete_data_sql     VARCHAR2(1000);
  v_count 						NUMBER(10);
begin
  return_msg  := '执行[OPERATE_LOG_PROC]成功';
  return_code := '1';

  err_index := 1;

  -- 生成分表的表名
  -- 保存上个月第一天前的记录,表名应该从上两个月开始算的月份
  -- 比如上个月第一天是2024年1月1日,这时候查出的数据还是2023年12月的数据,上两个月2023年12月1日所属的月份刚好对上那个月的数据
  SELECT 'OPERATE_LOG_' ||
         TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -2), 'yyyymm')
    INTO log_table_name
    FROM DUAL;
  
  --表存在不用创建
	select count(1) into v_count from user_tables where table_name = log_table_name;

	if v_count <= 0 THEN
		-- 打开游标
		create_table_cursor := DBMS_SQL.OPEN_CURSOR;
		-- 拼接创建表的SQL语句,并执行
		create_table_sql := 'CREATE TABLE "' || log_table_name || '" (
		"Num_ID" NUMBER(32) NOT NULL ,
		"Reg_ID" CHAR(12 BYTE) NOT NULL ,
		"User_ID" CHAR(18 BYTE) NOT NULL ,
		"Organization" VARCHAR2(100 BYTE) NOT NULL ,
		"Organization_ID" CHAR(12 BYTE) NOT NULL ,
		"User_Name" VARCHAR2(30 BYTE) NOT NULL ,
		"Operate_Time" CHAR(14 BYTE) NOT NULL ,
		"Terminal_ID" VARCHAR2(40 BYTE) NOT NULL ,
		"Operate_Type" NUMBER(1) NOT NULL ,
		"Operate_Result" CHAR(1 BYTE) NOT NULL ,
		"Error_Code" CHAR(4 BYTE) NULL ,
		"Operate_Name" VARCHAR2(30 BYTE) NOT NULL ,
		"Operate_Condition" VARCHAR2(200 BYTE) NOT NULL ,
		"Insert_Time" DATE NOT NULL ,
		"Foreign_Reg_ID" VARCHAR2(36 BYTE) NOT NULL ,
		"Operate_Display" VARCHAR2(2000 BYTE) NULL
		)';
		DBMS_SQL.PARSE(create_table_cursor, create_table_sql, DBMS_SQL.V7);
		DBMS_SQL.CLOSE_CURSOR(create_table_cursor);
	end if; 

  err_index := 2;

	-- 上个月第一天,如2019-09-01
  SELECT TO_CHAR(trunc(ADD_MONTHS(SYSDATE, -1), 'MM'), 'YYYY-MM-DD') INTO current_month_start FROM DUAL;
  -- 将Operate_Log表中上月记录添加到新创建的表中
  insert_data_sql := 'INSERT INTO ' || log_table_name ||
                     ' (SELECT * FROM OPERATE_LOG WHERE to_char(INSERT_TIME, ''YYYY-MM-DD'') < ''' || current_month_start || ''')';
  EXECUTE IMMEDIATE insert_data_sql;

  err_index := 3;

  -- 从Operate_Log表中删除上月记录
  delete_data_sql := 'DELETE FROM OPERATE_LOG WHERE to_char(INSERT_TIME, ''YYYY-MM-DD'') < ''' || current_month_start || '''';
  EXECUTE IMMEDIATE delete_data_sql;

  err_index := 4;

  COMMIT;

EXCEPTION
  WHEN OTHERS THEN
    return_msg  := '过程[OPERATE_LOG_PROC]出错,' || '第[' || err_index ||
                   ']块语句出错';
    return_code := '0';
    ROLLBACK;

end operate_log_proc;

说明:operate_log_proc存储过程名称,OPERATE_LOG表名。authid current_user关键字给存储过程授权,不然报错找不到表。

-- 上个月第一天
SELECT TO_CHAR(trunc(ADD_MONTHS(SYSDATE, -1), 'MM'), 'YYYY-MM-DD') FROM DUAL;

定时任务

BEGIN
 dbms_scheduler.create_job(
	 job_name        => 'my_job_test',	--定时任务名称
	 job_type        => 'STORED_PROCEDURE',	--类型存储过程
	 job_action      => 'declare return_code VARCHAR2(200);return_msg VARCHAR2(200);begin OPERATE_LOG_PROC(return_code,return_msg); end;',	--存储过程名称
	 start_date      => sysdate,	--立刻执行
	 repeat_interval => 'TRUNC(LAST_DAY(SYSDATE))+1+1/24',    -- 每月1号凌晨1点执行一次
	 enabled         => TRUE,	--是否启用
	 comments        => 'test');	--备注
 end;

说明:没有参数的存储过程,可以直接用存储过程名称加上";"分号。需要权限才能创建。

分割线


如果不想用存储过程,也可以用代码的方式实现,使用Spring Boot + Mybatis + Oracle 实现。

思路:使用scheduling定时任务,每天定时触发,动态创建新表,备份历史数据。

关键代码:

SysLogService.java

/**
 * 系统日志接口
 */
public interface SysLogService {

    /**
     * 备份日志
     * @param createTime	时间
     * @param timeSuffix	后缀
     */
    void backupLog(String createTime, String timeSuffix);
    
    /**
     * 表是否存在
     * @param tableName	表名
     * @return	true存在,false不存在
     */
    boolean existTable(String tableName);
    
    /**
     * 创建表
     * @param tableName	表名
     */
    void createTable(String tableName);
}

SysLogServiceImpl.java

/**
 * 系统日志实现类
 */
@Service("sysLogService")
public class SysLogServiceImpl implements SysLogService {

	/** 日志表前缀 */
    private final static String TABLE_PREFIX = "SYS_LOG_";

	/**
     * 备份日志
     * @param createTime	时间
     * @param timeSuffix	后缀
     */
	@Override
	public void backupLog(String createTime, String timeSuffix) {
		String tableName = TABLE_PREFIX + timeSuffix;
		boolean flag = existTable(tableName);	//判断表是否存在
		if(!flag){
			//表不存在,先创建表
			createTable(tableName);
		}
		
		baseMapper.backupLog(createTime, tableName);
	}
	
	/**
     * 表是否存在
     * @param tableName	表名
     * @return	true存在,false不存在
     */
	@Override
	public boolean existTable(String tableName){
		int count = baseMapper.existTable(tableName);
		if(count > 0){
			return true;
		}
		return false;
	}
	
	/**
     * 创建表
     * @param tableName	表名
     */
	@Override
	public void createTable(String tableName){
		baseMapper.createTable(tableName);
	}
}

SysLogDAO.java

/**
 * 系统日志Mapper
 */
public interface SysLogDAO{
	/**
     * 备份日志
     * @param createTime	时间
     * @param tableName	表名
     */
	void backupLog(@Param("createTime")String createTime, @Param("tableName")String tableName);
	
	/**
     * 表是否存在
     * @param tableName	表名
     * @return	记录数
     */
    int existTable(@Param("tableName")String tableName);
    
    /**
     * 创建表
     * @param tableName	表名
     */
    void createTable(@Param("tableName")String tableName);
}

SysLogDAO.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.xinghuo.modules.sys.dao.SysLogDAO">
	<!-- 备份日志 -->
	<insert id="backupLog">
		INSERT INTO ${tableName} 
		SELECT * FROM SYS_LOG 
		WHERE CREATE_TIME <![CDATA[ < ]]> #{createTime} 
	</insert>
	
	<!-- 查看表是否存在 -->
	<select id="existTable" parameterType="String" resultType="Integer">
		select count(1) from user_tables where table_name = #{tableName} 
	</select>
	
	<!-- 创建表 -->
	<update id="createTable" parameterType="String">
		CREATE TABLE ${tableName}(
			ID VARCHAR(36) NOT NULL,
			USER_ID VARCHAR(50),
			OPERATION VARCHAR(50),
			METHOD VARCHAR(200),
			PARAMS TEXT,
			TIME BIGINT NOT NULL,
			IP VARCHAR(64),
			CREATE_TIME TIMESTAMP(0),
			USER_NAME VARCHAR(100),
			TENANT_ID VARCHAR(36),
			RESULTS TEXT,
			PRIMARY KEY(ID)
		)
	</update>
	
</mapper>

QuartzService.java

/**
 * 定时任务
 */
@Component
@Slf4j
public class QuartzService {
	
	@Autowired
	private SysLogService sysLogService;
	
	// 每月1号凌晨1点执行一次
	@Scheduled(cron = "0 0 1 1 * ?")
	public void deleteScheduled() {
		log.info("==================== 删除三个月前的日志数据开始 ====================");
		this.delMailLog();
		log.info("==================== 删除三个月前的日志数据结束 ====================");
	}
	
	/**
	 * 删除三个月前的日志数据
	 */
	private void delMailLog() {
		try {
			Calendar c = Calendar.getInstance();
	        c.add(Calendar.MONTH, - 3);  //设置为三个月前
            c.set(Calendar.DAY_OF_MONTH, 1);  //设置为该月的第一天
	        Date dateBefore = c.getTime();   //得到三个月前的时间
	        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
	        String time = simpleDateFormat.format(dateBefore) + " 00:00:00";
			
            /*
			 * 保存三个月前的第一天前的记录,表名应该从那个月前一天开始算的年份
			 * 比如三个月前的第一天是2024年1月1日,这时候查出的数据还是2023年的数据,三个月前第一天的前一天是2023年12月31日所属的年份刚好对上那个月的数据
			 */
			Calendar c1 = Calendar.getInstance();
	        c1.setTime(dateBefore);
	        c1.add(Calendar.DAY_OF_MONTH, - 1);  //设置为三个月前第一天的前一天
			SimpleDateFormat sf = new SimpleDateFormat("yyyy");
	        String timeSuffix = sf.format(c1.getTime());
			//操作日志需要备份,因为可能需要统计登录次数
			sysLogService.backupLog(time, timeSuffix);
			
			//删除操作日志
	        QueryWrapper<SysLogEntity> qw = new QueryWrapper<>();
			qw.lt("CREATE_TIME", time);
	        sysLogService.remove(qw);
		} catch (Exception e) {
			log.error("日志删除异常:{}",e);
		}
	}
}

注意:

1.需要在启动类增加注解@EnableScheduling

2.${tableName} 动态表名这里是美元符号$

  • 6
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Oracle大表数据迁移是将一个包含大量数据Oracle表从一个地方移动到另一个地方的过程。这个过程可能是在同一个数据库中的不同表空间之间迁移,也可能是从一个Oracle数据迁移到另一个Oracle数据库。 在进行Oracle大表数据迁移之前,我们需要进行一些准备工作。首先,我们需要评估源表的大小和结构,以及目标表的可用空间。如果目标表空间不足,我们需要调整目标表空间的大小。 然后,我们需要使用Oracle提供的数据导出和导入工具(如exp和imp)来导出源表的数据,并将其导入到目标表中。这个过程可能需要一些时间,因为大表包含大量数据。为了加快数据导入的速度,我们可以考虑使用并行处理和压缩等技术。 在进行数据迁移之前,我们还应该备份源表和目标表,以便在出现意外情况时可以还原数据。此外,我们还需要确保目标表的结构与源表一致,包括表的列、约束和索引等。 完成数据导入之后,我们还需要验证数据的完整性。可以通过对源表和目标表进行比较,并检查数据的条数和内容是否一致来完成验证。 在整个迁移过程中,我们还应该关注迁移过程的性能和资源消耗。可以通过合理设置并行度和导入选项来提高性能,并使用数据库监控工具来监控资源的使用情况。 此外,我们还需要注意迁移过程中可能出现的问题,比如数据冲突、导入错误等。在出现问题时,我们需要及时进行故障排除,并采取相应的措施解决问题。 总结起来,Oracle大表数据迁移涉及到评估、准备、导出、导入、验证和故障排除等一系列步骤。正确地执行这些步骤可以确保数据的完整性和迁移过程的高效性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涛哥是个大帅比

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值