更新履历
- [更新日期:20200530] :存储过程是网上找的资料 原始链接 ,从代码上看好像原理没有问题,但一些小地方可能存在异常。我以此修改一下,抛砖引玉吧。
问题描述
客户现场有一套基于Oracle的业务系统。他们的数据库版本是Oracle 10g R2(10.2.0.5) Standard Edition,数据量超过1 TB。
当前存在三个问题:
- 数据量不断的增长,每天增长量达到3GB或以上;
- 业务系统涉及报表与查询的功能异常缓慢,甚至无法查询数据;
- RMAN备份压力非常大,异常恢复无法满足RTO要求;
问题分析
本番数据库数据量庞大,开发人员收到现场反馈性能缓慢就创建索引。不过在此数据规模之下,索引已经没有太大作用反而会影响增删改的效率。在不升级现有数据库为企业版运用分区表功能,那么我们就必须要建立历史库定期卸载本番库的数据到历史库。历史库可以针对数据量大的表实施按年分表、按月分表等操作缩减单表数据量。
如此本番库将会保持在可控范围内,历史库出现异常不会对产线生产造成影响,其修复时间与压力也可以减少。
问题处理
通过PL/SQL存储过程定期任务实现从本番库抽取数据到历史库并删除本番库已卸载的数据,大致流程参考如下:
- 构建测试数据
# 创建测试表
create table operate_log(str01 varchar2(50),cdate varchar2(20));
# 插入测试数据 (将此设置到脚本并设定到计划任务执行,比如30秒执行一次。)
begin
for i in 1..100 loop
INSERT INTO operate_log VALUES(dbms_random.string('x', 20),to_char(SYSDATE,'yyyy-mm-dd hh24:mi:ss'));
end loop;
commit;
end;
/
# 创建测试索引
create index idx_operate_log on operate_log(cdate);
- 存储过程
鉴于客户保密协议,无法提供本番环境脚本。只能用网上的脚本来修改演示。
脚本说明:脚本是按月分表,与我上面的图示有一些差异的。
CREATE OR REPLACE
procedure operate_log_proc(return_code OUT VARCHAR2,return_msg OUT VARCHAR2)
authid current_user
is
err_index NUMBER;-- 错误定位索引
table_name VARCHAR2(20);-- 源分表名称
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
table_name := 'OPERATE_LOG';
return_msg := '执行[OPERATE_LOG_PROC]成功';
return_code := '1';
err_index := 1;
-- 生成分表的表名
SELECT 'OPERATE_LOG_' ||
TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -1), '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 || '" (
"STR01" VARCHAR2(50 BYTE) NOT NULL ,
"CDATE" VARCHAR2(50 BYTE) NOT 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;
-- 当前月起始时间,如2020-05-01 00:00:00
SELECT TRUNC(SYSDATE, 'MM') INTO current_month_start FROM DUAL;
-- 将Operate_Log表中上月记录添加到新创建的表中
insert_data_sql := 'INSERT INTO ' || log_table_name || ' (SELECT * FROM ' || table_name || ' WHERE CDATE < ''' || current_month_start || ''')';
dbms_output.put_line('Insert SQL: '||insert_data_sql);
EXECUTE IMMEDIATE insert_data_sql;
err_index := 3;
-- 从Operate_Log表中删除上月记录
delete_data_sql := 'DELETE FROM ' || table_name || ' WHERE CDATE < ''' || current_month_start || '''';
dbms_output.put_line('Delete SQL: '||delete_data_sql);
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;
/
- 计划任务定时执行
执行时间:每月1日 0:30
网上脚本不足点:
- 没有操作日志记录,无法与其他系统关联输出操作结果。
- 没有分批次实施,闲忙月履历数据量是不一样的,一次性操作10W行记录与一次性操作1000W行记录性能完全不一样。
常见问题
- 历史数据迁移后,应用程序怎么查询?
应用程序需要根据分表情况实施一定的改造。通过界面的查询区间来判断应用程序发出的SQL是否跨越两个数据库,查询区间未超过番库保留周期则执行SQL方案一,查询区间超过番库保留周期则执行SQL方案二,查询区间不包含本番库保留周期则执行SQL方案三。
完整数据 = 生产数据 + 历史数据