[Mysql] X-DOC: Mysql 데이터베이스 대용량 데이터 질의 가속화 (Timed JOB 및 저장 프로시저 적용 사례)

1. 사건 배경

특정 미들엔드 시스템에서는 비즈니스 기능을 지원하기 위해 대량의 기본 데이터(차원 데이터, 차원 매핑 관계 등)를 설계하고, 비즈니스 테이블에는 차원 외래키 관련 필드가 많다는 장점이 있다. 데이터의 프런트엔드 선택 및 입력을 실현할 수 있다는 점, 검증을 통해 입력된 데이터의 정확성이 보장된다는 점, 업무 보고서 작성 시 차원 연관(조인) 작업이 많이 필요하다는 단점이 있다.
플랫폼이 보고서를 출력하기 위해 뷰를 작성해야 하는 딜레마로 인해 일부 보다 복잡한 보고서 요구 사항에는 많은 테이블 연결이 포함될 수 있으며 이는 보고서 성능에 큰 영향을 미칩니다. 외부 필터링 조건에 따라 우선적으로 필터링되지 않으므로 실행 효율성에 더욱 영향을 미칠 수 있으며, 데이터 양이 많은 비즈니스 데이터의 경우 데이터를 사용하지 못할 수도 있습니다.

2. 솔루션 아이디어

데이터 웨어하우스의 솔루션 아이디어를 소개하는 것은 예정된 스케줄링, 사전에 데이터 정리 및 구체화를 통해, 즉 기본 데이터, 과거 비즈니스 데이터 등과 같이 상대적으로 안정적이고 자주 변경되지 않는 일부 데이터를 예정된 작업을 통해 처리하고 중간체를 구성하기 위해 필요한 로직 최종 결과 데이터까지도 자체 구축된 물리적 테이블에 저장한 후, 테이블에 합리적인 인덱스를 생성하고, 최종적으로 이러한 물리적 테이블을 기반으로 보고서를 생성하여 프런트엔드에 제공합니다. 궁극적으로 더 나은 사용자 경험을 달성합니다.

3. 시행방법

시스템은 Mysql 데이터를 기반으로 하기 때문에 다음에서는 Mysql을 기반으로 구현 과정을 보여줍니다.

3.1 예약 예약 기능 켜기

일반적으로 관계형 데이터베이스에는 일정에 따라 스크립트를 실행하는 기능을 구현할 수 있는 자체 예약 예약 기능이 있습니다. SQLServer의 작업 및 Mysql의 이벤트 등.

# 1、开启MYSQL定时设置
-- 1.1、通过show EVENTS显示当前定义的事件
	show EVENTS;

여기에 이미지 설명을 삽입하세요.
(참고: 초기 목록은 비어 있으며 다음 단계가 완료된 후 그림의 기록이 생성됩니다)

-- 1.2、检查event_scheduler状态
	SHOW VARIABLES LIKE 'event_scheduler';

여기에 이미지 설명을 삽입하세요.

-- 1.3、设置job自动启动可以执行:
	SET GLOBAL event_scheduler = ON;
-- 或修改my.ini文件,添加:event_scheduler=1

3.2 JOB 로그 테이블 생성

후속 추적을 용이하게 하기 위해 작업 실행 상태를 기록하는 테이블을 사용자 정의합니다.

delimiter #
drop procedure if exists leodb.p_job_log;
create procedure leodb.p_job_log
(
	in p_id int,
	in p_job varchar(50),
	in p_task varchar(50),
	in p_note varchar(255)
)
begin
	CREATE TABLE if not exists leodb.t_job_log (
		id 				int,			-- job id
		job 			varchar(50),	-- job名称
		task 			varchar(50),	-- 任务名称
		starttime 		datetime,		-- 开始时间
		endtime 		datetime,		-- 结束时间
		NOTE			varchar(255),	-- 备注信息
		primary key( id, job, task)
	) ENGINE=InnoDB DEFAULT CHARSET=utf8;
	-- 存在则更新结束时间,并拼接备注信息
	if exists(select 1 from leodb.t_job_log 
						where id = p_id and job = p_job and task = p_task) 
	THEN
	 	update leodb.t_job_log set
	 		endtime = now(),
			note = case when ifnull(note,'')<>'' then note + '/' + p_note else p_note end
	 	where id = p_id and job = p_job and task = p_task;
	-- 不存在则插入日志
	else
		insert into leodb.t_job_log(id, job, task, starttime, note)
		value( p_id, p_job, p_task, now(), p_note ); 
	end if;
end#
delimiter ;
-- 测试日志维护存储过程
-- call leodb.p_job_log( cast(unix_timestamp() as signed) , 'test', 'test', 'test');
-- call leodb.p_job_log( 1688086594,, 'test', 'test', 'test');

3.3 JOB 태스크 생성

# 3、创建具体JOB存储过程
# 3.1、TASK1:维度、维度映射转存物理表
delimiter #
drop procedure if exists leodb.p_job_get_data_4_rp;
create procedure leodb.p_job_get_data_4_rp()
begin
	-- ·、成本中心映射预算部门
	start transaction;
	create table if not exists leodb.t_view_LEOPU_COST_BGDP 
	(
		s_object_code varchar(20),
		t_object_code varchar(20)
	);
	delete from leodb.t_view_LEOPU_COST_BGDP;
	insert into leodb.t_view_LEOPU_COST_BGDP 
	select s_object_code, t_object_code from leodb.view_LEOPU_COST_BGDP;
	commit;
	
	-- 2、消费类型映射预算科目
	start transaction;
	create table if not exists leodb.t_view_LEOPU_EXP_CSM_BGT
	(
		s_object_code varchar(20),
		t_object_code varchar(20)
	);
	delete from leodb.t_view_LEOPU_EXP_CSM_BGT;
	insert into leodb.t_view_LEOPU_EXP_CSM_BGT 
	select s_object_code, t_object_code from leodb.view_LEOPU_EXP_CSM_BGT;
	commit;
	
	-- 3、会计科目映射预算科目
	start transaction;
	create table if not exists leodb.t_view_LEOPU_ACC_BGT
	(
		s_object_code varchar(20),
		t_object_code varchar(20)
	);
	delete from leodb.t_view_LEOPU_ACC_BGT;
	insert into leodb.t_view_LEOPU_ACC_BGT 
	select s_object_code, t_object_code from leodb.view_LEOPU_ACC_BGT;
	commit;
	
	-- 4、LEOUP付款类型+供应商类型映射贷方(应付报账)
	start transaction;
	create table if not exists leodb.t_view_LEOPU_PUR_BILL
	(
		s1_object_code varchar(20),
		s2_object_code varchar(20),
		t_object_code varchar(20)
	);
	delete from leodb.t_view_LEOPU_PUR_BILL;
	insert into leodb.t_view_LEOPU_PUR_BILL 
	select s1_object_code, s2_object_code, t_object_code from leodb.view_LEOPU_PUR_BILL;
	commit;
end#
delimiter ;

# 3.2、TASK2:提取流程初审人到物理表
-- 基础表:记录流程初审人
select * from leodb.t_mdfp_bpm_audit_first;
drop PROCEDURE if exists leodb.p_job_task_first_audit;
create PROCEDURE leodb.p_job_task_first_audit( in p_minute int )
begin
	start transaction;
	-- 创建初审人员表
	create table if not exists leodb.t_mdfp_bpm_audit_first
	(	BUSINESS_ID varchar(32), OPERATE_TIME bigint, USER_NAME varchar(255));
	-- 创建临时表,减少对审批步骤表的访问,提升查询效率
	CREATE TEMPORARY TABLE if not exists leodb.tmp_mdfp_bpm_audit_history(
		BUSINESS_ID varchar(32), OPERATE_TIME bigint, USER_NAME varchar(255));   
	truncate table tmp_mdfp_bpm_audit_history;  
	-- 拉取最近的初审记录(增量不同:前p_minute分钟内产生的记录)
	insert into leodb.tmp_mdfp_bpm_audit_history( BUSINESS_ID, OPERATE_TIME, USER_NAME )	
	select BUSINESS_ID, OPERATE_TIME, USER_NAME
	from mdfp.mdfp_bpm_audit_history as t
	where ACT_NAME LIKE '%初审%' 
		and OPERATE_TYPE = 'approve'
		and OPERATE_TIME > UNIX_TIMESTAMP(DATE_ADD(now(),INTERVAL -p_minute MINUTE))*1000
		and not exists(select 1 from mdfp.mdfp_bpm_audit_history
										where BUSINESS_ID = t.BUSINESS_ID
											and ACT_NAME = t.ACT_NAME
											and OPERATE_TYPE = t.OPERATE_TYPE
											and OPERATE_TIME > t.OPERATE_TIME );

	-- 处理数据1:存在且时戳较大的,需要更新回去
	update leodb.t_mdfp_bpm_audit_first as d
	join leodb.tmp_mdfp_bpm_audit_history as t on d.BUSINESS_ID = t.BUSINESS_ID and t.OPERATE_TIME > d.OPERATE_TIME
	set d.OPERATE_TIME = t.OPERATE_TIME, d.USER_NAME = t.USER_NAME;					 
	-- 处理数据2:不存在的,直接插入
	insert into leodb.t_mdfp_bpm_audit_first( BUSINESS_ID, OPERATE_TIME, USER_NAME)
	select BUSINESS_ID, OPERATE_TIME, USER_NAME 
	from leodb.tmp_mdfp_bpm_audit_history as t
	where not exists(select 1 from leodb.t_mdfp_bpm_audit_first 
									 where BUSINESS_ID = t.BUSINESS_ID);

	-- 释放临时表
	drop table leodb.tmp_mdfp_bpm_audit_history;
	commit;		-- 提交事务
end;

-- 测试,由于以上存储过程,结合了同步周期考虑增量查询,此处入参写大一点,实现全量数据初始化
call leodb.p_job_task_first_audit(10000);

# 3.3、TASK3:预算存物理表
drop PROCEDURE if exists leodb.p_job_task_budget;
create PROCEDURE leodb.p_job_task_budget()
begin
	start transaction;	-- 开始事务
	-- 建表
	create table if not exists leodb.t_budget_data
	(
			FYEAR 						int,
			FMONTH 						int,
			FDATE 						date,	
			FYEARMONTH 				varchar(10),
			FBG_DEPT_CODE 		varchar(50),
			FBG_ACCOUNT_CODE 	varchar(50),
			FPROJECT_CODE 		varchar(50),
			FINDUSTRY_CODE 		varchar(50),
			BUDGET_AMOUNT 		decimal(16,6),					-- 预算金额
			OCCUPIED_AMOUNT 	decimal(16,6),				-- 占用金额
			ACTUAL_AMOUNT 		decimal(16,6),					-- 发生金额
			AVAILABLE_AMOUNT 	decimal(16,6),				-- 可用金额
			primary key(FYEAR, FMONTH, FBG_DEPT_CODE, FBG_ACCOUNT_CODE, FPROJECT_CODE, FINDUSTRY_CODE)
	)
	ENGINE=InnoDB DEFAULT CHARSET=utf8;
	-- 清除数据
	delete from leodb.t_budget_data where fyear = 2023;  
	-- 插入数据
	insert into leodb.t_budget_data( FYEAR, FMONTH, FDATE, FYEARMONTH, FBG_DEPT_CODE, FBG_ACCOUNT_CODE, 
		FPROJECT_CODE, FINDUSTRY_CODE, BUDGET_AMOUNT, OCCUPIED_AMOUNT, ACTUAL_AMOUNT, AVAILABLE_AMOUNT )
	select h.FYEAR, h.FMONTH, h.FDATE, FYEARMONTH, h.FBG_DEPT_CODE, h.FBG_ACCOUNT_CODE, 
		h.FPROJECT_CODE, h.FINDUSTRY_CODE,
		h.BUDGET_AMOUNT, h.OCCUPIED_AMOUNT, h.ACTUAL_AMOUNT, h.AVAILABLE_AMOUNT
	from leodb.view_mdfp_bm_format as h
	where fyear = 2003;
	
	COMMIT;		-- 提交事务
end;

3.4 작업 생성

# 4、创建具体JOB
delimiter #
drop event if exists leodb.JOB_RUN_EVERY1HOUR;
create event leodb.JOB_RUN_EVERY1HOUR  
on schedule every 1 hour starts timestamp '2023-06-29 00:00:01'
do
begin
	-- 1、维度数据、维度映射转存物理表
	set @v_id=cast(unix_timestamp() as signed);
	call leodb.p_job_log( @v_id , 'JOB_RUN_EVERY1HOUR', 'leodb.p_job_get_data_4_rp', '');
	call leodb.p_job_get_data_4_rp();
	call leodb.p_job_log( @v_id , 'JOB_RUN_EVERY1HOUR', 'leodb.p_job_get_data_4_rp', '维度数据转储成功');
	
	-- 2、审批步骤初审人另存物理表
	set @v_id=cast(unix_timestamp() as signed);
	call leodb.p_job_log( @v_id , 'JOB_RUN_EVERY1HOUR', 'leodb.p_job_task_first_audit(100)', '');
	call leodb.p_job_task_first_audit(100);		-- 100分钟
	call leodb.p_job_log( @v_id , 'JOB_RUN_EVERY1HOUR', 'leodb.p_job_task_first_audit(100)', '初审人员转储成功');
	
	-- 3、预算另存物理表
	set @v_id=cast(unix_timestamp() as signed);
	call leodb.p_job_log( @v_id , 'JOB_RUN_EVERY1HOUR','leodb.p_job_task_budget', '');
	call leodb.p_job_task_budget();		-- 100分钟
	call leodb.p_job_log( @v_id , 'JOB_RUN_EVERY1HOUR','leodb.p_job_task_budget', '预算数据转储成功');
end#
delimiter ;

3.5 JOB 유지관리 및 조회

# 5、JOB维护
-- 5.1、停止
ALTER EVENT leodb.JOB_RUN_EVERY1HOUR DISABLE;
-- 5.2、开启
ALTER EVENT leodb.JOB_RUN_EVERY1HOUR enable;
-- 5.3、查看状态
select * from mysql.event;

4. 요약

위에서 언급한 중간 데이터 처리를 통해 보고서 쿼리의 효율성이 크게 향상될 수 있습니다.
실제 프로젝트에서는 데이터 실시간 요구 사항이 높지 않은 시나리오에서 이 솔루션을 사용할 수 있으며, 실시간 요구 사항이 높은 시나리오의 경우 데이터를 세그먼트 단위로 처리할 수도 있습니다. 현재 데이터만 처리가 가능하며, 실시간 쿼리를 수행한 후 두 가지를 결합하면 전체 데이터에 대한 쿼리 속도가 빨라지며, 적절히 적용하면 속도 향상 효과도 얻을 수 있습니다.

원본 기사, 재인쇄 시 출처를 밝혀주세요 - X-Files

추천

출처blog.csdn.net/XLevon/article/details/131487428