백은빈, 이성욱 님의 "Real MySQL" 책을 정리한 포스팅 입니다.
- 스토어드 프로그램은 스토어드 루틴이라 하며, 절차적인 처리를 위해 제공됨
- 스토어드 프로그램은 스토어드 프로시저, 스토어드 함수, 트리거, 이벤트 등을 모두 아우르는 명칭임
- 모두 똑같은 문법으로 작성할 수 있음
1. 장단점
장점
보안 향상
- 입력값의 유효성을 체크한 후 동적인 SQL을 생성
- 단위별로 실행 권한 부여 가능
- 특정 테이블의 읽기, 쓰기
- 특정 컬럼에 대해 권한 설정
기능의 추상화
- 개발 언어나 도구와 관계없이 생성규칙을 알지 못해도 호출하여 발급받을 수 있음
- 각각 다른 프로그래밍 언어로 생성규칙 적용하여 생성된 값을 각각의 소스코드에서 구현하지 않아도 됨
네트워크 소요 시간 절감
- 애플리케이션에서 데이터베이스에 여러번 질의해야 할 경우, 스토어드 프로그램으로 한번에 요청하여 응답받을 수 있음
절차적 기능 구현
- 스토어드 프로그램은 DBMS 서버어서 절차적인 기능을 실행할 수 있는 제어 기능을 제공
- SQL에는 절차적인 기능을 제공하지 않음
- IF, WHILE 과 같은 제어 문장 사용 불가
개발 업무의 구분
- 애플리케이션 조직과 SQL 조직이 구분되어 작업이 가능함
- 트랜잭션 단위로 데이터베이스 관련 처리를 하도록 스토어드 프로그램을 API 같이 제공하여 역할 구분이 가능함
단점
낮은 처리 성능
- MySQL 서버는 처리 성능이 다른 프로그램 언어보다 떨어짐 (스토어드 프로그램 처리가 주목적이 아님)
- 스토어드 프로그램을 읽기 위한 파싱 작업이 추가로 들어감
애플리케이션 코드의 조각화
- 프로그램 코드와 MySQL 스토어드 프로그램이 서로 분리되면 설치나 배포가 더 복잡해짐
2. 문법
스토어드 프로시저
- 서로 데이터를 주고받아야 하는 여러 쿼리를 하나의 그룹으로 묶어서 독립적으로 실행하기 위해 사용함
생성
CREATE PROCEDURE sp_sum (IN param1 INTEGER, IN param2 INTEGER, OUT param3 INTEGER)
BEGIN
SET param3 = param1 + param2;
END;
- 반환값 없음
- IN: 입력 전용 파라미터
- OUT: 출력 전용 파라미터
수정
alter procedure sp_sum sql security definer;
삭제
drop procedure sp_sum;
실행
CALL sp_sum(1, 2, @result);
SELECT @result;
- CALL 명령어로 스토어드 프로그램을 호출함
- 세션 변수를 사용하여 출력 파라미터를 전달함
커서 반환
CREATE PROCEDURE sp_selectEmployees (IN in_empno INTEGER)
BEGIN
SELECT * FROM employees WHERE emp_no=in_empno;
END //
CALL sp_selectEmployees(10001);;
- OUT 변수가 있지도 않았는데 쿼리의 결과셋이 출력되거나 클라이언트로 전송됨
- 디버깅 용으로 사용됨
- 스토어드 프로시저는 로깅 기능이 없음
딕셔너리
- 스토어드 프로시저는 시스템 테이블로 저장됨 (사용자에게 보이지 않음)
- information_schema.ROUTINES 뷰로 확인 가능
select routine_schema, routine_name, routine_body
from information_schema.ROUTINES
where routine_schema='employees' and routine_type='PROCEDURE';
스토어드 함수
- 하나의 SQL 문장으로 작성이 불가능한 기능을 하나의 SQL 문장으로 구현해야 할 때 사용됨
생성 및 삭제
CREATE FUNCTION sf_sum(param1 INTEGER, param2 INTEGER)
RETURNS INTEGER
READS SQL DATA -- 또는 DETERMINISTIC 추가 가능
BEGIN
DECLARE param3 INTEGER DEFAULT 0;
SET param3 = param1 + param2;
RETURN param3;
END //
- RETURN 부 필수 (타입, 변수)
- PREPARE 스테이먼트 사용 불가 (PREPARE, EXECUTE)
- COMMIT / ROLLBACK 사용 불가
- 재귀 호출 사용 불가
- 프로시저 호출 사용 불가
- 결과 셋을 반환하는 SQL 사용 불가
실행
select sf_sum(1, 2);
트리거
- 테이블의 레코드가 저장되거나 변경될 때, 미리 정의해둔 작업을 자동으로 실행해주는 스토어드 프로그램
- 테이블에 대해서만 생성할 수 있음
예
- 칼럼의 유효성 체크
- 다른 테이블로의 복사나 백업
- 계산된 결과를 다른 테이블에 함께 업데이트
생성
CREATE TRIGGER on_delete BEFORE DELETE ON employees
FOR EACH ROW
BEGIN
DELETE FROM salaries WHERE emp_no=OLD.emp_no;
END;;
- 트리거를 실행할 이벤트
- BEFORE, AFTER
- INSERT, UPDATE, DELETE
- OLD: employees 테이블의 변경되기 전 레코드
- 사용하지 못하는 유형
- 외래키 관계에 의해 자동으로 변경되는 경우 호출되지 않음
- 복제에 의해 레플리카 서버에 업데이트되는 레코드 기반의 복제는 레플리카 서버의 트리거를 기동시키지 않음
트리거 딕셔너리
- information_schema의 TRIGGERS 뷰를 통해 트리거 정보 확인 가능
이벤트
- 특정한 시간에 스토어드 프로그램을 실행할 수 있는 스케줄러 기능
- 스케줄링을 전담하는 스레드가 활성화되는 경우에만 이벤트가 실행됨
- event_scheduler 시스템 변수를 활성화해야 함
확인
SHOW PROCESSLIST;
생성
- DO 절
- 스토어드 프로시저 호출 가능
- 쿼리 호출 가능 (BEGIN ... END)
- ON COMPLETE 절
- 완전히 종료된 이벤트를 삭제할 지 유지할지 결정할 수 있음
- PRESERVE: 이벤트 실행이 완료되도 삭제되지 않음
CREATE EVENT onetime_job
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 HOUR
DO
INSERT INTO daily_rank_log VALUES (NOW(), 'DONE');
- ON SCHEDULE AT: 단 한번 실행되는 일회성
CREATE EVENT daily_ranking
ON SCHEDULE EVERY 1 DAY STARTS '2020-09-07 01:00:00' ENDS '2021-09-07 01:00:00'
DO
INSERT INTO daily_rank_log VALUES (NOW(), 'DONE');
- ON SCHEDULE EVERY 1 DAY STARTS .. ENDS: 반복성 이벤트
상태
- ENABLE
- DISABLE
- DISABLE ON SLAVE (SLAVESIDE_DISABLED)
- 소스 서버에서 실행된 이벤트가 만들어낸 데이터 변경 사항은 자동으로 레플리카 서버로 복제됨
- 레플리카 서버에서는 DISABLE 됨 (중복 방지)
- 단, 레플리카 서버가 소스 서버로 승격되면 수동으로 ENABLE로 변경해야 함
확인
- information_schema의 EVENTS 뷰를 통해 조회할 수 있음
- 항상 마지막 실행 로그만 가짐
딕셔너리
- information_schema의 EVENTS 뷰를 통해 조회할 수 있음
- 이벤트 코드
- 반복인지 일회성인지
- 언제 마지막으로 실행되었는지
- 언제 등록되었는지
스토어드 프로그램 본문 작성
트랜잭션 (BEGIN...END 블록)
- 스토어드 프로시저 or 이벤트에만 사용 가능
- START TRANSACTION: 트랜잭션 시작
- COMMIT: 트랜잭션 종료
변수
- 변수는 BEGIN ... END 구문에서만 사용 가능
DECLARE v_name VARCHAR(50) DEFAULT 'Matt';
SET v_name = 'KIM';
SELECT last_name INTO v_name
FROM employees
WHERE emp_no=10001
LIMIT 1;
제어문
CREATE FUNCTION sf_greatest(p_value1 INT, p_value2 INT)
RETURNS INT
BEGIN
IF p_value1 IS NULL THEN RETURN p_value2;
ELSEIF p_value1 IS NULL THEN RETURN p_value2;
ELSEIF p_value1 >= p_value2 THEN RETURN p_value2;
ELSE RETURN p_value2;
END IF;
END;;
CREATE FUNCTION sf_greatest(p_value1 INT, p_value2 INT)
RETURNS INT
BEGIN
CASE
WHEN p_value1 IS NULL THEN RETURN p_value2;
WHEN p_value1 IS NULL THEN RETURN p_value2;
WHEN p_value1 >= p_value2 THEN RETURN p_value2;
ELSE RETURN p_value2;
END CASE;
END;;
CREATE FUNCTION sf_factorial(p_max INT)
RETURNS INT
BEGIN
DECLARE v_factorial INT DEFAULT 1;
factorial_loop : LOOP
SET v_factorial = v_factorial * p_max;
SET p_max = p_max - 1;
IF p_max <= 1 THEN LEAVE factorial_loop; END IF;
END LOOP;
RETURN v_factorial;
END;;
SQLSTATE와 에러 번호
- ERROR 1446 (42506) : Table 'test.not_found_table' doesn't exist
- ERROR ERROR-NO (SQL-STATE)
- ERROR-NO: MySQL에서만 식별 가능한 에러번호
- SQL-STATE: ANSI SQL 표준 DBMS의 상태 코드
컨디션
DECLARE dup_key CONDITION FOR 1062;
DECLARE dup_key CONDITION FOR SQLSTATE 42000;
- 핸들러가 처리할 상황을 정의하는 객체
- 기본적으로 ERROR-NO를 매칭
- SQLSTATE를 명시하는 경우 SQLSTATE 키워드 붙이고 뒤에 번호 붙이기
핸들러
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET error_flag=1;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT 'Error occured';
END ;;
DECLARE EXIT HANDLER FOR 1022, 1062 SELECT 'Error occured';
- 특정 컨디션 상황에서 어떻게 처리할지 정의하는 기능
- type
- CONTINUE: 에러가 발생하면 처리하고 다시 돌아옴
- EXIT: 에러가 발생하면 처리하고 실행 블록을 벗어남
시그널
- 예외를 직접 발생시키는 키워드
- BEGIN ... END 안에서 SIGNAL 사용
CREATE FUNCTION sf_divide (p_dividend INT, p_divisor INT)
RETURN INT
BEGIN
DECLARE null_divisor CONDITION FOR SQLSTATE '45000';
IF p_divisor IS NULL THEN
SIGNAL null_divisor
SET MESSAGE_TEXT = 'Divisor can not be null', MYSQL_ERRNO=9999;
ELSEIF p_divisor=0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Divisor can not be 0', MYSQL_ERRNO=9998;
ELSEIF p_dividend IS NULL THEN
SIGNAL SQLSTATE '01000'
SET MESSAGE_TEXT = 'Dividend is null', MYSQL_ERRNO=9997;
RETURN 0;
END IF;
RETURN FLOOR(p_dividend / p_divisor);
END;;
- 핸들러 코드에서 SIGNAL 사용
CREATE PROCEDURE sp_remove_user (IN p_userid INT)
BEGIN
DECLARE v_affectedrowcount INT DEFAULT 0;
DECLARE EXIT HANDLER FOR SQLEXCEPTION;
BEGIN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT='Can not remove user information', MYSQL_ERRNO=9999
END;
DELETE FROM tb_user WHERE user_id=p_userid;
SELECT ROW_COUNT() INTO v_affectedrowcount;
IF v_affectedrowcount <> 1 THEN SIGNAL SQLSTATE '45000'; END IF;
END;;
커서
- 데이터 결과 셋
- 전 방향 읽기만 가능
- 커서의 컬럼을 바로 업데이트하는 것이 불가
- 센서티브 커서
- 레코드에 대한 정보를 실제 레코드의 포인터만으로 유지하는 형태
- 별도 임시 테이블로 레코드를 복사하지 않으므로 커서의 오픈이 빠름
- 칼럼의 데이터 변경 및 삭제 가능
- 인센서티브 커서
- 일치하는 레코드를 별도의 임시 테이블로 복사해서 가지고 있는 형태
- 조회에 부합되는 결과를 우선적으로 임시 테이블로 복사해야 되므로 느림
- 컬럼 조회만 가능
CREATE FUNCTION sf_emp_count(p_dept_no VARCHAR(10))
RETURNS BIGINT
BEGIN
DECLARE v_total_count INT DEFAULT 0;
DECLARE v_no_more_data TINYINT DEFAULT 0;
DECLARE v_emp_no INTEGER;
DECLARE v_from_date DATE;
DECLARE v_emp_list CURSOR FOR
SELECT emp_no, from_date FROM dept_emp WHERE dept_no=p_dept_no;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_no_more_date = 1;
OPEN v_emp_list;
REPEAT
FETCH v_emp_list INTO v_emp_no, v_from_date;
IF v_emp_no > 20000 THEN
SET v_total_count = v_total_count + 1;
END IF;
UNTIL v_no_more_data END REPEAT;
CLOSE v_emp_list;
RETURN v_total_count;
END ;;
- 정의된 커서를 오픈하여 실제 쿼리의 결과를 가져옴
- 오픈된 커서는 페치 명령으로 레코드 단위로 읽어서 사용
- 사용이 완료된 후에 CLOSE 명령으로 커서 및 관련 자원을 해제
'Database > Mysql' 카테고리의 다른 글
[Real MySQL] 16-2. 복제: 타입 (0) | 2025.03.14 |
---|---|
[Real MySQL] 14-2. 스토어드 프로그램: 참고사항 (0) | 2025.03.13 |
[Real MySQL] 13-2. 파티션: 종류 (0) | 2025.03.13 |
[Real MySQL] 13-1. 파티션: 개요 (0) | 2025.03.12 |
[Real MySQL] 10-3. 실행 계획: 실행 계획 분석 (0) | 2025.03.12 |