스토어드 프로그램은 절차적인 처리를 위해 사용되는 문법입니다.
스토어드 프로그램을 작성하기 위해 먼저 문법을 알아야 하겠지만, 이번 장에서는 문법 보다는 자주 사용되는 제어문과 권한, 보안, 예외 핸들링 등 주의해야 할 사항을 위주로 학습합니다.
thread_stack
설정을 조정한다. (512KB 정도면 충분하다.)ignore_space
설정을 추가할 수도 있다.스토어드 프로시저는 데이터를 주고받아야 하는 여러 쿼리를 하나의 그룹으로 묶어서 독립적으로 실행하기 위해 사용한다.
스토어드 프로시저 생성 : CREATE PROCEDURE
CREATE PROCEDURE sp_sum(IN param1 INTEGER, IN param2 INTEGER, OUT param3 INTEGER)
BEGIN
SET param3 = param1 + param2;
END;;
스토어드 프로시저 생성 시 주의사항
RETURN
명령을 사용할 수 없다.IN
타입: 입력 전용 파라미터OUT
타입: 출력 전용 파라미터IOUT
타입: 입력 및 출력 용도로 모두 사용DELIMITER
: 명령의 끝을 알려주는 종료 문자
스토어드 프로시저 변경 : ALTER PROCEDURE
ALTER PROCEDUER
로 처리할 수 없다. 이때는 프로시저를 삭제하고 다시 생성해야 한다.--// 프로시저의 보안 옵션을 DEFINER로 변경
ALTER PROCEDURE sp_sum SQL SECURITY DEFINER;
스토어드 프로시저 삭제 : DROP PROCEDURE
DROP PROCEDURE sp_sum;;
프로그램을 실행하는 방법은 스토어드 프로시저와 스토어드 함수의 큰 차이점이다.
프로시저는 SELECT
쿼리에 사용될 수 없고, 반드시 CALL
명령어로 실행해야 한다.
프로시저의 IN
타입 파라미터는 리터럴 형태로 전달해도 되지만, OUT
또는 INOUT
타입 파라미터는 세션 변수를 사용해야 한다.
--// 리터럴 변수와 세션 변수를 사용해 프로시저 실행
SET @result:=0;
CALL sp_sum(1, 2, @result);
SELECT @result;
+---------+
| @result |
+---------+
| 3 |
+---------+
--// 세션 변수를 사용해 프로시저 실행
SET @param1:=1;
SET @param2:=2;
CALL sp_sum(@param1, @param2, @result);
스토어드 프로그램은 명시적으로 커서를 파라미터로 전달받거나 반환할 수 없다.
MySQL에서 커서(Cursor)는 데이터베이스 쿼리 결과를 한 번에 하나씩 처리할 수 있도록 하는 데이터베이스 객체이다.
하지만 스토어드 프로시저 내에서 커서를 오픈하지 않거나 SELECT
쿼리의 결과 셋을 페치하지 않으면 해당 쿼리의 결과 셋은 클라이언트로 바로 전송된다.
--// SELECT 쿼리의 결과 셋을 클라이언트로 바로 전송
CREATE PROCEDURE sp_selectEmployees (IN in_empno INTEGER)
BEGIN
SELECT * FROM employees WHERE emp_no = in_empno;
END;;
CALL sp_selectEmployees(10001);;
SELECT
쿼리의 결과 셋을 반환하는 OUT
변수가 없고, 별도로 화면에 출력하는 처리를 하지 않았지만 쿼리의 결과가 클라이언트로 전송되었다.스토어드 프로시저에서 쿼리의 결과 셋을 클라이언트로 전송하는 기능은 스토어드 프로시저의 디버깅 용도로 자주 사용한다. (SIGNAL, RESIGNAL, DIAGNOSTICS 명령을 사용하는 방법도 있다.)
-- // 디버깅 용도의 프로시저
CREATE PROCEDURE sp_sum2 (IN param1 INTEGER, IN param2 INTEGER, OUT param3 INTEGER)
BEGIN
SELECT '> Stored procedure started.' AS debug_message;
SELECT CONCAT('> param1 : ', param1) AS debug_message;
SELECT CONCAT('> param2 : ', param2) AS debug_message;
SET param3 = param1 + param2;
SELECT '> Stored procedure completed.' AS debug_message;
END;;
CALL sp_sum2(1, 2, @result);;
+-----------------------------+
| debug_message |
+-----------------------------+
| > Stored procedure started. |
+-----------------------------+
+---------------+
| debug_message |
+---------------+
| > param1 : 1 |
+---------------+
+---------------+
| debug_message |
+---------------+
| > param2 : 2 |
+---------------+
+-------------------------------+
| debug_message |
+-------------------------------+
| > Stored procedure completed. |
+-------------------------------+
MySQL 8.0 이전 버전까지 스토어드 프로시저는 mysql 데이터베이스의 proc
테이블에 저장된다.
MySQL 8.0 버전부터 스토어드 프로시저는 사용자에게 보이지 않는 시스템의 테이블로 저장된다.
information_schema
데이터베이스의 ROUTINES
뷰를 통해 스토어드 프로시저의 정보를 조회할 수 있다.--// 스토어드 프로시저 정보 조회
SELECT routine_schema, routine_name, routine_type
FROM information_schema.ROUTINES
WHERE routine_schema = 'realmysql';
+----------------+----------------------------+--------------+
| ROUTINE_SCHEMA | ROUTINE_NAME | ROUTINE_TYPE |
+----------------+----------------------------+--------------+
| realmysql | distanceInSphere | FUNCTION |
| realmysql | getDistanceMBR | FUNCTION |
| realmysql | sf_divide | FUNCTION |
| realmysql | sp_selectEmployees | PROCEDURE |
| realmysql | sp_sum | PROCEDURE |
| realmysql | sp_sum2 | PROCEDURE |
+----------------+----------------------------+--------------+
--// 스토어드 프로그램의 상세 정보 확인
SELECT routine_schema, routine_name, routine_definition, routine_body
FROM information_schema.ROUTINES
WHERE routine_name = 'sp_sum' \G
*************************** 1. row ***************************
ROUTINE_SCHEMA: realmysql
ROUTINE_NAME: sp_sum
ROUTINE_DEFINITION: BEGIN
SET param3 = param1 + param2;
END
ROUTINE_BODY: SQL
스토어드 함수는 하나의 SQL 문장으로 작성이 불가능한 기능을 하나의 SQL 문장으로 구현해야 할 때 사용한다.
--// 부서별로 최근 배속된 사원 2명씩 가져오는 기능
-- 에러 발생: [Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and (utf8mb4_unicode_ci,IMPLICIT) for operation '=']
-- collation을 추가하여 해결: mysql은 collation을 명시하지 않으면 default collation으로 설정한다. (시스템 변수의 character_set_database 값 참고)
-- 스토어드 함수에서 collation을 명시적으로 지정하지 않으면, IMPLICIT으로 생성된다.
DELIMITER //
CREATE FUNCTION sf_getRecentEmp(param CHAR(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci)
RETURNS VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci
DETERMINISTIC
BEGIN
DECLARE result VARCHAR(100);
SELECT GROUP_CONCAT(CONCAT_WS(': ', emp_no, CONCAT_WS(' ', last_name, first_name)) SEPARATOR ', ') INTO result
FROM (
SELECT d.dept_name, e.emp_no, en.last_name, en.first_name
FROM departments d
INNER JOIN dept_emp e ON e.dept_no = d.dept_no
INNER JOIN employee_name en on e.emp_no = en.emp_no
WHERE d.dept_no = param
ORDER BY e.from_date DESC
LIMIT 2
) recent_employees;
RETURN result;
END //
DELIMITER ;
--// 결과 조회
SELECT dept_no, dept_name, sf_getRecentEmp(dept_no) as recent_empolyee
FROM departments
GROUP BY dept_no;
+---------+--------------------+---------------------------------------------------+
| dept_no | dept_name | recent_empolyee |
+---------+--------------------+---------------------------------------------------+
| d009 | Customer Service | 459412: Zschoche Insup, 408784: Sidou Constantijn |
| d005 | Development | 270784: Docker Piyush, 83118: Chepyzhov Krisda |
| d002 | Finance | 445457: Matzat Jeong, 238435: Lalonde Alois |
| d003 | Human Resources | 109702: Maraist Jaihie, 71408: Rajala Kristinn |
| d001 | Marketing | 483985: Piazza Aleksandar, 248699: Sidou Shimshon |
| d004 | Production | 413250: Gihr Shigeichiro, 25734: Rosis Kish |
| d006 | Quality Management | 227384: Kilgour Luise, 209485: Janocha Deborah |
| d008 | Research | 282455: Bierbaum Yucai, 499098: Milicic Sachem |
| d007 | Sales | 71987: Zirintsis Shahid, 86425: Morris Shigeu |
+---------+--------------------+---------------------------------------------------+
스토어드 함수 생성 : CREATE FUNCTION
--// 스토어드 함수 생성
-- 에러 발생: [This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)]
-- DETERMINISTIC 으로 명시하여 해결
CREATE FUNCTION sf_sum(param1 INTEGER, param2 INTEGER)
RETURNS INTEGER
DETERMINISTIC
BEGIN
DECLARE param3 INTEGER DEFAULT 0;
SET param3 = param1 + param2;
RETURN param3;
END;;
DETERMINISTIC
키워드는 저장 프로시저 또는 함수가 동일한 입력에 대해 항상 동일한 결과를 반환한다는 것을 나타낸다.스토어드 함수와 프로시저의 차이점
RETURNS
로 반환되는 값의 타입을 명시해야 함RETURN
명령으로 반환해야 함스토어드 함수의 본문에 허용하지 않는 사항
스토어드 함수에서 결과 셋을 반환하려고 하면 에러가 발생한다.
--// 스토어드 함수를 디버깅 용도로 사용할 수 없음
-- [0A000][1415] Not allowed to return a result set from a function
CREATE FUNCTION sf_resultset_test()
RETURNS INTEGER
DETERMINISTIC
BEGIN
DECLARE param INTEGER DEFAULT 0;
SELECT 'Start stored function AS debug_message';
RETURN param;
END;;
스토어드 함수 변경 : ALTER FUNCTION
ALTER FUNCTION
으로 처리할 수 없다. 이때는 프로시저를 삭제하고 다시 생성해야 한다.--// 스토어드 함수의 보안 옵션을 DEFINER로 변경
ALTER FUNCTION sf_sum SQL SECURITY DEFINER;
스토어드 함수 삭제 : DROP FUNCTION
DROP FUNCTION sf_sum;;
스토어드 함수는 프로시저와 달리 CALL
명령으로 실행할 수 없고, SELECT
문장을 이용해 실행한다.
--// call 명령으로 실행시 오류 발생
-- ERROR 1305 (42000): PROCEDURE realmysql.sf_sum does not exist
CALL sf_sum(1,2);
SELECT sf_sum(1,2) AS sum;
+------+
| sum |
+------+
| 3 |
+------+
ROW
/STATEMENT
)에 관계없이 동일한 결과를 만들지만, 트리거의 실행 위치가 다르다는 차이가 있다.스토어드 함수 생성 : CREATE TRIGGER
--// employees 테이블의 데이터 삭제 시 salaries 테이블에서도 삭제하는 트리거
CREATE TRIGGER on_delete BEFORE DELETE ON employees
FOR EACH ROW
BEGIN
DELETE FROM salaries WHERE emp_no = OLD.emp_no;
end;;
OLD
키워드, 변경될 레코드는 NEW
키워드로 지칭한다.트리거가 실행될 이벤트(INSERT
,UPDATE
, DELETE
)와 시점(BEFORE
, AFTER
)을 명시할 수 있다.
모든 트리거는 항상 레코드(Row) 단위로 실행된다. (FOR EACH ROW
)
테이블에 대해 DROP
이나 TRUNCATE
가 실행되는 경우에는 트리거 이벤트가 발생하지 않는다.
BEGIN ... END
코드 블록에서 허용하지 않는 사항
ROW
)복제를 사용하는 경우 레플리카 서버에서 트리거 호출 XRETURN
문장 (트리거를 종료할 때는 LEAVE
명령을 사용)information_schema
, performance_schema
에 존재하는 테이블에 대한 트리거트리거는 MySQL 8.0 이전 버전까지 데이터베이스 디렉터리의 *.TRG
파일로 기록되었다.
MySQL 8.0 버전부터는 시스템 테이블로 저장되고, information_schema
데이터베이스의 TRIGGERS
뷰를 통해 조회만 할 수 있다.
--// 트리거 뷰 조회
SELECT trigger_schema, trigger_name, event_manipulation, action_timing
FROM information_schema.TRIGGERS
WHERE trigger_schema = 'realmysql';
+----------------+--------------+--------------------+---------------+
| TRIGGER_SCHEMA | TRIGGER_NAME | EVENT_MANIPULATION | ACTION_TIMING |
+----------------+--------------+--------------------+---------------+
| realmysql | on_delete | DELETE | BEFORE |
+----------------+--------------+--------------------+---------------+
SELECT trigger_schema, trigger_name, event_manipulation, action_timing, action_statement
FROM information_schema.TRIGGERS
WHERE trigger_schema = 'realmysql' \G
*************************** 1. row ***************************
TRIGGER_SCHEMA: realmysql
TRIGGER_NAME: on_delete
EVENT_MANIPULATION: DELETE
ACTION_TIMING: BEFORE
ACTION_STATEMENT: BEGIN
DELETE FROM salaries WHERE emp_no = OLD.emp_no;
end
주어진 특정한 시간에 스토어드 프로그램을 실행할 수 있는 스케줄러 기능을 이벤트라고 한다.
이벤트는 스케줄링을 전담하는 스레드가 활성화된 경우에만 실행된다.
이벤트 스레드를 활성화하려면 event_sheduler
시스템 변수의 값을 1
또는 ON
으로 설정한다.
MySQL은 전체 이벤트 실행 내역을 보관하지 않으며, 최근에 실행된 정보만 information_schema
데이터베이스의 EVENTS
뷰에서 확인할 수 있다.
--// 이벤트 스레드 설정 확인
SHOW GLOBAL VARIABLES LIKE 'event_scheduler';
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| event_scheduler | ON |
+-----------------+-------+
--// 프로세스 목록 확인
SHOW PROCESSLIST;
+----+-----------------+------------------+-----------+---------+--------+------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-----------------+------------------+-----------+---------+--------+------------------------+------------------+
| 5 | event_scheduler | localhost | NULL | Daemon | 727964 | Waiting on empty queue | NULL |
| 68 | user | localhost | realmysql | Sleep | 430022 | | NULL |
| 69 | user | localhost | realmysql | Query | 0 | starting | SHOW PROCESSLIST |
| 70 | user | 172.26.0.1:58546 | realmysql | Sleep | 406 | | NULL |
| 72 | user | 172.26.0.1:58550 | realmysql | Sleep | 1591 | | NULL |
+----+-----------------+------------------+-----------+---------+--------+------------------------+------------------+
이벤트는 반복 실행 여부에 따라 일회성 이벤트와 반복성 이벤트로 나눌 수 있다.
ON SCHEDULE AT
ON SCHEDULE EVERY n [MINUTE/HOUR/DAY/YEAR/..]
이벤트 처리 내용을 작성하는 DO
절은 하나의 쿼리나 프로시저를 호출하는 명령으로 작성할 수도 있고, BEGIN ... END
로 구성되는 복합 절을 사용할 수도 있다.
--// 예제를 위한 테이블 생성
CREATE TABLE daily_rank_log(
exec_dttm DATETIME,
exec_msg VARCHAR(50)
);
--// 일회성 이벤트 생성
CREATE EVENT onetime_job
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 HOUR
DO
INSERT INTO daily_rank_log VALUES (NOW(), 'Done');
--// 반복성 이벤트 생성
CREATE EVENT daily_ranking
ON SCHEDULE EVERY 1 DAY STARTS ' 2023-03-10 : 01:00:00' ENDS '2023-03-12 02:00:00'
DO
INSERT INTO daily_rank_log VALUES (NOW(), 'Done');
--// DO 절에서 프로시저 호출
CREATE EVENT daily_ranking_p
ON SCHEDULE EVERY 1 DAY STARTS ' 2023-03-10 : 02:00:00' ENDS '2023-03-12 03:00:00'
DO
CALL sp_sum(5, 5)
--// DO 절에서 BEGIN ... END 블록 사용
CREATE EVENT daily_ranking_p
ON SCHEDULE EVERY 1 DAY STARTS ' 2023-03-10 : 03:00:00' ENDS '2023-03-12 04:00:00'
DO BEGIN
INSERT INTO daily_rank_log VALUES (NOW(), 'Start');
-- 랭킹 정보 수집 & 관리
INSERT INTO daily_rank_log VALUES (NOW(), 'Done');
END;;
또한, 완전히 종료된 이벤트를 삭제할지, 그대로 유지할지 선택할 수 있다.
ON COMPLETION
: 완전히 종료된 이벤트 자동으로 삭제함ON COMPLETION PERSERVE
: 이벤트 실행이 완료 후 이벤트를 삭제하지 않음이벤트를 생성할 때는 ENABLE
, DISABLE
, DISABLE ON SLAVE
3가지 상태로 생성할 수 있다.
ENABLE
상태로 생성된다.SLAVESIDE_DISABLED
상태로 생성된다.ENABLE
로 변경해야 한다.--// 이벤트 뷰 확인
SELECT event_schema, event_name, status
FROM information_schema.EVENTS
+--------------+-----------------+---------+
| EVENT_SCHEMA | EVENT_NAME | STATUS |
+--------------+-----------------+---------+
| realmysql | onetime_job | ENABLED |
| realmysql | daily_ranking | ENABLED |
| realmysql | daily_ranking_p | ENABLED |
+--------------+-----------------+---------+
이벤트는 테스트를 위해 강제로 실행시켜 볼 수는 없다.
--// 스케줄링 시점을 임의로 설정하여 이벤트 실행 테스트
CREATE EVENT daily_ranking_1_min
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 MINUTE
ON COMPLETION PRESERVE
DO BEGIN
INSERT INTO daily_rank_log VALUES (NOW(), 'Done');
END;;
MuSQL 8.0 이전 버전까지는 생성된 이벤트
MySQL 8.0 버전부터는 시스템 테이블로 저장되고, information_schema
데이터베이스의 EVENTS
뷰를 통해 조회만 할 수 있다.
information_schema
데이터베이스의 EVENTS
뷰는 최근 실행 로그만 가지고 있기 때문에 전체 실행 로그가 필요한 경우에는 별도의 로그 테이블이 필요하다.
--// 이벤트 뷰 조회
SELECT * FROM information_schema.EVENTS \G
*************************** 1. row ***************************
EVENT_CATALOG: def
EVENT_SCHEMA: realmysql
EVENT_NAME: onetime_job
DEFINER: user@%
TIME_ZONE: SYSTEM
EVENT_BODY: SQL
EVENT_DEFINITION: INSERT INTO daily_rank_log VALUES (NOW(), 'Done')
EVENT_TYPE: ONE TIME
EXECUTE_AT: 2023-03-11 00:07:04
INTERVAL_VALUE: NULL
INTERVAL_FIELD: NULL
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
STARTS: NULL
ENDS: NULL
STATUS: ENABLED
ON_COMPLETION: NOT PRESERVE
CREATED: 2023-03-10 23:07:04
LAST_ALTERED: 2023-03-10 23:07:04
LAST_EXECUTED: NULL
EVENT_COMMENT:
ORIGINATOR: 1