'Oracle/PL/SQL Pattern'에 해당되는 글 4건

  1. 2010.05.07 PL/SQL-면접문제 808
  2. 2009.11.11 오라클에서 is_number, is_date 함수 사용하기 457
  3. 2008.05.09 묵시적인 형변환을 피하라 414
  4. 2008.05.02 PL/SQL 에서 NUMBER 타입의 성능 테스트

작년에 회사에서 기술면접에 필요한 문제를 여러 개 만들었는데 그 중에 하나를 소개한다. PL/SQL의 예외처리에 관련된 것이고 개념문제이므로 응시자들이 어렵지 않게 풀 수 있을 거라 생각하였다. 하지만 결과는 예상 밖이었다.

 

오라클 내부구조, OWI, AWR, Query Transformation 등 어려운 개념은 맞추는 사람이 있는 반면 PL/SQL의 기본에 속하는 예외처리에 대해서는 아무도 정답을 맞추지 못했다. 정답에 근접한 사람도 아무도 없었다. 도대체 얼마나 어려운 문제이길래? 여러분도 아래의 문제를 풀어보기 바란다.

 

문제3)

1. Built In Exceptions

2. User-Defined Exceptions

3. RAISE_APPLICATION_ERROR  

4. EXCEPTION_INIT Pragma


위의 네 가지는 오라클 PL/SQL 상에서 예외처리방법을 나열한 것이다.
이 네 가지의 차이점을 설명하시오.

 

 

이 네 가지의 차이점을 정확히 알고 있는 사람은 아래의 파일을 다운받을 필요가 없다. 하지만 차이점을 정확히 설명할 수 없다면 이 기회에 정리하기 바란다. 4(8 페이지) 이므로 10분만에 볼 수 있을 것이다. 정답은 마지막 페이지에 있다.

 

모든 분야에는 기본이라고 불리는 것이 있다. 오라클의 세계에서도 예외는 아닌것 같다. 필자 또한 기본을 놓치지 않으려고 노력하는 사람중의 하나이다.


invalid-file

Oracle PL/SQL - Exceptions 정리

Posted by extremedb
,

지인에게서 전화가 오다
지인 : 데이터를 체크해야 하는데 오라클에 is_number, is_date 함수가 없어서 데이터를 체크하기가 불편합니다.
          데이터를 오라클에서 가져와서 자바에서 체크하고 있습니다. 그러다 보니 너무 느립니다.
필자 : 그럴 필요 없습니다.
지인 : 물론 External Function을 사용하면 자바를 사용하여 오라클에 함수를 생성할수도 있겠지요.
필자 : 그냥 PL/SQL 로 하시면 됩니다.
지인 : 네?

무서운 일이다. 전체 데이터를 Network를 타고 가져와서 자바로 체크하다니... Network I/O 가 엄청 날것이다.          

오라클에서 제공하는 함수가 없다
"오라클에서 is_number, is_date 함수가 없어서 데이터를 체크하기가 불편하다" 이말은 옳다. 하지만 오라클에서 체크함수를 제공하지 않는 이유는 아마도 개발자가 너무도 쉽게 만들 수 있어서 그런 것이 아닐까?

is_number, is_date
함수를 직접 만들어 보자.

 

CREATE OR REPLACE FUNCTION is_number(v_str_number IN varchar2)

RETURN NUMBER

IS  /* 데이터가 number 형인지 검사하는 함수임. 1 이 나오면 NUMBER 형임 */

     V_NUM NUMBER;

BEGIN

  V_NUM := TO_NUMBER(v_str_number);

  RETURN 1;

EXCEPTION

  WHEN OTHERS THEN RETURN 0    ;

END;


CREATE OR REPLACE FUNCTION is_date(v_str_date IN varchar2, V_FORMAT IN VARCHAR2 DEFAULT 'YYYYMMDD')

RETURN NUMBER

IS   /* 데이터가 DATE 형인지 검사하는 함수임. 1 이 나오면 DATE 형임 */

     V_DATE DATE;

BEGIN

  V_DATE := TO_DATE(v_str_date, V_FORMAT);

  RETURN 1;   

EXCEPTION

  WHEN OTHERS THEN RETURN 0    ;

END; 


너무나 쉽게 생성 되었다. 그럼 이제 사용해보자.


함수사용법


select  is_number('abcd'), is_number('1234'),

        is_date('20090230'), is_date('20090228')

  from dual ;

 

결과: 

IS_NUMBER('ABCD') IS_NUMBER('1234') IS_DATE('20090230') IS_DATE('20090228')

----------------- ----------------- ------------------- -------------------

                0                 1                   0                   1

1 row selected.


number 형 에서 벗어나는 데이터와 date 형 에서 벗어나는 데이터를 가려 내었다. DBMS 에서 사용할 수 있는 함수와 기능이 똑같다. 이렇게 해서 개발자의 문제가 일시적으로 해결되었다.



-------------------------------------------------------------------------------------------------------------------------

문제는 성능이다
다음날 다시 전화가 왔다. 일회성이 아닌 지속적으로 데이터를 체크해야 하는데 이전보다는 빨라졌지만 여전히 성능이 느리다는 것이었다. 이제부터 함수의 성능에 대해 논의 해보자. 먼저 올바른 데이터 1000만 건을 만들고 number형이 아닌 데이터와 date형이 아닌 데이터를 1건 추가해보자.  

 

drop table test_tbl purge;

 

create table test_tbl nologging as

select a.*

  from (select to_char(level) as varchar_num, to_char(level + sysdate, 'YYYYMMDD') as varchar_date

          from dual

       connect by level <= 100) a,

       (select level from dual connect by level <= 100000) b ;

 

insert into test_tbl values('ABCD', '20090230');

commit;


이제 함수를 실행 해보자.

 

alter session set statistics_level = all;

alter system flush buffer_cache;

 

select /*+ gather_plan_statistics */ *

  from test_tbl a

 where is_number(varchar_num) = 0;


결과 :

VARCHAR_NUM      VARCHAR_DATE

---------------- ------------

ABCD             20090230

 

select * from table(dbms_xplan.display_cursor(null,null,'allstats last')) ;

 

-------------------------------------------------------------------------------

| Id  | Operation         | Name     | A-Rows |   A-Time   | Buffers | Reads  |

-------------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |          |      1 |00:00:45.47 |   23463 |  23447 |

|*  1 |  TABLE ACCESS FULL| TEST_TBL |      1 |00:00:45.47 |   23463 |  23447 |

-------------------------------------------------------------------------------

 

Predicate Information (identified by operation id):

---------------------------------------------------

   1 - filter("IS_NUMBER"("VARCHAR_NUM")=0)

 


함수를 사용하면 너무 느리다

함수를 천만번 수행하는데 무려 45초 이상 걸렸다. 너무나 느려서 사용할 수 없는 수준이다. 함수를 빠르게 실행하기 위해서 Deterministic 형 함수로 수정해보자. Deterministic 함수는 Input 에 대한 Output 의 값이 항상 같을 때만 사용해야 한다. Deterministic 함수를 사용하면 같은 값의 Input이 여러 번 들어올 경우 한번만 수행할 수 있다. 하지만 Deterministic 함수도 비효율이 있다. Post 의 마지막에 Deterministic 함수 의 비효율과 관련된 Link를 표시하였으므로 반드시 읽어보기 바란다.

 

-- Deterministic 함수로 바꿈

CREATE OR REPLACE FUNCTION is_number(v_str_number IN varchar2)

RETURN NUMBER DETERMINISTIC IS 

이후 생략

/

DETERMINISTIC 함수를 사용해보자

alter system flush buffer_cache;

 

select /*+ gather_plan_statistics */ *

  from test_tbl a

 where is_number(varchar_num) = 0;

 

select * from table(dbms_xplan.display_cursor(null,null,'allstats last')) ;

 

-------------------------------------------------------------------------------

| Id  | Operation         | Name     | A-Rows |   A-Time   | Buffers | Reads  |

-------------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |          |      1 |00:00:07.50 |   23463 |  23447 |

|*  1 |  TABLE ACCESS FULL| TEST_TBL |      1 |00:00:07.50 |   23463 |  23447 |

-------------------------------------------------------------------------------

 

Predicate Information (identified by operation id):

---------------------------------------------------

   1 - filter("IS_NUMBER"("VARCHAR_NUM")=0)

 

대단한 성능향상이다. 수행시간이 45초 에서 7초로 줄어들었다. 하지만 여기서 멈출순 없다.

alter system flush buffer_cache;

 

select /*+ gather_plan_statistics */ *

  from test_tbl a

 where (select is_number(varchar_num) from dual) = 0;

 

select * from table(dbms_xplan.display_cursor(null,null,'allstats last')) ;

 

--------------------------------------------------------------------------------

| Id  | Operation          | Name     | A-Rows |   A-Time   | Buffers | Reads  |

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |          |      1 |00:00:03.00 |   23439 |  23433 |

|*  1 |  FILTER            |          |      1 |00:00:03.00 |   23439 |  23433 |

|   2 |   TABLE ACCESS FULL| TEST_TBL |     10M|00:00:00.01 |   23439 |  23433 |

|   3 |   FAST DUAL        |          |    101 |00:00:00.01 |       0 |      0 |

--------------------------------------------------------------------------------

 

Predicate Information (identified by operation id):

---------------------------------------------------

   1 - filter(=0)  


함수사용시 스칼라서브쿼리를 활용하라
천만 건을 체크하는데 3초 밖에 걸리지 않았다. 함수 사용시 스칼라 서브쿼리를 사용하면 비효율 없이 함수 호출을 최소화 할 수 있다. Deterministic 함수든 아니든 상관없이 스칼라 서브쿼리의 효과는 동일하다. 그렇다면 함수 + 스칼라서브쿼리의 조합이 최선인가? 만약 일회성이 아닌 지속적으로 데이터를 체크해야 하는 경우라면 FBI(Function Based Index)를 생성해야 한다. 

 

create index idx_is_number on test_tbl (is_number(varchar_num)) ; -- FBI 생성

 

alter system flush buffer_cache;

 

select /*+ gather_plan_statistics index_rs(a idx_is_number) */ *

  from test_tbl a

 where is_number(varchar_num) = 0;

 

select * from table(dbms_xplan.display_cursor(null,null,'allstats last')) ; 

 

----------------------------------------------------------------------------------------------

| Id  | Operation                   | Name          | A-Rows |   A-Time   | Buffers | Reads  |

----------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT            |               |      1 |00:00:00.03 |       5 |      4 |

|   1 |  TABLE ACCESS BY INDEX ROWID| TEST_TBL      |      1 |00:00:00.03 |       5 |      4 |

|*  2 |   INDEX RANGE SCAN          | IDX_IS_NUMBER |      1 |00:00:00.03 |       4 |      3 |

----------------------------------------------------------------------------------------------

 

Predicate Information (identified by operation id):

---------------------------------------------------

   2 - access("A"."SYS_NC00003$"=0)

FBI 가 최적이다
Block I/O 수(Buffers 항목)를 비교해보라. 함수 + 스칼라 서브쿼리를 사용하는 것과 인덱스를 사용하는 것은 성능의 비교가 되지 않는다.  지속적으로 데이터를 검증해야 하고 테이블의 건수가 많지만 데이터를 체크하여 만족하지 않는 데이터의 건수가 적은 경우는 인덱스를 사용하는 것이 최적임을 알 수 있다
.

이제 내일은 is_date, is_number 함수와 관련된 문제로 필자에게 전화가 오지는 않으리라 믿는다.^^

관련 Post :
http://ukja.tistory.com/159
http://adap.tistory.com/entry/Deterministic-의-진실Multi-buffer



'Oracle > PL/SQL Pattern' 카테고리의 다른 글

PL/SQL-면접문제  (808) 2010.05.07
묵시적인 형변환을 피하라  (414) 2008.05.09
PL/SQL 에서 NUMBER 타입의 성능 테스트  (0) 2008.05.02
Posted by extremedb
,

묵시적인 형변환을 피하라

SQL 에서 묵시적인 형변환(Implicit Datatype Conversion) 을 피하라.
위와같은 말을 언젠가 들어보았을것이다.
WHERE 절의 묵시적 형변환에 의한 Full table scan 은 잘알려져 있다 .
이문제는 Oracle 10g 부터 SQL 이 PL/SQL내에서 사용된다면 dbms_warning 패키지나 plsql_warnings 기능을 사용하면 식별될수 있다.
아래 예제를 보자.

SQL> ALTER SESSION SET PLSQL_WARNINGS='ENABLE:ALL'; --> warning 가능 활성화

SQL> CREATE TABLE t ( a VARCHAR2(10) ); --> varchar2 타입으로 컬럼생성
Table created.

SQL> CREATE OR REPLACE PROCEDURE p
2 IS
3 BEGIN
4 INSERT INTO t VALUES ( 10 ); --> number 타입으로 insert
5 END p;
6 /

SP2-0804: Procedure created with compilation warnings

SQL> SHO ERR
Errors for PROCEDURE P:

4/26 PLW-07202: bind type would result in conversion away from column type
-->4번째 라인에서 묵시적인 형변환이 일어나고 있음을 알려준다.

그런데 이런 묵시적인 형변환에의한 성능저하가 SQL에만 해당되는 이야기인가?
묵시적인 형변환에 의한 PL/SQL 자체의 성능도 한번쯤 의문을 가져볼 필요가 있다.

아래 예제는 데이터 타입선정을 잘못하면 프로그램이 얼마나 망가질수 있는지를 잘나타내고 있다.

create or replace procedure imp_type_conv_test is
x date;
y varchar2(12) := '01-MAR-03'
t number := dbms_utility.get_time;
begin
for i in 1 .. 1000000 loop
x := y; --> 묵시적인 형변환
end loop; dbms_output.put_line((dbms_utility.get_time-t)'hsec');
end;
/

결과 : 826hsec

create or replace procedure exp_type_conv_test is
x date;
y x%type := to_date('01-MAR-03'); --> date 형으로 선언하고 미리 conversion 함
t number := dbms_utility.get_time;
begin
for i in 1 .. 1000000 loop
x := y; --> 형변환이 일어나지 않음
end loop;
dbms_output.put_line((dbms_utility.get_time-t)'hsec');
end;
/

결과 : 38hsec --> 무려 2200% 가까이 성능이 향상됬다.

결론 :
Implicit Datatype Conversion 은 SQL 의 성능 뿐만아니라 PL/SQL 자체의 성능도 저하시킴을 알수 있다.
명시적으로 TO_DATE, TO_NUMBER 등의 함수를 사용함으로써 좀더 직관적이고
성능면에서도 유리한 프로그램을 개발할수 있다.

참조서적 : Mastering Oracle PL/SQL(Connor McDonald )

'Oracle > PL/SQL Pattern' 카테고리의 다른 글

PL/SQL-면접문제  (808) 2010.05.07
오라클에서 is_number, is_date 함수 사용하기  (457) 2009.11.11
PL/SQL 에서 NUMBER 타입의 성능 테스트  (0) 2008.05.02
Posted by extremedb
,

PL/SQL 에서 NUMBER 타입의 성능 테스트

10g 버젼까지는 소수점없는 숫자타입 연산에서 제일빠른 숫자타입은 pls_integer 였다.
이제 11g 에서 기존 pls_integer 의 sub type 인 simple_integer 이 나왔으므로 성능관점에서
어떤것이 가장빠른것인지 test를 해볼 필요가 있다.
PL/SQL 의 성능관점에서 11g 에 새로운 simple_integer 타입에 대하여 간단한 테스트를 진행하고자 한다.
진행하기전에 먼저 simple_integer 의 특징에 대해 간단히 설명한다.

simple_integer 의 특징
1)simple_integer 는 기존 pls_integer 의 sub type 이다.
2)simple_integer 의 사용범위는 2,147,483,648 ~ 2,147,483,647 이다.
3)null 값을 가지지 못한다. --> default 로 0 을 주어야 한다.
4)성능면에서 최적이다. 특히 native 로 컴파일 할때는 pls_integer 보다 4~5 배 빠르다.

아래는 number 와 pls_interger,simple_integer 를 비교실험하는 간단한 프로시져이다.
로직은 단순히 1씩 증가하는 연산이다.
성능테스트

CREATE OR REPLACE PROCEDURE plsql_number_test(v_loop in number) as
l_start NUMBER;
l_number NUMBER := 0;
l_number_incr NUMBER := 1;
l_pls_integer PLS_INTEGER := 0;
l_pls_integer_incr PLS_INTEGER := 1;
l_simple_integer SIMPLE_INTEGER := 0;
l_simple_integer_incr SIMPLE_INTEGER := 1;

BEGIN

-- number 형 test
l_start := DBMS_UTILITY.get_time;
FOR i IN 1 .. v_loop LOOP
l_number := l_number + l_number_incr;
END LOOP;

DBMS_OUTPUT.put_line('NUMBER : ' (DBMS_UTILITY.get_time - l_start) ' hsecs');

-- pls_integer 형 test
l_start := DBMS_UTILITY.get_time;
FOR i IN 1 .. v_loop LOOP
l_pls_integer := l_pls_integer + l_pls_integer_incr;
END LOOP;
DBMS_OUTPUT.put_line('PLS_INTEGER: ' (DBMS_UTILITY.get_time - l_start) ' hsecs');

--simple_integer 형 test
l_start := DBMS_UTILITY.get_time;
FOR i IN 1 .. v_loop LOOP
l_simple_integer := l_simple_integer + l_simple_integer_incr;
END LOOP;
DBMS_OUTPUT.put_line('SIMPLE_INTEGER: ' (DBMS_UTILITY.get_time - l_start) ' hsecs');

END plsql_number_test;
/

실행1
SQL> plsql_number_test(100000000 );

결과1
NUMBER : 1217 hsecs
PLS_INTEGER: 546 hsecs
SIMPLE_INTEGER: 512hsecs --> 가장빠르나 PLS_INTEGER 와는 거의 차이가 나지않는다.

NATIVE 모드로 컴파일 한다.
ALTER SESSION SET PLSQL_CODE_TYPE=NATIVE;
ALTER PROCEDURE plsql_number_test COMPILE;

실행2
SQL> plsql_number_test(100000000 );

결과2
NUMBER : 386 hsecs
PLS_INTEGER: 129 hsecs
SIMPLE_INTEGER: 32 hsecs -->PLS_INTEGER 보다 무려 4배정도가 빠르다.


결론 :
native 로 컴파일 했을때 모든 숫자형식이 다빨라졌지만 특히 SIMPLE_INTEGER의 성능향상이 눈에 뛴다.
2,147,483,648 ~ 2,147,483,647 범위내에서 소수점이 없는 숫자연산을 할경우에는
SIMPLE_INTEGER 를 써야 한다.
물론 10g 이하버젼 이라면 number 형보다 2~3 배 이상 빠른 PLS_INTEGER 를 사용해야 할것이다.
아쉬운점은 PL/SQL 뿐만 아니라 테이블의 데이터 타입으로도 사용할수 있으면 하는것이다.
12버젼에서 컬럼타입으로 사용할수 있게 되기를 기대해보면서 이글을 마친다.

'Oracle > PL/SQL Pattern' 카테고리의 다른 글

PL/SQL-면접문제  (808) 2010.05.07
오라클에서 is_number, is_date 함수 사용하기  (457) 2009.11.11
묵시적인 형변환을 피하라  (414) 2008.05.09
Posted by extremedb
,