Database/데이터베이스
[MySQL] 스토어드 프로시저(Stored Procedure)
happy coding!
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을 이용한 배치 실행
반응형