2010년 6월 4일 금요일

The Trouble with the Triggers-트리거 유지 보수 및 구현에 대한 가이드

The Trouble with the Triggers
트리거 유지 보수 및 구현에 대한 가이드

기술 인력들은 트리거 유지 보수 및 구현과 관련하여 어려운 과제에 직면 하고있다

asktom.oracle.com 웹 사이트를 자주 방문하는 분들은 필자가 트리거를 정말 싫어한다는사실을 잘알고 있을 것이다. 한때는 트리거를 아주훌륭한 기술로 생각해 자주 사용했으며 어쩌면 남용했다고 할 수도 있었다. 그렇지만 현재는 가능하면 트리거를 사용하지 않으려고 한다.
이처럼 트리거를 피하게 된 큰 이유는 두 가지가 있다.

•트리거는 고질적인 유지 보수 문제를 야기한다. 트리거는 직접 실행되지 않는 작은 코드다. 그저 다른 작업의 부산물로“발생”할뿐이다. 작업이 부산물로 이루어지기 때문에 사람들은 트리거가있다는 사실을 종종 잊곤 한다(그리고 모든 부산물에 대한 코드 검토가 불가능한 것은 아니지만 어렵다).

•구현된 트리거를 볼 때마다 거의 대부분 잘못 구현되어 있었다. 트리거에는 개발자가 알거나 예상하지 못한 큰 논리적 오류가 있다. 그 이유는 대부분 발생하는 문제를 예상하지 못했기 때문이다.

유지 보수 문제

트리거를 피하는 첫 번째 이유인 유지 보수 문제는 아마도 쉽게 확인할 수 있을 것이다. 다른 사람의 작업을 인계 받아 다른 사람의 프로젝트를 작업 하고 있다고 가정해 보자. 그리고 트랜잭션을 나타내는 몇 줄의 코드나 그보다 나은 저장된 프로시저가 있으며 이를 읽는다고 생각해 보자. 이런 경우를 상상해 볼수 있다“. 이게 무엇인지 이해가 되는군. 알겠어.”그러나 여기저기에 트리거가 있는시스템에서 작업하고있다면 여러분은 전혀 이해 하지를 못하거나 잘못이해하게 될 것이다.

트리거는 부작용을 야기한다. 트리거는 메인스트림에서 벗어나 있다. 업데이트를 실행하고“행 처리된”것을 보면, 500개의 다른 것도 발생했을 수있다. 필자는 다음과 같은 내용의 비슷한 e-메일을 몇번이나 받았다(내용은 잘라내 붙여넣은 것이며 필자가 만든 것이 아니다).

우리가 열을 업데이트할 때 문제가 발생했다. 해당열을 업데이트 할 때(유 형이 varchar2), 업데이트는 1,972열이 업데이트 된 것을 보여주고 있다 (그리고UPDATE 후 커밋한다). 해당 열을 검색하면 쿼리는 업데이트한 열값을 반환하지 않고 다른열이 업데이트되며 이러한 값을 볼 수 있다. 무엇이 문제일까?

나는 간단히“트리거가 있습니까?”라고 물었다. 내게 돌아온 답변은“오, 이제야 알겠습니다. 문제가 발견되었습니다. 트리거에‘:new.name := :new.fname |‘| ’|| :new.lname;’이있었습니다.”라는것이었다.(나는 이와같은 문제를 매주 듣고 있다. 이런일은 실제로 여러분이 생각하는 것 보다 더 자주 일어난다.)

그래서 트리거를 검토하고 즉시 사용하지 않기로 결정했다. 먼저, NAME 속성은 뷰 내열이었어야한다(또는Oracle Database 11g에서는가상열). 이 이름 정보는 두 가지 다른 열에 있는 값을 기준으로 데이터를 추출한다. NAME 속성은 분명한 함수의 결과이고 이 함수는 연산 집약적이 아니며 필요할경우함수를색인화할수있다. 표에함수결과를저장할이유가없 었다.

둘째, 트리거에 있는NAME 속성을 무조건 덮어쓴다. 따라서 혼동을 일으키고(그렇지 않았다면, 앞의e-메일을 받지 않았을 것이다!) 데이터작업을 어렵게 만든다. 이 시스템에서는 이NAME 속성을 명백하게“fname||’ ‘||lname”으로 예상하지 않았다. 그랬다면 이 업데이트를 시도하지 않았 을것이다(왜냐하면NAME 속성을 해당 함수 결과로 설정하지 않기 때문이다!). 확실히 이들은 다른 어떤 값으로 설정하기 위해 애쓰고 있었다(하지만 트리거를 비활성화 하지 않고는 그렇게할 수 없었을 것이다).

트리거는 기존 시스템을 이해하기 힘들게 만들고, 기존 시스템을 유지 보 수하는 데 어려움을 가중시키며, 혼란을 발생시킬 뿐만 아니라, 데이터 정의언어(DDL: Data Definition Language)와스키마에숨겨져있다. 코드를 검토하는 동안 트리거는 일반적으로 검토 되고 있는코드의 컨텍스트에도 나타나지 않았다. 하지만 나타나야 한다. 트리거는 몇 번이나 반복 해서 호출되는서브루틴과 같다. 트리거는 서브루틴과 흡사 하지만 대부분의 사람들은 트리거를 DDL의 일부로 간주해버린다. 그리고 패키지구현을 검토하는 동안CREATE TABLE 문을 검토하지 않는 것처럼 대부분의 사람들은 트리거에 숨겨진 코드를 검토하지 않는다.

잘못된 구현

트리거를 싫어하는 첫 번째 이유(고질적인 유지 보수 문제)는 단순히 트리 거가“지겨운골칫거리”라는사실을알았기때문이다. 적절한 문서화와 적절한 검토프로세스를 통해 아마도 유지보수 문제를 처리할 수있을 것이다. 그러나 트리거의 잘못된 구현 문제를 해결하는 것은 또 다른 문제다. 종종 사람들은 트리거가 가져다줄 파장을 이해하지 못한상태에서 트리거를 만들것이다. 예를들어, 이트리거에서 심각한 버그를 즉시 알아볼 수 있는가?


SQL> create trigger send_mail 2 after insert on purchase_order 3 for each row 4 begin 5 utl_mail.send 6 (sender=>'database@x.com', 7 recipients=>'orders@x.com', 8 subject=>'New Order ' || :new.po_number, 9 message=> ' ... ' ); 10 end; 11 / Trigger created.


확실히 문법적으로 올바르며 컴파일 된다. 레코드를 입력하면 트리거는 문제없이 실행된다. 그러나 아주 잘 못된 것이며, 구현에 심각한 실수가 있다. 실수는 한단어로 요약된다. 바로 롤백이다. 100행을PURCHASE_ORDER 테이블에 입력한 다음 커밋하지 않고 롤백하기로 결정하는 경우 어떤일이 발생할까? SMTP(Simple Mail Transfer Protocol)는 Oracle Database 의 분산 트랜잭션에 참가하지 않으므로 e-메일 전송은 롤백이 되지않는다. 실제로는 결코 발생하지 않는100개의 새로운 주문을 설명하는100개의 e-메일을 전송한다. 이것은 트리거를 사용할때, 즉 롤백 할 수 없는 작업을 수행할 때 발생하는 가장 빈번한 오류일 것이다.

(롤백이 발생하도록 하기 위해 직접 롤백을 실행할 필요는 없다는 점을 유 념하자. 정상 처리의 일부로 Oracle Database는 사용자에게 알리지 않고 업데이트, 삭제 및 병합을 롤백한다. 이와 관련한 자세한 내용은 tkyte. blogspot.com/2005/08/something-different-part-i-of-iii.html, tkyte.blogspot.com/2005/08/part-ii-seeing-restart.html 및 tkyte.blogspot.com/2005/09/part-iii-why-is-restart-importantto. html을참조하자.)

이는 다시 말해서 많은UTL_ 함수를 호출하는 트리거가 잘못될 수 있다는 뜻이다. 예를 들어, 트리거를 사용하여UTL_FILE을 호출해 텍스트를 파일에 쓰는 경우 잘못 될 가능성이 있다. 트리거에서UTL_FILE .PUT_LINE 호출은 롤백이 되지 않는다. 절대 발생하지 않는 이벤트에 대해 파일로 쓰도록 할 것이다. 트리거를 사용하여UTL_HTTP를 호출하여 웹 서버의 서비스를 실행하는 경우 결코 발생하지 않은 이벤트에 대해 해당서비스를 호출했을 것이다. UTL_MAIL, UTL_SMTP 및UTL_TCP가 같은 문제를 일으킨다. 트리거를 실행시키는 트랜잭션을 롤백하는 경우 롤백할 수 없는작업을수행했을것이다.

따라서 트리거를 작성하는 사람들의 경우 트리거 코딩의 첫 번째 규칙은 “롤백 할 수 없는 작업을 수행하지 마라”는 것이다. 트리거가 실행되지만 (코드실행) 트랜잭션이 롤백되는 경우 어떤일이 발생하는지생각해보자. 트리거와 관련한 다음 구현문제는 개발자들이 종종 데이터베이스의 동시성 제어와 격리의미묘한 차이점을 완전히 이해 하지 못하고 있다는 사실에서 기인한 것이다. Oracle Database의 가장유용한기능 중 하나가 읽기가 쓰기를 방해하지 않고 쓰기가 읽기를 방해하지 않는다는 것이다. 그러나 개발자가 완벽하게 이해하지 못할 때 이 단일 기능은 개발자가 특히 일부 “규칙”을 시행하는 트리거를 사용할 때는 책임이 된다. 예를 들어, 최근 asktom.oracle.com에서 다음과 같은시나리오를 제공했다.

기본 통화와 국가 통화 조합을 포함하고 있는 테이블이 있다고 가정한다. 다음은 샘플데이터이다.

Country Currency Primary_Currency US USD Y US USN N US USS N

우리는 해당국가에 대해 많아야 하나의 통화를 기본 통화로 할 수 있는 규칙을 시행해야 한다. 해당 국가에 기본 통화가 있는지 여부를 확인하기 위해 각 행마다 위의 표에서BEFORE UPDATE 트리거를 사용한다(예기치 않은 오류를 방지하기 위해 독립된 트랜잭션사용).
이것이 전부다. 필자가 읽었을 때 여기에 심각한 버그가 있다는 것을 알게 되었다. 바꾸어말하면:

•많아야 한가지 통화가 기본통화가 될 수 있다(테이블에서행을가 로지르는 제약 조건이 있음).
•여기에 트리거가. . .있다.
•예기치 않은 테이블 오류를 방지하기 위해 독립된 트랜잭션을 사용 하고 있다.

아래와 같은 트리거가 있을것이다.

SQL> create or replace 2 trigger currencies_trigger 3 before update on currencies 4 for each row 5 declare 6 PRAGMA AUTONOMOUS_TRANSACTION; 7 l_cnt number; 8 begin 9 select count(*) 10 into l_cnt 11 from currencies 12 where primary_currency='Y' 13 and country = :new.country; 14 if ( l_cnt > 1 ) 15 then 16 raise_application_error 17 (-20000, 'only one allowed'); 18 end if; 19 end; 20 / Trigger created.

이제 이 트리거에는 많은 오류가 있다. 그러나 무엇인가 심각하게 잘못되 었다는 첫 번째 확실한 단서는 독립된 트랜잭션을 사용해야 한다는 것이 다. 이는 독립된 트랜잭션없이는 업데이트가 다음과 같은 결과를 낳기 때문이다.

SQL> update currencies 2 set primary_currency = 'Y'; update currencies * ERROR at line 1: ORA-04091: table OPS$TKYTE.CURRENCIES Is mutating, trigger/function may not see it ORA-06512: at "OPS$TKYTE.CURRENCIES_TRIGGER”, line 4 ORA-04088: error during execution of trigger 'OPS$TKYTE.CURRENCIES_TRIGGER'

사실 오류라기보다는 경고에 가깝다. 기본적으로“귀하는Oracle Database 가이와 같은작업을 하도록 허용 하지 않는데도 트리거에서 기본적으로 잘못된 방식을 사용하고 있습니다.”라고 말하고 있다. 데이터베이스가 정의된 테이블을 트리거가 읽도록 허용했다면, 업데이트가 처리되면서 트리거는 테이블이 부분적으로 데이트된 것을 확인할 것이다. 5개 행이 업데이트 되는 경우 행트리거는 수정된 행중 하나가 있는 테이블을 본 다음 둘, 셋등으로 진행할 것이다. 이는 테이블을 결코 볼 수 없는 방식으로 테이블을보게된다.
예를 들어, 위의CURRENCIES 테이블이 질문에서 제공한 샘플 데이터를 갖고 있고 테이블을 변경하는동안 트리거가 테이블을 읽는 것을 허용했다고 가정하자. 이제 다음 명령을 실행한다.

update currencies set primary_currency = decode(currency, 'USD', 'N', 'USN', 'Y') where country = 'US' and currency in ( 'USD', 'USN');

기본 통화 플래그를 USD에서 USN으로 이동시키기 때문에 이 명령은 문제가 없다. 명령문이 완료된 후기본 통화 행은 하나만있을 것이다. 그러나 먼저USN, 그다음 USD의 순서로 행이 업데이트 되었다면 어떻게 될까. 트리거를 처음 실행하면PRIMARY_CURRENCY=‘Y’인USN과 PRIMARY_CURRENCY=‘Y’인USD가 표시된다. 트리거는 명령문을 실패하지만 명령문은 성공한 것처럼 보인다. 반면에 먼저USD, 그런다음 USN의 순서로 데이터가 처리 된다면 어떻게 될까? 이경우 트리거가 실행되고 0개의PRIMARY_CURRENCY=‘Y’행을 찾은 다음 다시 실행하면 하나만 표시되고 완료된다.

따라서 이트리거에서, 업데이트는 때때로 일부 데이터에 대해서 만적용된다. 일부 데이터의 경우이터가 있는 2개의 데이터베이스는 다른 행에서는 실패하고 또 다른 행에서는 성공할 것이다. 이는 디스크에서 데이터가 어떻게 구성되었으며 어떤 순서로 처리되는지에 달려 있다. 이런 것은 용납되지 않는다(혼동스러운 것은말할것도없다).

즉 서로를 보호하려는 독립된 테이블 제약 조건이 존재하기 때문이다. 그러나 불행하게도 이 문제를 물어보는 개발자들은 독립된 테이블 제약 조건을 해결하는 방법이 독립된 트랜잭션이라는 것을 발견했다. 이“기능”은 마치 다른세션/트랜잭션에 있는 것처럼 트리거가 실행되는 테이블에 개발자가 쿼리하는 것을 허용한다. 트리거는 스스로가 테이블을 수정하는 것을 알지못하며 바로이점에서“트리거가자신이알수 없는 수정을 확인하려고 시도한다”는 생각에 심각한 모순이있는 것이다 트리거의 유일한 목적은 데이터가 수정 되었는지 확인하는 것이지만, 트리거는수정이 되기전의 데이터를 읽고있다. 따라서 정상적인 작업이 될 수 없다!
개발자는 이 독립된 테이블 제약 조건을 해결하는 다른 방법을 찾을 수 있다. 일반적인 기법은 패키지와3개의 트리거를 사용하는 것이다(asktom. oracle.com/tkyte/Mutate에 설명되어 있다. Oracle Database 11g에는 이“패키지및3개트리거”기법 대신 사용할 수 있지만, 그 결과는 다음에서 설명하는 것과 동일한 복합트리거라는 새로운 기능이 있다).

이 기법에서 패키지는 배열(array) 같이PLSQL 테이블 유형의 글로벌 변수를 사용한다. 글로벌 변수는BEFORE 명령문 트리거에 의해“공백”으로 설정된다. 그런 다음 글로벌 변수는FOR EACH ROW 트리거(수정 된행의키) 또는rowids에 의해 기본키로 채워진다. 마지막으로 AFTER 명령문 트리거는 글로벌 변수값에 대해 반복되고 수정이 이미 일어났기 때문에 트리거가 정의된 테이블을 쿼리할 수 있다. 이는 사용자가 데이터베 이스의 유일한 사용자이고 한 번에 둘 이상의 트랜잭션을 갖고 있지 않은 경우에 효과적이라는 것을 의미한다! 제가 검토한 다른 많은 트리거와 마 찬가지로 이 트리거 솔루션은 순전히 단일 사용자 환경에서 실행된다. 격리된 곳에서는 제대로 실행되지만 여러 사용자가 동시에 트리거를 호출 할 때는 제대로 실행 되지않는다.

예를들어, 통화테이블을 다음과 같이 시작했다고 가정해보자 (primary_currency가모두N):

Country Currency Primary_Currency US USD N US USN N US USS N

이제 한세션에서 다음 명령을 실행한다.

update currencies set primary_currency='Y' where country = 'US' and currency = 'USD';

3개트리거 솔루션에서는 BEFORE 명령문 트리거가 실행 되고 세션의 글로벌 변수를 공백으로 설정할 것이다. 그런 다음 FOR EACH ROW 트리 거가실행되고:NEW.COUNTRY 및:OLD.COUNTRY 값을 기록하는 것처럼 이 글로벌 변수에서 수정한 국가를 기억한다. 마지막으로, 모든 레코드가 업데이트 된 후에 AFTER 명령문 트리거가 실행되고 발견한 COUNTRY 값에 대해 반복된다. 따라서PRIMARY_CURRENCY= ‘Y’및COUNTRY=‘US’레코드를 쿼리하고 계산하며 한 레코드만 있 다는것을발견한다. 모든 것이 순조롭게 진행된다. 그러나 다른 세션에서, 이 업데이트 직후에(아직 커밋하지 않았음) 다음 명령을 실행한다.

update currencies set primary_currency='Y' where country = 'US' and currency = 'USN';

이제 트리거는 이세션에서 실행되고 AFTER 명령문 트리거가 다른 세션에서 했던 것처럼 같은 카운트를이AFTER 명령문 트리거가 수행하면 이세션의 트리거도COUNTRY=‘US’에대해PRIMARY_CURRENCY= ‘Y’인 레코드가 하나만 있는 것을 발견한다! 이와같은 이유로 이트리거의 읽기가 다른세션의 쓰기에 의해 차단되지 않는 것이며 이 세션의 트리거는 다른 세션의 업데이트를 알수없다.

이제 두 트랜잭션을커밋하고COUNTRY=‘US’의두레코드가PRIMARY _CURRENCY value=‘Y’인 테이블을 갖게 된다. 즉, 우리가 확인하려 고 했던 규칙이 확인되지 않았다. 트리거에서 이 규칙을 시행하려면 테이 블을 잠그고 테이블에 순서대로 액세스해야 한다. 두 세션이 같은 국가에 대해동시에기본통화를입력하지않도록해야한다(우리가볼수없는데 이터를 잠그는 것은 매우 어려우며 다른 사람이 입력하는 것을 알 수 없으므로 이 사람이 입력하지 못하도록 해야 한다).

테이블의행에서무결성을시행하는트리거를갖고있는경우이트리거는 참조무결성을시행하려고테이블에접근을시도하고여러분은순차적액 세스를위한LOCK TABLE 명령을사용하지않았기때문에잘못될것은 확실하다. 읽기는 쓰기에 의해 차단되지 않고 마찬가지로 쓰기도 읽기에 의해차단되지않기때문에트리거에서엔티티무결성을시행하는것은매 우복잡하고명시적잠금을포함해야한다. 더욱이이잠금은행수준잠금 보다 훨씬 상위 수준에서 이루어져야 한다. 대개 테이블 수준 잠금이 되어 야한다.

그러나 필자는 머지 않아 이와같은 방식으로LOCK TABLE 명령을 사용 하는 애플리케이션을 보게될 것이다. 트리거를 사용하는 많은 애플리케이션이 엔티티 무결성을 시행하지만 거의 일반적으로 잘못 구현된 것을확인 할 수 있었다. 이들은 다중 사용자 시나리오에서 잘못된 데이터를 입력하 고저장하는경쟁조건을갖고있다. 결국 다 른프로세스가 데이터가“정제 되고 유효하며 규칙 을따른다”는가정하에 작업하는 다운 스트림 처리에서 실패를 초래하게된다.

answer

앞에 언급된 비즈니스 문제에 대한 답은 무엇일까(많아야 하나의 통화가 해당국가의기본통화가될수있음)? 두가지해답이있다. 둘모두동시성 이있고(확장가능), 올바르다(실제로작동). 트리거를사용하는경우매우 동시적이거나 올바른 것 중 하나를 선택할 수 있다. 둘을 동시에 만족시킬 수는없다. 트리거구현으 로 매우 동시적인 경우, 아마도 잘못 수행했을 것이다. 트리거 구현이 올바르다면 거의 분명히 확장성은매우 낮을 것이다. 따라서 데이터모델이 완전히 잘못되었기 때문에 저의 첫번째 답변을 참조 하여 데이터 모델을 수정해야 한다. 이 비즈니스 문제는 기본 통화가 있는 테이블과 다른 통화가 있는 테이블 등 두 테이블에서 오는 것이다. 애플리 케이션이쿼리하기쉬운단일“테이블”을요구하는경우보기는모든데이 터를 통합할수있다“. 많아야 한 통화가 기본 통화가 될수있음”규칙을 시행하기 위해 간단히 다음 명령을실행할것이다.

SQL> create table primary_currency 2 ( country varchar2(2) primary key, 3 currency varchar2(3) 4 ) 5 / Table created.
SQL> create table other_currencies 2 ( country varchar2(2), 3 currency varchar2(3), 4 constraint other_currencies_pk 5 primary key(country,currency) 6 ) 7 / Table created.

완료되었다. 우리는하나의“규칙”을구현했다. 나는 여러분이 COUNTRY =‘US’에 대해 두 개의 기본 통화를 만들어 볼 것을 주문할 것이며 그것은 불가능하다. 기본키가 이를 실행한다.
그러나  실제운영환경에서이는일반적으로시작일뿐이다.“ 많아야하나 의 통화가 해당 국가의 기본 통화가 될 수 있음”이라는 다음 규칙은“한 국 가는 하나의 기본 통화를 가져야 함”이라는 말과 같다. 즉, 많아야 하나의 기본통화가있고적어도하나이상의기본통화가있어야한다.
다시말해이를위한코드가필요하지않다. 간단한선언적제약조건이우 리가필요한모든것을수행한다.

SQL> alter table other_currencies add 2 constraint must_have_at_least_one_primary 3 foreign key(country) 4 references primary_currency(country) 5 / Table altered.

우리는다시한번실행했으며, 이번에는원래규칙과후속규칙, 코드가없 고 유지 보수하고 이해하기 쉬운 방법을 사용했다. 외래 키를 사용하면 기 본통화를갖지않고다른통화를갖는것이불가능하다. 그리고이는매우 확장가능한방식으로올바르게수행된다.

실제환경에서는요점을강조하기위해이데이터에필요한규칙을사용하 여 수행하지 않을 것이다. 실질적이고 완벽한 규칙은 아마도 다음과 같을 것이다.“ 한 국가는 최소한, 그리고 많아야 하나의 기본 통화를 가져야 하 며기본통화는다른통화가될수없습니다.”즉, USD가미국의기본통화 이면USD는COUNTRY=‘US’일때OTHER_CURRENCIES 테이 블에나타날수없다.

이것은 까다로운 작업이다. 마치 존재하지 않는“반외래 키”같다. 그러나 우리는 데이터베이스 규칙으로 이를 구현할 수 있다. 기본적으로 우리는 COUNTRY 및CURRENCY에 의해PRIMARY_CURRENCY를 OTHER_CURRENCIES에 조인하는 경우 결과적으로 항상 제로 레코 드가있는지확인해야한다.
리스팅1은 조인의 결과로 데이터가 생성되지 않도록 조인하고 제약 조건 을설정하는materialized view를만든다.
코드리스팅1: 조인이데이터를생성하지않도록하는Materialized view 및제약조건

SQL> create materialized view log 2 on primary_currency with rowid 3 / Materialized view log created. SQL> create materialized view log 2 on other_currencies with rowid 3 / Mat erialized view log created. SQL> create materialized view primary_is_not_other 2 refresh fast 3 on commit 4 as 5 select a.rowid arid, b.rowid brid 6 from primary_currency a, other_currencies b 7 where a.country = b.country 8 and a.currency = b.currency 9 / Materialized view created. SQL> alter table primary_is_not_other 2 add constraint primary_curr_cannot_be_other 3 check (arid is null and brid is null) 4 / Table altered.

따라서 이제 커밋 할 때 리프레시 되는 materialized view를 갖게 되었고 두 테이블 간에 데이터가 조인할 수 없도록 할 수 있다. 이 materialized view는항상비어있다. 그리고확장가능하며(직렬화를위한유일한기회 가COMMIT 시간에 있음) 올바르다. 데이터베이스는 우리를 대신하여 이 제약조건을시행한다.
이것이원래규칙“많아야 하나의 통화가 해당국가의 기본통화가 될 수 있음”을 충족시키는 첫번째 응답이며 같은 정보에 대해 추가규칙이 필요할 수있다. 두번째 응답은 원래규칙이 유일한 규칙인 경우(위에서설명한규 칙의다른두부분이없음) 원래테이블에서 다음 색인을 사용할 수 있다.

create unique index only_one_can_be_primary on country_currency_ref ( case when primary_currency = 'Y' then country end );

이 응답은PRIMARY_CURRENCY=‘Y’일 때COUNTRY를 고유 하게색인화한다. PRIMARY_CURRENCY가‘Y’가 아니면 케이스 명령문은 색인화 되지 않은 전체NULL 색인키를 반환한다. 이와같은식으로PRIMARY_CURRENCY=‘Y’레코드만 고유하게 색인화 한다.
나는이색인을“불량해킹”이라고간주한다. 수행되기는 하지만 썩훌륭한 것은 아니다. 그리고 존재할 수도있는 다른 제약조건은 실행할 수 없다. 요구사항을 지원하기 위해 설계했을 때 올바른 데이터 모델은 선언적명령문으로 훌륭하게 모든 것을 수행한다.
코드가 적을 수록 버그도 적어진다. 적은 수의 코드를 작성하는 방법을 찾 아보아야한다.

결론

트리거는 회의론자의 눈에 띄어야 한다. 트리거가 엔티티 무결성을 실행하는 곳에 있는 경우 의심해보고 다중 사용자 조건에 대해 생각해보자. 두명 이나 세 명이 유사한 데이터에서 동시에 작업할 때 어떤 일이 발생할지 생 각해야 한다. 머리 속이나 화이트보드에서 모든 조합을 실행하자. 다중 세 션을 사용하여 스키마를 실행한다. 동시 액세스할 때 어떤 일이 일어나는지 확인해야한다. 열에값을 제공하는 트리거가 있는경우, 가능한 유지보수 문제와 “예상치못한”부작용을 인식 해야한다.

트리거는 규칙이 아니라 예외가 되어야 한다. 다른 방식으로 무엇인가 할 수없을 때만 사용해야 한다. 동일성문제, 비트랜 잭션작업을 수행하는 문제 및 유지보수 문제가 있는 경우 트리거는 그 사용을 자제해야한다.


필자소개

Tom Kyte는 오라클의 서버 기술 사업부 내 데이터베이스 전도사로서 1993년부터 오라클에서 근무했다. 그는 Expert Oracle Database Architecture: 9i and 10g Programming Techniques(Apress, 2005) 및 Effective Oracle by Design(Oracle Press, 2003)의 저자이다.


출처 : 한국 오라클
제공 : DB포탈사이트 DBguide.net 

댓글 없음:

댓글 쓰기

팔로어