안녕하세요. SQLER의 코난 김대우입니다.
이번 강좌에서는, 6-8. 데이터 무결성 - 트리거(TRIGGER)를 진행 하겠습니다.
SQLER에서 진행되는, 챗GPT와 함께 배우는 SQL Server 강좌 목록
이번에 진행할 강좌는 데이터 무결성 - 트리거(TRIGGER)입니다.
TL;DR
트리거는 데이터베이스 테이블 또는 뷰에 대해 INSERT, UPDATE, DELETE 구문 실행 시 자동으로 작동하는 루틴입니다. 트리거는 사용자가 생성하며, 데이터 무결성 작업을 자동화할 수 있습니다.
트리거
트리거는 데이터베이스 테이블이나 뷰에 대해 INSERT, UPDATE 또는 DELETE 구문을 실행할 때 자동으로 실행되도록 정의된 특별한 유형의 루틴(저장 프로시저)입니다. 트리거는 사용자가 임의로 생성할 수 있고, 트리거의 액션으로 데이터 무결성 작업을 사용자가 수행할 수 있습니다. 별로 어렵지 않게 느껴집니다. 그러면, 트리거 생성 구문을 보면서 계속 진행하겠습니다.
트리거 생성구문
CREATE [ OR ALTER ] TRIGGER [ schema_name . ]trigger_name ON { table | view } [ WITH <dml_trigger_option> [ ,...n ] ] { FOR | AFTER | INSTEAD OF } { [ INSERT ] [ , ] [ UPDATE ] [ , ] [ DELETE ] } [ WITH APPEND ] [ NOT FOR REPLICATION ] AS { sql_statement [ ; ] [ ,...n ] | EXTERNAL NAME <method specifier [ ; ] > } <dml_trigger_option> ::= [ ENCRYPTION ] [ EXECUTE AS Clause ] <method_specifier> ::= assembly_name.class_name.method_name |
트리거는 테이블 또는 뷰에 생성할 수 있습니다. 트리거는 한 데이터베이스 내부의 테이블과 같은 데이터 저장소에 걸려서 데이터가 변경될 때 실행되지만, 트리거는 데이터베이스를 넘어서 실행을 제어할 수 있습니다.
데이터베이스 간 트리거 정의 및 사용이 가능하고, 단일 테이블을 넘어 여러 개의 테이블 또는 뷰, 데이터베이스 범위에 걸쳐서 트리거를 실행할 수 있습니다.
그럼, 트리거를 생성하는 SQL 구문을 통해 살펴보도록 하겠습니다.
트리거 SQL 구문
고객 테이블에 새로운 고객이 추가되면, 고객프로모션 테이블에 자동으로 정보를 추가하는 로직을 생성하고 싶습니다. 이럴 경우 트리거를 이용할 수 있습니다.
USE AdventureWorks; GO -- 만약 테이블이 존재하면 삭제 IF OBJECT_ID(N'dbo.new_customers_promotion', N'U') IS NOT NULL DROP TABLE dbo.new_customers_promotion; IF OBJECT_ID(N'dbo.customers', N'U') IS NOT NULL DROP TABLE dbo.customers; GO -- customers 테이블 생성 CREATE TABLE customers ( customer_id INT PRIMARY KEY, customer_name NVARCHAR(10) NOT NULL, hobby NVARCHAR(15) ); GO -- customers 테이블 생성 CREATE TABLE new_customers_promotion ( new_customers_promotion_id INT PRIMARY KEY IDENTITY(1, 1), -- 자동증가 IDENTITY customer_id INT NOT NULL, reg_date datetime NOT NULL DEFAULT(GETDATE()), -- 기본값 FOREIGN KEY (customer_id) REFERENCES customers (customer_id) -- 고객테이블 참조 외래 키 ); GO -- 트리거 생성 - customers 테이블에 사용자가 추가되면 프로모션 테이블에도 추가 CREATE TRIGGER trg_customers_insert ON customers AFTER INSERT AS -- inserted 특수 임시 테이블 사용 INSERT INTO new_customers_promotion(customer_id) SELECT customer_id FROM inserted; GO -- customers 테이블에 데이터 삽입 INSERT INTO customers (customer_id, customer_name, hobby) VALUES (1, N'손석구', N'영화배우'); GO INSERT INTO customers (customer_id, customer_name, hobby) VALUES (2, N'박은빈', N'활쏘기'); GO -- 데이터 조회 SELECT * FROM customers; SELECT * FROM new_customers_promotion; GO
약간 복잡해 보이지만, 중간의 TRIGGER 테이블만 주의해서 보시면 됩니다.
생성한 트리거는 고객 테이블에 새로운 고객이 추가되면, 고객프로모션 테이블에 자동으로 정보를 추가합니다.
-- 트리거 생성 - customers 테이블에 사용자가 추가되면 프로모션 테이블에도 추가 CREATE TRIGGER trg_customers_insert ON customers AFTER INSERT AS -- inserted 특수 임시 테이블 사용 INSERT INTO new_customers_promotion(customer_id) SELECT customer_id FROM inserted; GO
ON 구문 뒤에는 테이블 또는 뷰를 설정합니다.
AFTER 구문은 데이터 INSERT / UPDATE / DELETE 작업 후에 동작하게 설정합니다.
AFTER INSERT는 INSERT 할 때 동작을 트리거합니다.
inserted 특수 임시 테이블을 사용합니다. 트리거는 동작 중에 inserted와 deleted 임시 테이블을 생성합니다. inserted는 추가된 데이터 로우가 포함되고, deleted는 삭제된 데이터 로우가 포함되어 있습니다. 이 임시 테이블에서 원하는 정보를 선택해 트리거 루틴에 추가하면 됩니다.
우리의 목표는 “고객 테이블에 새로운 고객이 추가되면, 고객프로모션 테이블에 자동으로 정보를 추가”입니다. 따라서, INSERT 될 때 생긴 inserted 테이블의 행 정보를 프로모션 테이블에 INSERT 하면 됩니다.
트리거는 데이터 수정(INSERT/UPDATE/DELETE) 작업에만 동작
데이터 조회 작업에서 트리거를 실행할 수 있을까요? 아쉽지만 데이터 수정 작업에서만 동작합니다. SELECT와 같은 조회 작업을 루틴으로 만들고 여러 쿼리를 추가하고 싶다면, 이후에 배우실 저장프로시저(Stored Procedure)를 이용하세요. 몇 번 말씀드린 것처럼, 트리거를 이용하기보다는 저장프로시저로 로직을 생성하실 것을 권장해 드리고, 그 이유는 맨 아래에서 설명드리겠습니다.
트리거의 inserted, deleted 임시 테이블
주의해서 보실 부분은? 트리거 내부 SQL구문입니다.
INSERT INTO new_customers_promotion(customer_id) SELECT customer_id FROM inserted;
트리거에서 INSERT 할 때 inserted 특수 임시 테이블이 생성되고 DELETE 할 때는 deleted 임시 테이블이 생성됩니다. 그렇다면, UPDATE에서는? UPDATE는 삭제-삽입 순서로 진행되어 deleted 테이블과 inserted 테이블 두 개가 트리거 루틴에서 생성됩니다.
inserted와 deleted 테이블 사용 범위는 트리거 구문 내부 입니다. 트리거 구문 밖에서는 이 특수 임시 테이블을 참조할 수 없습니다. 임시 “테이블”이기 때문에, 조회해 출력하거나 JOIN 하는 것도 가능합니다. SQL 쿼리 예제를 수행해 체크해 보겠습니다.
-- 트리거 생성 - customers 테이블이 UPDATE 되면 deleted, inserted 정보 출력 CREATE TRIGGER trg_updated_customer_info ON customers AFTER UPDATE -- UPDATE 시 동작 AS -- 데이터 조회만 수행 SELECT * FROM inserted; SELECT * FROM deleted; GO -- 트리거 테스트 UPDATE customers SET hobby = N'영화감상' WHERE customer_id = 2; GO 결과 customer_id customer_name hobby ----------- ------------- --------------- 2 박은빈 영화감상 customer_id customer_name hobby ----------- ------------- --------------- 2 박은빈 활쏘기
이렇게 내부적으로 inserted, deleted 임시 테이블이 생성되고 사용됩니다.
트리거 생성구문 암호화
간략히 트리거를 SQL 쿼리 예제로 살펴보았습니다. 트리거 생성 구문도 SQL Server에 저장됩니다. 아래 방법으로 트리거 생성 구문 정보를 볼 수 있습니다.
EXEC sp_helptext trg_updated_customer_info
또는, 권장하지 않지만 syscomments 테이블을 조회해 볼 수 있습니다.
트리거는 쿼리문이지만, 중요한 비즈니스 로직이나 정보를 포함할 수 있기 때문에 트리거 구문을 데이터베이스 사용자로부터 보호해야 할 경우가 있습니다. 이럴 경우 어떻게 트리거 생성 구문을 보호할 수 있을까요?
-- 만약 트리거가 존재하면 삭제 IF OBJECT_ID(N'dbo.trg_updated_customer_info', N'TR') IS NOT NULL DROP TRIGGER dbo.trg_updated_customer_info; GO -- 트리거 생성 - 트리거 생성 구문 암호화 CREATE TRIGGER trg_updated_customer_info ON customers WITH ENCRYPTION -- 트리거 생성 구문 암호화 AFTER UPDATE AS SELECT * FROM deleted SELECT * FROM inserted; GO EXEC sp_helptext trg_updated_customer_info; GO 결과 The text for object 'trg_updated_customer_info' is encrypted.
WITH ENCRYPTION 옵션을 사용하면 생성 구문을 암호화해 보호할 수 있습니다.(물론 실행은 잘 됩니다.)
INSTEAD OF 트리거, AFTER 트리거
AFTER 트리거는 간단합니다. 지금까지 살펴본 트리거 구문이 모두 AFTER 트리거입니다. 지금까지의 살펴본 트리거들은 모두 특정 SQL구문(INSERT, DELETE, UPDATE)이 실행될 때 트리거되어 실행됩니다. 바꿔 말하면, SQL 구문이 실행되고, 데이터가 “변경된 후” 트리거의 작업이 실행됩니다. (엄밀하게는 트랜젝션 작업 중에 발생해 “변경 중”입니다.)
그렇다면, INSTEAD OF 트리거는 뭘까요?
트리거를 호출한 SQL 문 “대신(INSTEAD)” 트리거 구문이 실행되어, 트리거를 호출한 SQL구문 대신 실행됩니다. 예를 들어, DELETE 구문을 실행해 INSTEAD OF 트리거가 실행되면 DELETE 구문 대신 다른 작업을 수행할 수 있습니다. INSTEAD OF 트리거는 테이블 또는 뷰에 생성할 수 있고, INSERT, DELETE, UPDATE에 한 개씩만 지정이 가능합니다.
역시 예제를 통해 살펴보겠습니다. 테이블에 있는 is_deleted 플래그 컬럼을 잘 살펴보세요.
-- 만약 테이블이 존재하면 삭제 IF OBJECT_ID(N'dbo.new_customers_promotion', N'U') IS NOT NULL DROP TABLE dbo.new_customers_promotion; IF OBJECT_ID(N'dbo.customers', N'U') IS NOT NULL DROP TABLE dbo.customers; GO -- customers 테이블 생성 CREATE TABLE customers ( customer_id INT PRIMARY KEY, customer_name NVARCHAR(10) NOT NULL, hobby NVARCHAR(15), is_deleted BIT DEFAULT(0) -- 삭제 플래그 컬럼 ); GO -- 트리거 생성 - DELETE시, 실제 DELETE가 아니라 컬럼의 flag를 업데이트 CREATE TRIGGER trg_customers_instead_delete ON customers INSTEAD OF DELETE AS -- deleted 특수 임시 테이블 사용 UPDATE customers SET is_deleted = 1 WHERE customer_id = (SELECT customer_id FROM deleted); GO -- customers 테이블에 데이터 삽입 INSERT INTO customers (customer_id, customer_name, hobby) VALUES (1, N'손석구', N'영화배우'); GO INSERT INTO customers (customer_id, customer_name, hobby) VALUES (2, N'박은빈', N'활쏘기'); GO -- 삭제를 수행하면 삭제 대신 INSTEAD OF 트리거가 수행 DELETE FROM customers WHERE customer_id = 2; GO SELECT * FROM customers; SELECT * FROM customers WHERE is_deleted = 0; GO
위의 구문을 수행하면 DELETE 구문 대신, INSTEAD OF 트리거가 실행되어 is_deleted 플래그 컬럼 값을 1로 UPDATE 합니다. 말 그대로, “대신” 실행하게 되고, 이런 루틴을 가끔 사용할 경우가 있습니다.
참고 - 삭제 대신 삭제 플래그 업데이트
is_deleted와 같은 삭제 플래그는 현업에서 자주 사용되는 루틴입니다. 데이터베이스에 들어온 값은 여러 테이블에서 참조될 수 있고, 기술적인 용도뿐만 아니라 - 비즈니스 용도, 감사(Audit) 등에 사용될 수 있기 때문에 함부로 실제 삭제를 하지 않습니다.
대신 이렇게 is_deleted와 같은 플래그 컬럼을 두고 사용합니다.
예를 들어, 사용자 애플리케이션에서 목록을 리스트 할 때 SQL 쿼리를 SELECT * FROM 상품 WHERE is_deleted = 0 식으로 사용자에게 디스플레이할 경우에는 플래그가 0인 값들만 조회하게 합니다. 삭제 루틴이 돌면 실제 삭제가 아니라, 플래그만 1로 UPDATE 해 사용자 애플리케이션에서 리스트 되지 않게 개발합니다.
트리거에서 사용불가한 T-SQL 구문
트리거는 테이블과 뷰에 사용되는 무결성 보장 기능입니다. 트리거에서 사용할 수 없는 SQL 구문이 있습니다. 아래와 같은 DB를 생성, 백업, 복구하는 명령, 또는 시스템 명령은 트리거에서 수행할 수 없습니다.
|
다중 트리거(Multiple Trigger)
하나의 테이블, 그리고 하나의 INSERT 구문에 여러 개의 트리거를 생성할 수 있고, 이를 다중 트리거라고 부릅니다.
-- 다중 트리거 생성 CREATE TRIGGER trg_customers_insert1 ON customers AFTER INSERT AS -- customers 테이블에 INSERT가 실행될 때 동작하는 트리거 1 SELECT N'트리거 1 실행'; GO -- 다중 트리거 생성 CREATE TRIGGER trg_customers_insert2 ON customers AFTER INSERT AS -- customers 테이블에 INSERT가 실행될 때 동작하는 트리거 2 SELECT N'트리거 2 실행'; GO INSERT INTO customers (customer_id, customer_name, hobby) VALUES (3, N'김대우', N'독서'); GO
이렇게 다중 트리거를 생성합니다.
중첩 트리거(Nested Trigger)
중첩 트리거는 트리거가 다른 트리거를 시작하는 작업을 수행하는 과정입니다. 예를 들어, 트리거 1이 테이블 1에 삽입하여 트리거 2가 발동되고, 트리거 2가 테이블 2를 삽입하는 과정으로 트리거가 트리거를 호출하는 것을 중첩되었다고 말하며, 최대 32 수준(Level)까지 중첩될 수 있습니다.
재귀 트리거(Recursive Trigger)
재퀴 트리거는 설명 이미지를 보면 빠르게 이해할 수 있습니다. 테이블1을 수정하면서 트리거1이 발동되고, 트리거 1로 인해 테이블1이 수정되어 다시 트리거1이 발동되는 구조가 재귀 트리거입니다. 재귀 트리거는 직접 재귀(Direct recursion) 트리거와 간접 재귀(Indirect recursion) 트리거가 있습니다.
직접 재귀
테이블 1을 수정하면서 트리거 1이 발동되고, 트리거 1로 테이블이 수정되어 다시 트리거 1이 발동되는 구조가 직접 재귀 트리거입니다.
이미지 - 직접 재귀 트리거
간접 재귀
직접 재귀와 비슷하지만, 두개 이상의 테이블에 적용됩니다. 테이블 1을 수정하면서 트리거 1이 발동되고, 트리거 1로 테이블 2가 수정되어 트리거 2가 발동되며 테이블 1이 다시 수정되는 구조가 간접 재귀 트리거입니다.
이미지 - 간접 재귀 트리거
재귀 트리거 SQL 쿼리 예제
그렇다면, 간단한 직접 재귀 트리거 예제를 SQL 쿼리로 실행해 보겠습니다.
-- 만약 테이블이 존재하면 삭제 IF OBJECT_ID(N'dbo.customers', N'U') IS NOT NULL DROP TABLE dbo.customers; GO -- customers 테이블 생성 CREATE TABLE customers ( customer_id INT PRIMARY KEY IDENTITY(1, 1), customer_name NVARCHAR(50) NOT NULL, hobby NVARCHAR(15) NOT NULL ); GO -- 재귀 트리거 생성 CREATE TRIGGER trg_recursive ON customers AFTER INSERT AS -- 재귀 트리거 구문 - GUID 값으로 사용자와 취미 추가 INSERT INTO customers (customer_name, hobby) VALUES (NEWID(), N'독서'); GO -- 데이터 삽입 INSERT INTO customers (customer_name, hobby) VALUES (N'김대우', N'독서'); GO -- 왜 1개만 추가되었지? SELECT * FROM customers; GO
재귀적으로 동작하니 많은 값이 추가되거나, 무한루프라 에러가 떨어질 것으로 예상했는데, 데이터를 조회해 보면 1건만 수행되었습니다. 좀 더 정확히, 최대 중첩 레벨이 32이기 때문에 32건이 INSERT 되거나 롤백될 것으로 예상했습니다만, 아니네요.
그 이유는 서버와 데이터베이스에 중첩 트리거 사용과 재귀 트리거 사용 옵션을 설정하지 않았기 때문입니다.
중첩 트리거 설정과 재귀 트리거 설정은 기본적으로 비활성화되어 있습니다. 트랜잭션이 중첩되면 테이블이나 뷰에 잠금(Lock)이 발생해 블로킹(Blocking)이나 데드락(Deadlock)을 유발할 수 있기 때문입니다.(이후 잠금 강좌에서 배우게 됩니다.)
☑️ 챗GPT 활용: 데이터베이스에서 잠금, 블로킹, 데드락에 대해서 알려줘
우리는 스터디 차원에서 재귀 트리거를 실행하니 바로 풀고 진행해 보겠습니다.
-- 서버 설정과 데이터베이스 설정 허용 필요 EXEC sp_configure 'show advanced options', 1; RECONFIGURE; GO -- SQL Server 설정 정보 표시 EXEC sp_configure; GO -- 서버의 중첩 트리거 옵션 활성화 EXEC sp_configure 'nested triggers', 1; RECONFIGURE; GO -- 데이터베이스 설정 조회 - 데이터베이스의 IsRecursiveTriggersEnabled가 1로 되어야 활성화 SELECT DATABASEPROPERTYEX('AdventureWorks', 'IsRecursiveTriggersEnabled'); -- 재귀 트리거 사용 가능하도록 데이터베이스 설정 수정 ALTER DATABASE AdventureWorks SET RECURSIVE_TRIGGERS ON; -- 다시 데이터 삽입 INSERT INTO customers (customer_name, hobby) VALUES (N'박은빈', N'활쏘기'); GO 메시지 217, 수준 16, 상태 1, 프로시저 trg_recursive, 줄 7 [배치 시작 줄 208] Maximum stored procedure, function, trigger, or view nesting level exceeded (limit 32). -- 예상대로 32 레벨 이상 중첩이라 오류가 발생하며 모두 롤백(취소)됨 -- 중첩레벨(NestLevel)을 확인해 32 레벨 전에 끝내는 방법 -- 만약 트리거가 존재하면 삭제 IF OBJECT_ID(N'dbo.trg_recursive', N'TR') IS NOT NULL DROP TRIGGER dbo.trg_recursive; GO CREATE TRIGGER trg_recursive ON customers AFTER INSERT AS --트리거의 NestLevel을 알아야 한다. 최대 32 레벨을 넘을 수 없으므로 --10번만 재귀를 시킨 후 COMMIT 하게 한다. DECLARE @objId int, @nestLevel int SET @objId = (SELECT OBJECT_ID('dbo.trg_recursive')) SET @nestLevel = (SELECT trigger_nestlevel(@objId)) SELECT @nestLevel -- 디버그 출력 --만약 중첩레벨이 10이 넘으면? COMMIT 하게 한다. IF @nestLevel > 10 BEGIN RETURN END --중첩레벨이 10이 넘지 않았으면? 값을 삽입한다. ELSE BEGIN INSERT INTO customers (customer_name, hobby) VALUES (NEWID(), N'독서') END; GO -- 다시 데이터 INSERT 테스트 INSERT INTO customers (customer_name, hobby) VALUES (N'김우빈', N'웨이트운동'); GO -- 조회해 보면 10건의 로우가 재귀 트리거로 INSERT 된 것을 확인 가능 SELECT * FROM customers;
이렇게 쿼리로 trigger_nestlevel 값을 이용하면, 현재 실행 중인 트리거의 중첩 단계를 파악하고, 단계에 맞는 작업을 제어할 수 있습니다. 중간에 IF 구문이나 변수 선언 구문 등은 이어지는 T-SQL 프로그래밍 강좌에서 좀 더 상세하게 소개해 드리니, 지금은 이런 게 있다는 것 정도만 기억해 두세요.
CLR 트리거
.NET Framework CLR(공용 언어 런타임 이하 - CLR)을 이용해 C#과 같은 프로그래밍 언어로 트리거를 생성할 수 있습니다. 하지만, SQL Server 강좌의 범주가 아니라 진행하지 않습니다. CLR 트리거 역시 C# 코딩 자체가 어렵지는 않습니다. 기회가 되면 SQLER의 포스트로 한번 풀어보도록 하겠습니다.
트리거 주의사항
text, image 데이터형은 트리거의 inserted나 deleted 임시 테이블에 기록되지 않음.
우선, text 데이터형과 image 데이터형은 차기 SQL Server에서 지원하지 않으니 사용하지 마세요. 트리거의 inserted 테이블이나 deleted 임시 테이블에서도 text와 image는 (미움받아서) 조회나 재처리가 안됩니다.
메시지 311, 수준 16, 상태 1, 프로시저 trg_text, 줄 8 [배치 시작 줄 272] Cannot use text, ntext, or image columns in the 'inserted' and 'deleted' tables. |
대신, VARCHAR(MAX)나 VARBINARY(MAX)는 AFTER 트리거와 INSTEAD OF 트리거 모두 사용 가능합니다.
트리거를 실행하면 애플리케이션에서 결과셋을 가져오지 못하는 오류
(x rows affected) (x개의 행이 영향받음)이라는 메시지가 SSMS에서 SQL 쿼리를 수행할 때 생기는 것을 보셨을 겁니다. 이렇게 (x rows affected)라는 처리가 1개의 결과셋을 의미하고, 애플리케이션에서는 결과셋을 가져와 데이터를 조회하거나 처리를 확인합니다.
그런데, 트리거에서 다른 테이블 수정 작업이 수행되고 빈 결과셋이 먼저 리턴되며 다음에 이어서 실제 데이터 조회 결과셋이 리턴되면, 애플리케이션은 비어있는 첫 번째 데이터 수정 결과셋을 받아서 처리를 시도합니다. 그래서 결과셋을 가져오지 못하는 것처럼 보이게 됩니다.
이 경우에는 트리거 구문에 SET NOCOUNT ON을 설정하면 불필요한 비어있는 결과셋을 애플리케이션에 반환하지 않아 정상적으로 수행됩니다.
SSMS에서 GUI로 트리거 생성 불가
SSMS에서 GUI로 트리거를 생성/수정하는 방법은 없습니다. 데이터베이스-테이블-트리거 하위에서 “새 트리거”를 수행하면 쿼리창이 열리며 트리거 템플릿이 나오고, 직접 수정하면 됩니다.
결론적으로, 트리거 가능한 쓰지 마세요
헛? 이게 무슨 말이죠? 힘들게 트리거 강좌를 수행했는데 쓰지 말라고요?
트리거는 기본적으로 (땜빵) 보완 작업입니다.
트리거는 테이블이나 뷰의 데이터가 변경될 때 자동 실행되는 훌륭한 데이터베이스 무결성 보완 장치입니다. 하지만, 데이터가 변경될 때 “암시적”으로 실행되기 때문에, 애플리케이션 개발자나 다른 데이터베이스 사용자의 혼란이 가중될 수 있습니다.
A 테이블을 INSERT 할 때 B 테이블에 자동으로 INSERT하는 트리거를 만들기보다는, 개발자와 협의해 명시적으로 A 테이블과 B 테이블에 트랜잭션으로 INSERT하는 쿼리를 생성하세요. 일반적인 SQL 쿼리도 좋고 저장 프로시저면 훨씬 더 좋습니다.
모듈화를 위해 트리거를 쓴다면, 대신 저장 프로시저를 사용하세요.
트리거 역시 개발과 데이터베이스의 모듈화(캡슐화)를 제공하는 훌륭한 장치입니다. 데이터베이스 개발자와 애플리케이션 개발자 간 훌륭한 업무 분장을 제공합니다.
하지만, 트리거는 기본적으로 자동적, 암시적으로 실행됩니다. 만약, 개발자가 데이터베이스 업무에 익숙하지 않아 개발하면서 ORM(Object Relational Mapping)을 이용할 경우, 한번 트리거와 충돌하면, 문제 발생 위치를 찾기도 어려워지는 난감한 상황이 발생합니다. (ORM이 좋다 나쁘다 이야기가 아니라, 트리거 이야기입니다.)
이런 위협을 완화하기 위해 가능한 트리거를 제거하고, 저장 프로시저로 변환해 모듈화 하며, 개발자에게 저장 프로시저 사용을 가이드하는 게 좋습니다.
트리거는 제어하기 어려운 트랜잭션을 사용합니다.
대규모 OLTP 시스템이라 A테이블 - B테이블 - C테이블 순서로 트랜잭션 처리를 하며 잠금(Lock)과 블로킹(Blocking)을 최소화하는 대규모 OLTP 시스템의 경우, 트리거가 이런 과정 중간에 위치하면 데드락(Deadlock) 발생 가능성이 높아집니다.
트리거는 실행되면서 실행 구문과 함께 트랜잭션으로 묶여 처리되기 때문에 트랜잭션 범위와 리소스 순차 접근 제어가 어려워집니다. 트리거로 생성하기보다는 저장 프로시저로 생성해 명시적으로 트랜잭션을 범위에 맞춰 제어하면 이런 위협을 완화할 수 있습니다.
☑️ 챗GPT 활용: 대규모 OLTP 시스템에 대해서 알려줘
그렇다면 트리거는 언제 사용하나요?
트리거는 외주 개발 솔루션이라 애플리케이션 코드에 대한 제어권이 없거나, 레거시 데이터베이스라 스키마나 디자인, 신규 쿼리나 저장 프로시저를 전혀 건드릴 수 없는 특수한 상황에서만 최소한으로 사용하세요.
SQL 강좌 책 구매
강좌가 도움이 되셨다면, 책으로 구매 가능합니다. 책 판매 수익금은 전액 코딩 교육 사회공헌 활동에 기부되며, 아래 링크에서 구매하시면 더 많은 금액이 기부됩니다.