-
[MySQL] 스토어드 프로시저(Stored Procedure)Database/데이터베이스 2024. 8. 28. 21:43반응형
개요
- 평소 SQL문을 사용할 때 쿼리문을 자주 사용하는데 특정한 경우 프로시저를 사용하면 편리성 면에서 효과가 있음
- MySQL 프로시저(Procedure)는 저장된 프로시저로도 알려져 있으며, 서버에 저장된 하나 이상의 SQL 문장의 집합
- 프로시저는 여러 SQL문을 모아서 재사용할 수 있는 방법을 제공하며 데이터베이스 작업의 자동화, 코드 재사용성 향상, 그리고 복잡한 로직을 캡슐화하는데 유용함
프로시저의 주요 특징
1.입력 및 출력 매개변수 지원
프로시저는 'IN', 'OUT', 'INOUT' 매개변수를 사용할 수 있음
- IN: 호출할 때 전달되는 입력 매개변수
- OUT: 프로시저가 반환하는 출려 ㄱ매개변수
- INOUT: 입력과 출력을 모두 담당하는 매개변수
2. SQL 문장의 집합
- 여러 SQL 문을 포함할 수 있으며, 복잡한 로직을 구현할 수 있음
3. 제어 구조
- IF, CASE, LOOP, WHILE, REPEAT, LEAVE, ITERATE 와 같은 제어 구조를 사용할 수 있음
4. 트랜잭션 제어
- 프로시저 내에서 트랜잭션을 관리할 수 있음 (START TRANSACTION, COMMIT, ROLLBACK 등)
5. 보안
- 프로시저는 데이터베이스에서 일련의 작업을 수행할 수 있는 권한을 캡슐화함. 이를 통해 보안 및 접근 제어를 쉽게 관리할 수 있음
프로시저의 기본 구조
DDELIMETER $$ CREATE PROCEDURE procedure_name (IN parameter1 datatype, OUT parameter2 datatype) BEGIN -- SQL statement go here SELECT colum1 INTO parameter2 FROM table WHERE column2 = paramter1; -- You can add more SQL statements or control structures like IF, WHILE, etc. END $$ DELIMETER ;
프로시저 예시
다음은 두 개의 숫자를 더하는 간단한 프로시저이다.
DELIMETER $$ CREATE PROCEDURE AddNumber(IN num1 INT, IN num2 INT, OUT sum INT) BEGIN SET sum = num1 + num2 END $$ DELIMITER ;
프로시저 호출
CALL AddNumber(5, 10, @result); SELECT @resultl -- 15
프로시저의 활용
- 복잡한 비즈니스 로직 구현: 여러 테이블 간의 복잡한 데이터 처리 작업을 프로시저로 캡슐화하여 관리할 수 있음
- 반복 작업 자동화: 일일 보고서 생성, 데이터 백업, 대량의 데이터 처리 등 반복적인 작업을 자동화할 수 있음
- 보안 관리: 특정 데이터베이스 작업에 대한 접근을 제어하고 관리할 수 있음
활용 예시 1. 오래된 구매 이력을 삭제
create definer = 계정명@`%` procedure delete_old_purchase2(IN start_date varchar(19), IN keep_years int, IN period int) BEGIN /*** :param_start_date: 기준 날짜를 입력 받는다. 프로시저 실행 날짜 :param keep_years: 유지할 데이터의 기간을 입력받는다. (년 단위) :param period: 삭제할 기간을 일 단위로 입력 받는다. */ DECLARE delete_start_time BIGINT; -- 삭제할 데이터의 시작 시간 DECLARE delete_end_time BIGINT; -- 삭제할 데이터의 종료 시간 DECLARE five_years_ago_limit BIGINT; -- 5년 이내 데이터는 삭제하지 않는다. DECLARE cnt_rows BIGINT DEFAULT 0; -- 반복문 내에서 삭제된 행 수를 저장하기 위한 변수 DECLARE min_created_at BIGINT; -- 테이블에서 가장 오래된 데이터의 생성 시간 DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN SELECT 'SQL Error occurred, transaction rollback.'; END; -- start_date를 DATETIME 형식으로 변환 (예: 2024-07-05 00:00:00) SET @start_datetime = STR_TO_DATE(start_date, '%Y-%m-%d 00:00:00'); -- 2024-07-05 00:00:00 -- 기준 날짜에서 keep_years 년 전의 시간을 UNIX 타임스탬프로 변환 (예: 2019-07-05 00:00:00) SET five_years_ago_limit = UNIX_TIMESTAMP(DATE_SUB(@start_datetime, INTERVAL keep_years YEAR)) * 1000; -- 2019-07-05 00:00:00 -- 삭제할 데이터의 초기 시작 시간을 설정 (예: 테이블의 가장 오래된 데이터 시간) SET min_created_at = (SELECT MIN(created_at) FROM lezhincomics.purchase2); -- 예: 2014-01-02 09:00:00 SET delete_start_time = min_created_at; -- period 기간 동안의 종료 시간을 설정 SET delete_end_time = delete_start_time + (period * 86400000); -- 2014-01-02 09:00:00 + period(3일) -- 가장 오래된 데이터가 유지할 기간 내에 있는지 확인 IF min_created_at >= five_years_ago_limit THEN SELECT 'No data to delete, the oldest data is within the retention period.'; ELSE -- 한 번의 트랜잭션으로 지정된 시간 범위 내의 데이터를 삭제 REPEAT DELETE FROM lezhincomics.purchase2 WHERE created_at >= delete_start_time -- 예: 2014-01-02 09:00:00 AND created_at < delete_end_time -- 예: 2014-01-05 09:00:00 ANd created_at < five_years_ago_limit -- 5년 전 데이터까지만 삭제 LIMIT 1000; SET cnt_rows = ROW_COUNT(); -- 0.1초 대기 DO SLEEP(0.1); UNTIL cnt_rows = 0 END REPEAT; END IF; END;
활용 예시 2. 구매 이력 데이터를 구매 이력 보관 테이블로 동기화
## UTC로 start_date 전달 # 2024-07-31 03:00 실행 # start_date: 2024-07-30 18:00:00 UTC create definer = 계정명@`%` procedure recent_one_year_migrate_purchase3(IN start_date varchar(19), IN period int) BEGIN DECLARE insert_start_time BIGINT; DECLARE insert_end_time BIGINT; DECLARE one_year_ago_limit BIGINT; DECLARE cnt_rows BIGINT DEFAULT 0; DECLARE cur_time BIGINT; DECLARE last_insert_time BIGINT DEFAULT 0; DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; SELECT 'Error occurred, transaction rolled back.'; END; -- @start_datetime: 2024-07-30 18:00:00 UTC # SET @start_datetime = STR_TO_DATE(start_date, '%Y-%m-%d %H:%i:%s'); -- 현재 시간을 UNIX 타임스탬프로 변환 (UTC 2024-07-31 03:00:00) SET cur_time = UNIX_TIMESTAMP() * 1000; -- 기준 날짜에서 1년 전의 시간을 UNIX 타임스탬프로 변환 UTC 2023-07-30 18:00:00 # SET one_year_ago_limit = UNIX_TIMESTAMP(DATE_SUB(@start_datetime, INTERVAL 1 YEAR)) * 1000; -- 이전에 저장된 마지막 이관 시간 조회 SELECT MAX(created_at) INTO last_insert_time FROM lezhincomics.purchase3; -- 이전에 저장된 마지막 이관 시간이 없으면 start_date 사용 IF last_insert_time IS NULL THEN # SET insert_start_time = one_year_ago_limit; ELSE SET insert_start_time = last_insert_time + 1; -- 마지막 삽입 시간 다음부터 시작 END IF; -- 기준 날짜를 기준으로 종료 시간을 설정 SET insert_end_time = insert_start_time + (period * 86400000); -- 현재 시간보다 미래의 시간을 참조하지 않도록 조정 IF insert_end_time > cur_time THEN SET insert_end_time = cur_time; END IF; -- 트랜잭션 시작 START TRANSACTION; -- 지정된 시간 범위 내의 데이터를 가져와서 삽입 INSERT IGNORE INTO lezhincomics.purchase3 ( SELECT * FROM lezhincomics.purchase2 WHERE created_at >= insert_start_time AND created_at < insert_end_time ); SET cnt_rows = ROW_COUNT(); -- 트랜잭션 커밋 COMMIT; -- 결과 확인용 메시지 IF cnt_rows > 0 THEN SELECT CONCAT(cnt_rows, ' rows migrated from purchase2 to purchase3.'); ELSE SELECT 'No data migrated, no rows matched the criteria.'; END IF; END; select max(created_at) from lezhincomics.purchase3;
활용 예시 3. 구매 이력 데이터를 구매 이력 보관 테이블로 아카이빙
uspa_archiving_insert_purchsae
create definer = 계정명@`%` procedure uspa_archiving_insert_purchase2(IN archiving_date varchar(10), IN year_ago int, OUT archive_purchase_max_created_at bigint, OUT archive_limit bigint) BEGIN /**************************************************************** DB : lezhincomics SP : uspa_purchase2_archiving 해당 프로시저는 입력받은 날짜를 유닉크타입스탬프*밀리초 로 변환하여 아카이빙 테이블에 데이터를 복사하는 프로시저이다. :param archiving_date: 기준 날짜를 입력 받는다. 보통은 프로시저 실행일 이 될 것임. :param year_ago: 기준 날짜보다 몇 년 전의 날짜를 아카이빙 할 것인지 입력 받는다. 보통은 5년이 될 것임. 호출 예시: call uspa_archiving_purchase2('2024-06-10', 5); ***************************************************************** -- #01 | 2024-06-10 | yyun | 최초 생성 *****************************************************************/ DECLARE archive_limit_utc bigint; DECLARE archive_limit_kst bigint; DECLARE archive_limit_kst_midnight VARCHAR(19); #DECLARE archive_limit bigint; #DECLARE archive_purchase_max_created_at bigint; DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; SELECT 'Error occurred, transaction rolled back.'; END; -- 트랜잭션 시작 START TRANSACTION; SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -- 1. Rundeck에서 매일 새벽 4시에 주기적으로 해당 프로시저를 호출한다. (KST 2024.08.03 04:00:00) -- 2. archiving_date: 2024.08.02 19:00:00 (파라미터는 UTC로 넘어옴) -- 3. UTC로 받은 시간을 1년 전 날짜를 구한다. (2023.08.02 19:00) SET archive_limit_utc = UNIX_TIMESTAMP(DATE_SUB(archiving_date, INTERVAL year_ago YEAR)) * 1000; -- 4. 9시간을 더해 KST로 변환한다. (KST 2023.08.03 04:00:00) SET archive_limit_kst = archive_limit_utc + (60 * 60 * 9 * 1000); -- 5. KST 로 날짜를 맞춘 뒤 00분 00초로 변환 (2023.08.03 00:00:00) set archive_limit_kst_midnight = DATE_FORMAT(FROM_UNIXTIME(archive_limit_kst / 1000), '%Y-%m-%d 00:00:00'); -- 6. 5번을 unix_timestamp로 다시 UTC 변환 (2023.08.02 15:00:00) -- KST 00:00 -> UTC 15:00 set archive_limit = UNIX_TIMESTAMP(archive_limit_kst_midnight) * 1000 - (60 * 60 * 9 * 1000); -- 7. archive_purchase2에 저장된 가장 최근 max값을 조회한다. select max(created_at) into archive_purchase_max_created_at from archive_purchase2; -- 8. archive_purchase2에 저장된 데이터가 없다면 archive_limit에서 하루를 뺸다. if archive_purchase_max_created_at is null then set archive_purchase_max_created_at = archive_limit - (60 * 60 * 24 * 1 * 1000); end if; -- 9. 즉 아카이빙 되는 대상은 max_created < 아카이빙 대상 < archive_limit (2023.08.02 15:00:00 -> KST 2023.08.03 00:00:0 -- 아카이빙 테이블에 데이터가 있다면 삭제 후 데이터 입력을 한다. #IF EXISTS(SELECT 1 # FROM archive_purchase2 # WHERE created_at > archive_limit - 86400000 AND created_at <= archive_limit) THEN # CALL uspa_archiving_delete_purchase2(archiving_date, 5); #END IF; INSERT IGNORE INTO archive_purchase2 ( balance_bonus_coin, balance_coin, balance_point, bonus_coin, bulk_purchase_id, canceled, coin, coin_product_id, company_event_id, country, created_at, currency, episode_coin, invitation_id, ip, is_head, issue_id, lezhin_object_id, lezhin_object_type, locale, meta, payment_id, platform, point, price, purchase_group_id, purchase_id, purchase_v1_id, ref_id, revenue_share, reward_type, store, title, type, updated_at, user_id, voucher_id ) SELECT balance_bonus_coin, balance_coin, balance_point, bonus_coin, bulk_purchase_id, canceled, coin, coin_product_id, company_event_id, country, created_at, currency, episode_coin, invitation_id, ip, is_head, issue_id, lezhin_object_id, lezhin_object_type, locale, meta, payment_id, platform, point, price, purchase_group_id, purchase_id, purchase_v1_id, ref_id, revenue_share, reward_type, store, title, type, updated_at, user_id, voucher_id FROM purchase2 WHERE created_at > archive_purchase_max_created_at AND created_at < archive_limit; -- 트랜잭션 커밋 COMMIT; END;
uspa_archiving_delete_purchase
create definer = 계정명@`%` procedure uspa_archiving_delete_purchase2(IN archiving_date varchar(10), IN year_ago int, IN archive_purchase_max_created_at bigint, IN archive_limit bigint) BEGIN # DECLARE archive_limit_utc bigint; # DECLARE archive_limit_kst bigint; # DECLARE archive_limit_kst_midnight VARCHAR(19); # DECLARE archive_limit bigint; # DECLARE archive_purchase_max_created_at bigint; DECLARE cnt_rows bigint DEFAULT 0; DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; SELECT 'Error occurred, transaction rolled back.'; END; -- 1. Rundeck에서 매일 새벽 4시에 주기적으로 해당 프로시저를 호출한다. (KST 2024.08.01 04:00:00) -- 2. archiving_date: 2024.07.31 19:00:00 (파라미터는 UTC로 넘어옴) -- 3. UTC로 받은 시간을 1년전 날짜를 구한다. (2023.07.31 19:00) -- SET archive_limit_utc = UNIX_TIMESTAMP(DATE_SUB(archiving_date, INTERVAL year_ago YEAR)) * 1000; -- 4. 9시간을 더해 KST로 변환한다. (KST 2023.08.01 04:00:00) -- SET archive_limit_kst = archive_limit_utc + (60 * 60 * 9 * 1000); -- 5. KST 로 날짜를 맞춘 뒤 00분 00초로 변환 (2023.08.01 00:00:00) -- set archive_limit_kst_midnight = DATE_FORMAT(FROM_UNIXTIME(archive_limit_kst / 1000), '%Y-%m-%d 00:00:00'); -- 6. 5번을 unix_timestamp로 다시 UTC 변환 (2023.07.31 15:00:00) -- KST 00:00 -> UTC 15:00 -- set archive_limit = UNIX_TIMESTAMP(archive_limit_kst_midnight) * 1000 - (60 * 60 * 9 * 1000); -- 7. purchase2에 저장된 가장 최근 max값을 조회한다. -- select max(created_at) into archive_purchase_max_created_at from archive_purchase2; # if archive_purchase_max_created_at is null then # set archive_purchase_max_created_at = archive_limit - (60 * 60 * 24 * 2 * 1000); # end if; REPEAT -- 트랜잭션 시작 START TRANSACTION; DELETE FROM purchase2 WHERE created_at > archive_purchase_max_created_at AND created_at < archive_limit LIMIT 1000; SET cnt_rows = ROW_COUNT(); -- 트랜잭션 커밋 COMMIT; -- 지연 시간 추가로 잠금 최소화 DO SLEEP(0.1); UNTIL cnt_rows = 0 END REPEAT; END;
RUNDECK을 이용한 배치 실행
반응형'Database > 데이터베이스' 카테고리의 다른 글
[MySQL] 옵티마이저 (0) 2024.12.16 [MySQL] ST_Distance_Sphere 함수 (0) 2024.11.11 [데이터베이스] RDBMS vs NoSQL (0) 2022.10.09 자주 사용하는 SQL Query 2 (0) 2018.12.01 자주 사용하는 SQL Query 1 (0) 2018.11.09