Post List

2025년 6월 24일 화요일

세션모니터링

Describe 세션모니터링 here
set linesize 200 pagesize 999
spool c:\session_monitoring.txt
select sid, username from v$session;
----------------------------------
-- 반드시 아래의 글을 읽어 볼 것!!
----------------------------------
--
-- Oracle 8.1.6에서 테스트 한 것임
-- 해당되는 사용자의 sid를 넣는다. 
-- 프로그램이 강제로 kill하는 중이라면 Serial#는 계속 변화한다.
-- alter system kill session 'sid, serial#'; 명령으로 kill 할 수 있다.
-- define으로 위에서 조회한 것을 토대로&sid에 일률적인 값을 넣는다. 
-- (사용예 : define sid = 12)

-----------------
--세션 모니터링--
-----------------
col NAME FOR a60
SELECT a.sid, b.NAME, a.VALUE 
FROM v$sesstat a, v$statname b
WHERE a.statistic# = b.STATISTIC#
AND VALUE > 10000
AND a.sid = &sid
ORDER BY 1;

--------------------------------
--세션에서 수행되는 SQL문 찾기--
--------------------------------
select a.piece || ' ' || a.sql_text
from v$sqltext_with_newlines a, v$session b
where a.address = b.saddr
and b.sid = &sid
order by 1;

-------------------------
--세션 이벤트 통계 정보--
-------------------------
col event 	format a30
col t_wait	format 999999
col t_out	format 99999
col t_waitd	format 99999
col m_wait	format 99999
select event, 
       total_waits t_wait,
	   total_timeouts t_out,
	   time_waited t_waited, 
	   average_wait a_wait,
	   max_wait m_wait
from v$session_event
where sid = &sid;

------------------
--세션 wait 정보--
------------------
col p1text format a10
col p2text format a10
col p3text format a10
col e_name format a20
col p3 format 999
select
 SID, 
 substr(EVENT, 1, 20) e_name, 
 P1 , 
 P1RAW, 
 P3TEXT, 
 P3 
from v$session_wait
WHERE SID=&sid;

-----------------------
--PGA, UGA양 알아내기--
-----------------------
col username format a20
col program format a20
col pga format a20
col uga format a20
col terminal format a20
select max(decode(t2.num, 1, sid)) sid, 
	   max(decode(t2.num, 1, username)) username, 
	   max(decode(t2.num, 1, pgm)) program, 
	   max(decode(t2.num, 1, terminal, 'UGA, PGA 합계 :')) terminal,
	   sum(pga)||'K' pga,
	   sum(uga)||'K' uga
from (
	  select a.sid, a.username, substr(a.program, 1, 25) as pgm, a.terminal, 
	  max(decode(c.name, 'session pga memory', trunc(value/1000), 0)) pga, 
	  max(decode(c.name, 'session uga memory', trunc(value/1000), 0)) uga 
	  from v$session a, v$sesstat b, v$statname c 
	  where a.sid = b.sid 
	  and b.statistic# = c.statistic# 
	  and c.name like 'session%' 
	  group by a.sid, a.username, substr(a.program, 1, 25), a.terminal) t1,
	  (select 1 as num from dual union all select 2 from dual) t2
group by decode(t2.num, 1, sid);
spool off




Shared Pool Size예상

Describe SharedPoolSize예상 here


CREATE OR REPLACE FUNCTION calc_shared_pool(max_users IN NUMBER)
RETURN NUMBER
IS
    --------------------------------------------------
    -- 반드시 as sysdba 권한을 가진 사용자로 접속한다. 
    --------------------------------------------------
    
    session_id NUMBER;     --sample session id
    session_uga_max NUMBER; -- session uga_memory max
    sql_area NUMBER;
    plsql_area NUMBER;
    temp NUMBER;
BEGIN
     -- 하나의 세션이 가지는 메모리에 대한 샘플 세션을 가져온다. 
     SELECT id INTO session_id
     FROM (SELECT sid AS id
           FROM  v$process p, v$session s
           WHERE p.addr = s.paddr
           ORDER BY dbms_random.VALUE)
     WHERE ROWNUM = 1;
      --dbms_output.put_line('샘플 세션 ID : ' || session_id);
      
      --해당 세션에 가질 수 있는 최대 메모리
      SELECT VALUE INTO session_uga_max
      FROM v$sesstat s, v$statname n
      WHERE s.statistic# = n.statistic#
      AND n.NAME = 'session uga memory max'
      AND sid = session_id;
      --dbms_output.put_line('해당 세션에 가질 수 있는 최대 메모리 : ' || session_uga_max);
      
      --Total Shared SQL Area
      SELECT SUM(sharable_mem) INTO sql_area
      FROM v$sqlarea;
      
      --PL/SQL Sharable Memory Area
      SELECT SUM(sharable_mem) INTO plsql_area
      FROM v$db_object_cache;
      
      --최소 Shared Pool Size 계산 
      temp := (session_uga_max * max_users) + sql_area + plsql_area;

      RETURN CEIL((temp + temp*0.3)/1024/1024);
END;
/

--Shared Pool의 크기는 이제부터다. 
--Shared Pool 크기 계산을 위한 임시테이블을 생성후 주기적으로  아래와 같은
--Insert ~ Select 문을 이용해서 삽입한후 마지막으로 가장 큰 값을 찾아낸다. 

CREATE TABLE temp_shared_pool_size(nSIZE NUMBER);

INSERT INTO temp_shared_pool_size
SELECT calc_shared_pool(400) "최소 Shared Pool Size(MB)" 
FROM dual;

--이 결과는 Shared Pool의 최소 크기임을 명심해야 한다. 되도록이면 더 크게 잡아준다.
SELECT MAX(nSIZE) FROM temp_shared_pool_size;





힌트에대한설명

출처: devworld.co.kr
/*+ ALL_ROWS */
explicitly chooses the cost-based approach to optimize a statement
block with a goal of best throughput (that is, minimum
total resource consumption)
가장 좋은 단위 처리량의 목표로 문 블록을 최적화하기 위해 cost-based
접근 방법을 선택합니다. (즉, 전체적인 최소의 자원 소비)





/*+ CHOOSE */
causes the optimizer to choose between the rule-based
approach and the cost-based approach for a SQL statement
based on the presence of statistics for the tables accessed by
the statement
최적자(optimizer)가 그 문에 의해 접근된 테이블을 위해 통계의 존재에
근거를 두는 SQL 문을 위해 rule-based 접근 방법과 cot-based 접근 방법
사이에 선택하게 합니다.

/*+ FIRST_ROWS */
explicitly chooses the cost-based approach to optimize a statement
block with a goal of best response time (minimum
resource usage to return first row)
가장 좋은 응답 시간의 목표로 문 블록을 최적화하기 위해 cost-based 접근
방법을 선택합니다. (첫번째 행을 되돌려 주는 최소의 자원 사용)

/*+ RULE */
explicitly chooses rule-based optimization for a statement
block
문 블록을 위하여, rule-based 최적화를 고르는



/*+ AND_EQUAL(table index) */
explicitly chooses an execution plan that uses an access path
that merges the scans on several single-column indexes
그만큼 실행 계획을 선택합니다. 그리고 여럿의 single-column 색인에
그 scan을 합병하는 접근 경로를 사용합니다.

/*+ CLUSTER(table) */
explicitly chooses a cluster scan to access the specified table
선택합니다. 그리고, 클러스터는 그 명시된 테이블을 접근하기 위해 살핍니다.

/*+ FULL(table) */
explicitly chooses a full table scan for the specified table
그 명시된 테이블을 위하여, 전체 테이블 scan을 고르는

/*+ HASH(table) */
explicitly chooses a hash scan to access the specified table
선택합니다. 그리고, 해쉬는 그 명시된 테이블을 접근하기 위해 운율을 살핍니다.

/*+ HASH_AJ(table) */
transforms a NOT IN subquery into a hash antijoin to access
the specified table
변환, 그 명시된 테이블을 접근하는 해쉬 antijoin으로의 NOT IN 부속 조회

/*+ HASH_SJ (table) */
transforms a NOT IN subquery into a hash anti-join to access
the specified table
변환, 그 명시된 테이블을 접근하는 해쉬 anti-join으로의 NOT IN 부속 조회

/*+ INDEX(table index) */
explicitly chooses an index scan for the specified table
그 명시된 테이블을 위하여, 색인 scan을 고르는

/*+ INDEX_ASC(table index) */
explicitly chooses an ascending-range index scan for the specified
table
그 명시된 테이블을 위하여, ascending-range 색인 scan을 고르는

/*+ INDEX_COMBINE(table index) */
If no indexes are given as arguments for the INDEX_COMBINE
hint, the optimizer uses whatever Boolean combination
of bitmap indexes has the best cost estimate. If particular
indexes are given as arguments, the optimizer tries to use
some Boolean combination of those particular bitmap indexes.
어떤 색인도 INDEX_COMBINE 암시를 위해 인수로서 주어지지 않는다면,
bitmap 색인의 결합이 어떤 부울의를 가장 좋은 수행 난이도 평가를 가지고
있든지 최적자는 이용합니다.
특별한 색인이 인수로서 주어진다면, 최적자는 그 특별한 bitmap 색인의
몇몇의 부울의 결합을 사용하려고 노력합니다.

/*+ INDEX_DESC(table index) */
explicitly chooses a descending-range index scan for the specified
table
그 명시된 테이블을 위하여, descending-range 색인 scan을 고르는

/*+ INDEX_FFS(table index) */
causes a fast full index scan to be performed rather than a full
table scan
빠른 전체 색인 scan이 전체 테이블 scan이라기보다는 수행되게 합니다.

/*+ MERGE_AJ (table) */
transforms a NOT IN subquery into a merge anti-join to access
the specified table
변환, NOT IN 부속 조회, 그 명시된 테이블을 접근하기 위해 anti-join을
합병합니다.

/*+ MERGE_SJ (table) */
transforms a correlated EXISTS subquery into a merge semi-join
to access the specified table
변환, 관련된 EXISTS 부속 조회, 접근으로 semi-join을 합병합니다,
그 명시된 테이블

/*+ ROWID(table) */
explicitly chooses a table scan by ROWID for the specified
table
그 명시된 테이블을 위하여, ROWID에 의해 테이블 scan을 고르는

/*+ USE_CONCAT */
forces combined OR conditions in the WHERE clause of a
query to be transformed into a compound query using the
UNION ALL set operator
힘은 질의의 WHERE 문절에 있는 UNION ALL 집합 연산자를 사용하는 합성의
질의로 변형되는 OR 조건을 합쳤습니다.



/*+ ORDERED */
causes Oracle to join tables in the order in which they appear
in the FROM clause
오라클이 어느 것에 순서로 테이블을 결합시키게 합니다.

/*+ STAR */
forces the large table to be joined last using a nested-loops join
on the index
큰 있는 테이블이 최종 사용/회전율에 nested-loops를 결합시킨 힘은
그 색인에 결합합니다.



/*+ DRIVING_SITE (table) */
forces query execution to be done at a different site from that
selected by Oracle
힘은 그것과 다른 오라클에 의해 선택된 사이트에 되는 실행을 질의합니다.

/*+ USE_HASH (table) */
causes Oracle to join each specified table with another row
source with a hash join
오라클이 테이블이 다른 행 자원으로 해쉬 접합으로 명시되면서 각자와
합치게 합니다.

/*+ USE_MERGE (table) */
causes Oracle to join each specified table with another row
source with a sort-merge join
오라클이 테이블이 다른 행 자원으로 sort-merge 접합으로 명시되면서 각자와
합치게 합니다.

/*+ USE_NL (table) */
causes Oracle to join each specified table to another row
source with a nested-loops join using the specified table as the
inner table
오라클이 그 명시된 테이블을 그 안의 테이블로 사용하는 nested-loops 접합과
각자와 다른 행 자원에 대한 명시된 테이블을 합치게 합니다.



/*+ APPEND */ , /*+ NOAPPEND */
specifies that data is simply appended (or not) to a table; existing
free space is not used. Use these hints only following the
INSERT keyword.
데이타가 테이블로 단순히 덧붙여진다는 (or not)것 명시합니다; 무료인
현존하는 영역은 사용되지 않습니다.
단지 그 삽입 키 핵심어를 따르는 이 암시를 사용하시오.

/*+ NOPARALLEL(table) */
disables parallel scanning of a table, even if the table was created
with a PARALLEL clause
그 테이블이 PARALLEL 문절로 새로 만들어졌다면 테이블의 평행의 순차 검색을
무능하게 만듭니다.

/*+ PARALLEL(table, instances) */
allows you to specify the desired number of concurrent slave
processes that can be used for the operation.
DELETE, INSERT, and UPDATE operations are considered for
parallelization only if the session is in a PARALLEL DML
enabled mode. (Use ALTER SESSION PARALLEL DML to
enter this mode.)
당신이 그 연산을 위해 사용될 수 있는 동시의 슬레이브(slave) 프로세스의
요구된 수를 명시하는 것을 허락합니다.
그 세션이 가능하게 된 PARALLEL DML에 모드를 있다면, DELETE, INSERT, UPDATE
연산은 단지 parallelization에 대해 고려됩니다. (사용은 이 모드에 들어가기
위해 평행의 세션 DML을 변경합니다.)

/*+ PARALLEL_INDEX
allows you to parallelize fast full index scan for partitioned
and nonpartitioned indexes that have the PARALLEL attribute
parallelize에 당신에게 빠른 가득한 색인 scan을 허락합니다. 그런데,
그것은 PARALLEL 속성을 가지고 있는 색인을 분할했고 nonpartitioned했습니다.

/*+ NOPARALLEL_INDEX */
overrides a PARALLEL attribute setting on an index
병렬이 색인을 나아가는 것을 속하게 하는 대체


/*+ CACHE */
specifies that the blocks retrieved for the table in the hint are
placed at the most recently used end of the LRU list in the
buffer cache when a full table scan is performed
그 블록이 찾아서 가져왔다는 것을 명시합니다. 그리고 그 테이블을 위해
그 암시에 놓여집니다. 그런데, 그것은 가장 요즈음 사용된 언제 그 버퍼 캐쉬,
가득한 테이블 scan에 있는 LRU 리스트의 끝입니다. 수행됩니다.

/*+ NOCACHE */
specifies that the blocks retrieved for this table are placed at
the least recently used end of the LRU list in the buffer cache
when a full table scan is performed
그 명시합니다. 그리고, 그 블록은 이 테이블을 위해 검색되면서 요즈음 사용된
언제 그 버퍼 캐쉬, 가득한 테이블 scan에 있는 LRU 리스트의 가장 작은 끝에
놓여집니다. 수행됩니다.

/*+ MERGE (table) */
causes Oracle to evaluate complex views or subqueries before
the surrounding query
오라클이 그 둘러싸는 질의 전에 복잡한 뷰나 부속 조회를 평가하게 합니다.

/*+ NO_MERGE (table) */
causes Oracle not to merge mergeable views
오라클이 mergeable 뷰를 합병하지 않게 하지 않습니다

/*+ PUSH_JOIN_PRED (table) */
causes the optimizer to evaluate, on a cost basis, whether or
not to push individual join predicates into the view
개개 접합을 미는 것이 그 뷰 안으로 단정 하든 간에 비용 방식으로 최적자가
평가하게 합니다.

/*+ NO_PUSH_JOIN_PRED (table) */
Prevents pushing of a join predicate into the view
접합 술부 중에서 그 뷰로 밀면서, 막는

/*+ PUSH_SUBQ */
causes nonmerged subqueries to be evaluated at the earliest
possible place in the execution plan
원인은 그 실행 계획에서의 가장 이른 가능한 장소에 평가되는 부속 조회를
nonmerged했습니다.

/*+ STAR_TRANSFORMATION */
makes the optimizer use the best plan in which the transformation
has been used.
최적자가 그 변형이 사용된 가장 좋은 계획을 사용하는 제작

객체의사용

===========
객체의 사용
===========

일단 객체에 대한 개념이 있다는 전제하에 시작하겠습니다...
만일 객체에 대한 개념이 없으시다면 다른 웹문서나 서적을 참고하시기 바랍니다..

오라클은 객체-관계형 데이타베이스 관리 시스템입니다..
즉, 객체와 관계형 모델을 모두 지원해서 사용한다는 뜻입니다..
물론 기준은 관계형 모델입니다...
객체를 관계형 모델에서 사용하는 것처럼 사용합니다...
즉, 객체지향의 중심개념인 캡슐화, 상속성, 다형성을 제공한다는 것입니다..
객체형으로 선언하면 그 객체는 메소드와, 속성을 가질 수 있습니다..
오브젝트 뷰라는 것도 있는데...이것은 오라클 관리 부분에서 다룹니다..그쪽을 참고하시구여..
개념만 이야기 하자면 테이블에서 뷰를 만들고 이를 객체형으로 만든다는 것입니다..
일단 객체를 정의하는 것부터 살펴보겠습니다..
다시한번 말씀드리지만 이 부분은 객체에 대한 개념이 없으시다면 읽어도 무슨 소리인지 모를겁니다..
위에서 말한것 처럼 객체의 개념을 익히시고 이 문서를 보시면 좋겠습니다..
C++이나 다른 객체지향형의 언어나 툴을 다루셨다면 이제까지 데이타베이스를 하면서 느끼지 못한 즐거움을 
느끼실 겁니다..물론 객체형 데이타베이스를 하신분은 빼구여...헐헐~

바로 실습에 들어가지여..언급한데로 PL/SQL의 예제는 (주)사이버출판사의 PL/SQL임돠...
문법은 메뉴얼을 참고하시구여...
쩝...맨날 메뉴얼 참고하라는데..메뉴얼이 있어야 어디서 보지...라구 외치시는 분덜은 제 자료실을 찾아보세요..
제가 주로 참고하는 문서라고 있습니다..

자..그러면 객체-관계형 DBMS인 오라클이 왜 객체-관계형이라고 하는지 실제로 해보면서 살펴봅시다..
책에는 처음에 하기엔 너무 길고 어려운 예들이 많슴다...헐헐~
이것은 나중에 하구여...제가 만들어서 해보겠슴다..
객체에 대한 개념이 있다는 가정하에 밑에 것을 해보시게 된다면 금방 이해가 될 것이라 확신함돠..
혹시나 객체에 대한 개념이 없으신 분덜이 아래 예를 해보신다면 객체가 먼지를 알지도 모르겠슴다.. 

SQL> create type test_object as object(
  2  id varchar(4),
  3  name varchar(10));
  4  /

유형이 생성되었습니다.

SQL> create table test_object_table of test_object;       -------------> 객체를 테이블화 했슴다...

테이블이 생성되었습니다.

SQL> insert into test_object_table
  2  values (test_object('1111', 'scott'));               ---------------> 관계형 처럼 입력이 되는 것이 보입니다..

1 개의 행이 만들어졌습니다.

SQL> insert into test_object_table
  2  values (test_object('2222', 'yasi'));

1 개의 행이 만들어졌습니다.

SQL> select * from test_object_table;                 --------------> 객체타입을 사용해서 만든 테이블이 관계형에서와 똑같이 조회됩니다..
                                                                      이것이 바로 오라클을 객체-관계형 DBMS 라고 부르는 이유임돠..
ID   NAME
---- ----------
1111 scott
2222 yasi

SQL> create table test_object_table2
  2  ( id_name test_object,                        --------------> test_object는 앞에서 생성한 객체타입입니다...재사용성을 보여주고 있습니다..
  3    group_id varchar2(10));                                     

테이블이 생성되었습니다.

SQL> select * from test_object_table2;

선택된 레코드가 없습니다.

SQL> insert into test_object_table2
  2  values (test_object('1111', 'system'), 'sysoper');

1 개의 행이 만들어졌습니다.

SQL> insert into test_object_table2
  2  values (test_object('2222', 'yasi'), 'users');

1 개의 행이 만들어졌습니다.

SQL> select * from test_object_table2;

ID_NAME(ID, NAME)                        GROUP_ID
---------------------------------------- ----------
TEST_OBJECT('1111', 'system')            sysoper
TEST_OBJECT('2222', 'yasi')              users

SQL> select t.id_name.id as id,                   -----------> 객체지향 언어에서 사용하는 점(.) 표시법으로 조회하고 있씀다...
  2         t.id_name.name as name,                            재밌슴다...^^
  3         t.group_id as group_id
  4  from test_object_table2 t;

ID   NAME       GROUP_ID
---- ---------- ----------
1111 system     sysoper
2222 yasi       users

SQL> create or replace type test_object2 as object(
  2  id_name test_object,                             ----------> 앞에서 생성한 객체의 특성을 그대로 사용해서 또 다른 객체를 생성함돠....
  3  address varchar2(40));                                       
  4  /

유형이 생성되었습니다.


헐~ 상당히 재밌씀다...저는 이것을 익히면서 너무나도 재미를 느꼈었슴다...
이것을 보시는 분덜도 그러리라 생각합니다...헐헐~
이제 감을 잡으셨다고 생각하고 복잡한 예제를 보겠슴다..

SQL> CREATE OR REPLACE TYPE address AS OBJECT (
  2    street_1      VARCHAR2(40),
  3    street_2      VARCHAR2(40),
  4    city          VARCHAR2(40),
  5    state_abbr    VARCHAR2(2),
  6    zip_code      VARCHAR2(5),
  7    phone_number  VARCHAR2(10),
  8    MEMBER PROCEDURE ChangeAddress (                      -------------------> 메소드까정 생성
  9      st_1 IN VARCHAR2, st_2 IN VARCHAR2, cty IN VARCHAR2,
 10      state IN VARCHAR2, zip IN VARCHAR2),
 11    MEMBER FUNCTION getStreet (line_no IN number) RETURN VARCHAR2,
 12    MEMBER FUNCTION getCity RETURN VARCHAR2,
 13    MEMBER FUNCTION getStateAbbr RETURN VARCHAR2,
 14    MEMBER FUNCTION getPostalCode RETURN VARCHAR2,
 15    MEMBER FUNCTION getPhone RETURN VARCHAR2,
 16    MEMBER PROCEDURE setPhone (newPhone IN VARCHAR2)
 17  );
 18  /

유형이 생성되었습니다.


address라는 객체타입을 생성했습니다...
프로시저와 함수를 선언했지여...프로시저는 리턴값이 없는 것을 야그하고, 함수는 리턴값이 있는 것을 말합니다...
이러한 메소드를 생성하는 이유는 생성한 객체타입에 대한 조작을 하기 위한 것이지여...
패키지 생성하는 것처럼 몸체도 생성해 주어야 합니다...
컬럼 비스무리하게 선언한 것은 속성임돠...오라클의 객체타입은 속성을 1000개까정 가질수 있슴다..
그럼 몸체를 생성해 보겠슴다..

SQL> CREATE OR REPLACE TYPE address AS OBJECT (
  2    street_1      VARCHAR2(40),
  3    street_2      VARCHAR2(40),
  4    city          VARCHAR2(40),
  5    state_abbr    VARCHAR2(2),
  6    zip_code      VARCHAR2(5),
  7    phone_number  VARCHAR2(10),
  8    MEMBER PROCEDURE ChangeAddress (
  9      st_1 IN VARCHAR2, st_2 IN VARCHAR2, cty IN VARCHAR2,
 10      state IN VARCHAR2, zip IN VARCHAR2),
 11    MEMBER FUNCTION getStreet (line_no IN number) RETURN VARCHAR2,
 12    MEMBER FUNCTION getCity RETURN VARCHAR2,
 13    MEMBER FUNCTION getStateAbbr RETURN VARCHAR2,
 14    MEMBER FUNCTION getPostalCode RETURN VARCHAR2,
 15    MEMBER FUNCTION getPhone RETURN VARCHAR2,
 16    MEMBER PROCEDURE setPhone (newPhone IN VARCHAR2)
 17  );
 18  /

유형이 생성되었습니다.

SQL> CREATE OR REPLACE TYPE BODY address AS
  2    MEMBER PROCEDURE ChangeAddress (
  3      st_1 IN VARCHAR2, st_2 IN VARCHAR2, cty IN VARCHAR2,
  4      state IN VARCHAR2, zip IN VARCHAR2) IS
  5    BEGIN
  6      IF (st_1 IS NULL) OR (cty IS NULL) OR
  7         (state IS NULL) OR (zip IS NULL)
  8         OR (upper(state) NOT IN ('AK','AL','AR','AZ','CA','CO',
  9                                  'CT','DC','DE','FL','GA','HI',
 10                                  'IA','ID','IL','IN','KS','KY',
 11                                  'LA','MA','MD','ME','MI','MN',
 12                                  'MO','MS','MT','NC','ND','NE',
 13                                  'NH','NJ','NM','NV','NY','OH',
 14                                  'OK','OR','PA','RI','SC','SD',
 15                                  'TN','TX','UT','VA','VT','WA',
 16                                  'WI','WV','WY'))
 17  OR (zip <> ltrim(to_char(to_number(zip),'09999'))) THEN
 18       RAISE_application_error(-20001,'The new Address is invalid.');
 19      ELSE
 20        street_1 := st_1;
 21        street_2 := st_2;
 22        city := cty;
 23        state_abbr := upper(state);
 24        zip_code := zip;
 25      END IF;
 26    END;
 27  
 28    MEMBER FUNCTION getStreet (line_no IN number)
 29      RETURN VARCHAR2 IS
 30    BEGIN
 31      IF line_no = 1 THEN
 32        RETURN street_1;
 33      ELSIF line_no = 2 THEN
 34        RETURN street_2;
 35      ELSE
 36        RETURN ' ';    --send back a blank.
 37      END IF;
 38    END;
 39  
 40    MEMBER FUNCTION getCity RETURN VARCHAR2 IS
 41    BEGIN
 42      RETURN city;
 43    END;
 44  
 45    MEMBER FUNCTION getStateAbbr RETURN VARCHAR2 IS
 46    BEGIN
 47      RETURN state_abbr;
 48    END;
 49  
 50    MEMBER FUNCTION getPostalCode RETURN VARCHAR2 IS
 51    BEGIN
 52      RETURN zip_code;
 53    END;
 54  
 55    MEMBER FUNCTION getPhone RETURN VARCHAR2 IS
 56    BEGIN
 57      RETURN phone_number;
 58    END;
 59  
 60    MEMBER PROCEDURE setPhone (newPhone IN VARCHAR2) IS
 61    BEGIN
 62      phone_number := newPhone;
 63    END;
 64  END;
 65  /

유형 본문이 생성되었습니다.


헐헐~ 상당히 길지만....패턴은 비슷함돠...
MEMBER PROCEDURE ChangeAddress 는 속성에 대한 변경을 하려고 만든 프로시저입니다...
속성 각각에 입력되거나 갱신할때 데이타의 무결성을 유지하기 위한 제어문이 들어갔슴다..
각각의 멤버 함수는 값을 리턴하기 위해서 생성된 것들입니다...

오라클의 객체타입에는 생성자 메소드라는 것이 내장되어 있습니다...
이것은 그 객체형의 인스턴스를 생성하는데 사용됩니다..
그 생성자 메소드는 다음과 같은 식이지여..

FUNCTION address(street_1 in varchar2, street_2 in varchar2, city in varchar2, state_abbr,.............) returns address

이런 생성자 메소드는 항상 같은 형의 객체를 반환합니다...
생성자 함수의 사용은 다음과 같이 하지여..

address_variable := address('101 Oak', '', 'Detroit', 'MI', '48223', '3135358886');


함수의 몸체에서 다음을 살펴봅시다..

MEMBER FUNCTION getPhone RETURN VARCHAR2 IS
    BEGIN
      RETURN phone_number;
    END;

속성을 반환하는데 사용되는 메소드입니다...이 메소드를 Accessor Method라 합니다...
일반적으로 get이란 접두어를 사용합니다..

그 밑에 있는 setPhone은 mutator Method라는 것입니다...코드상에서 보는 것과 같이 속성을 직접 참조하지 않습니다..
즉, 속성을 직접 건더리지 않고 간접적으로 건더리는 것입니다...   

MEMBER PROCEDURE setPhone (newPhone IN VARCHAR2) IS
    BEGIN
      phone_number := newPhone;
    END;    
    
자...그럼 이러한 메소드를 사용해서 객체타입의 인스턴스를 생성하고, 조회해 보겠슴다..

SQL> set serveroutput on
SQL> 
SQL> --A PL/SQL block demonstrating the
SQL> --use of the address object.
SQL> DECLARE
  2    address_1   address;
  3    address_2   address;
  4    address_3   address;
  5  BEGIN
  6    --Instantiate a new address object named address_1,
  7    --and assign a copy of it to address_2.
  8    address_1 := address ('2700 Peerless Road','Apt 1',              -------------> address는 생성자 함수지여? address_1이 새로운 인스턴스가 됩니다..
  9                          'Cleveland','TN','37312','4235551212');
 10    address_2 := address_1;
 11  
 12    --Change address #1
 13    address_1.ChangeAddress ('2800 Peermore Road','Apt 99',
 14                             'Detroit','MI','48823');                 --------------> ChangeAddress 프로시저를 사용해서 인스턴스를 갱신함돠..
 15  
 16    --Instantiate a second object.
 17    address_3 := address ('2700 Eaton Rapids Road','Lot 98',
 18                          'Lansing','MI','48911','5173943551');
 19  
 20    --Now print out the attributes from each object.
 21    dbms_output.put_line('Attributes for address_1:');           -------------> 여기부터는 생성한 인스턴스를 이쁘게 보여주기 위한 짓임돠..
 22    dbms_output.put_line(address_1.getStreet(1));                -------------> 객체형으로 생성해서 엑서스 메소드를 사용합니다..객체의 사용처럼 점표시법으로...
 23    dbms_output.put_line(address_1.getStreet(2));
 24    dbms_output.put_line(address_1.getCity
 25                         || ' ' || address_1.getStateAbbr
 26                         || ' ' || address_1.getPostalCode);
 27    dbms_output.put_line(address_1.getPhone);
 28  
 29    dbms_output.put_line('-------------------------');
 30    dbms_output.put_line('Attributes for address_2:');
 31    dbms_output.put_line(address_2.getStreet(1));
 32    dbms_output.put_line(address_2.getStreet(2));
 33    dbms_output.put_line(address_2.getCity
 34                         || ' ' || address_2.getStateAbbr
 35                         || ' ' || address_2.getPostalCode);
 36    dbms_output.put_line(address_2.getPhone);
 37  
 38    dbms_output.put_line('-------------------------');
 39    dbms_output.put_line('Attributes for address_3:');
 40    dbms_output.put_line(address_3.street_1);
 41    dbms_output.put_line(address_3.street_2);
 42    dbms_output.put_line(address_3.city
 43                         || ' ' || address_3.state_abbr
 44                         || ' ' || address_3.zip_code);
 45    dbms_output.put_line(address_3.phone_number);
 46  END;
 47  /
Attributes for address_1:
2800 Peermore Road
Apt 99
Detroit MI 48823
4235551212
-------------------------
Attributes for address_2:
2700 Peerless Road
Apt 1
Cleveland TN 37312
4235551212
-------------------------
Attributes for address_3:
2700 Eaton Rapids Road
Lot 98
Lansing MI 48911
5173943551

PL/SQL 처리가 정상적으로 완료되었습니다.

노가다입니다...헐헐~ 이상스럽게 이러한 예제는 비효율적이라고 생각합니다...
웬지 그런 느낌이 드네여..헐헐~
암튼...
여기까지 했으면 감이 잡혔지여??

다른 것을 살펴보구 갱신하는 짓을 해봅시다...

SQL> CREATE TABLE employee
  2         (emp_id           INTEGER,
  3           emp_name      VARCHAR2(32),
  4           supervised_by      INTEGER,
  5           pay_rate               NUMBER(9,2),
  6           pay_type              CHAR);

테이블이 생성되었습니다.

SQL> alter table employee
  2  add (home_address address);      ------------------> 객체형으로 정의된 컬럼을 추가합니다.

테이블이 변경되었습니다.

SQL> INSERT INTO employee
  2    (emp_id, emp_name,pay_rate,pay_type,home_address)
  3    VALUES (597,'Matthew Higgenbottom',120000,'S',
  4            address('101 Maple','','Mio','MI','48640','5173943551'));       --------> 데이타 삽입

1 개의 행이 만들어졌습니다.

SQL> 
SQL> COMMIT;

커밋이 완료되었습니다.

SQL> 
SQL> DECLARE
  2    emp_home_address    address;           ---------------------> 객체형으로 변수 선언
  3  BEGIN
  4    emp_home_address := address('911 Pearl','Apt 2','Lewiston',
  5                                'MI','48645','5173363366');
  6    INSERT INTO employee
  7      (emp_id, emp_name,pay_rate,pay_type,home_address)
  8      VALUES (598, 'Raymond Gennick',55,'H',emp_home_address);
  9    COMMIT;
 10  END;
 11  /

PL/SQL 처리가 정상적으로 완료되었습니다.

SQL> SELECT emp_id, emp_name, home_address
  2    FROM employee
  3   WHERE home_address IS NOT null;

    EMP_ID EMP_NAME                         HOME_ADDRESS(STREET_1, STREET_2, CITY, STATE_ABBR, ZIP_CODE, PHONE_NUMBER)
---------- -------------------------------- --------------------------------------------------------------------------------
       597 Matthew Higgenbottom             ADDRESS('101 Maple', NULL, 'Mio', 'MI', '48640', '5173943551')
       598 Raymond Gennick                  ADDRESS('911 Pearl', 'Apt 2', 'Lewiston', 'MI', '48645', '5173363366')
       

설명을 안해도 될꺼 같습니다...
이제 갱신하는 것을 해보겠습니다...기본적으로 PL/SQL 어케돌아가는지 아신다면 쉽습니다...

SQL> DECLARE
  2    emp_addr    address;
  3  BEGIN
  4    --Retrieve the object from the table
  5    SELECT home_address INTO emp_addr
  6      FROM employee
  7     WHERE emp_id = 597;
  8  
  9    --Use a mutator method to change the phone number.
 10    emp_addr.setPhone('3139830301');               --------------> 뮤테이터 메소드를 사용해서 갱신함돠..
 11  
 12    UPDATE employee
 13       SET home_address = emp_addr
 14     WHERE emp_id = 597;
 15  
 16    COMMIT;
 17  END;
 18  /

PL/SQL 처리가 정상적으로 완료되었습니다.

SQL> SELECT emp_id, emp_name, e.home_address.phone_number home_phone
  2    FROM employee e
  3   WHERE emp_id = 597;

    EMP_ID EMP_NAME                         HOME_PHONE
---------- -------------------------------- ----------
       597 Matthew Higgenbottom             3139830301
     
SQL> UPDATE employee e
  2     SET e.home_address  = address(e.home_address.street_1,
  3                         e.home_address.street_2, e.home_address.city,
  4                         e.home_address.state_abbr, e.home_address.zip_code,
  5                         '5173433333')
  6   WHERE emp_id = 598;

1 행이 갱신되었습니다.

SQL> COMMIT;

커밋이 완료되었습니다.


마지막에서 살펴본 갱신문도....처음에 해본것들과 비슷합니다...점표시법을 사용해서 잘 구분해주고...
각각을 조회, 갱신, 입력, 삭제할 수 있습니다...
그러면 오라클은 어떻게 객체를 사용하는데 각각을 구분하는 방법을 생각해봅시다...
구분하는 방법은 각 테이블의 컬럼으로 정의된 객체의 각 속성에 일치합니다... 따라서 테이블의 각 행은
객체의 한 인스턴스를 저장하는데 사용됩니다...
이렇게 구분하는 방법말구...오라클은 Object idenfifier란 오라클의 생성하는 값으로 구분을 합니다..
이 값은 각각 생성한 객체를 데이타베이스 전체에서 고유하게 구별하는 것 입니다...

다음은 객체형 테이블을 만드는 예를 살펴보겠슴다..

SQL> CREATE OR REPLACE TYPE building AS OBJECT (
  2    BldgName          VARCHAR2(40),
  3    BldgAddress       address,
  4    BldgMgr           INTEGER,
  5    MEMBER PROCEDURE  ChangeMgr (NewMgr IN INTEGER),
  6    ORDER MEMBER FUNCTION Compare (OtherBuilding IN building)      ---------> ORDER 형 멤버 함수를 사용
  7        RETURN INTEGER
  8    );
  9  /

유형이 생성되었습니다.


여기서 봐야 될것은  ORDER 입니다...
이제까지 했던 다른 것들하고 먼가가 틀립니다...
ORDER 함수는 두 객체를 비교하는 코드를 작성할 수 있게 해줍니다..
두 객체가 같은지 아니면 어떤 것이 더 큰지 또는 작은지 가리키는 값을 반환합니다..
나중에 더 살펴보구여...일단 이런것이라는 것만 알고 계시면 되리라 생각합니다..

SQL> CREATE OR REPLACE TYPE BODY building AS
  2    MEMBER PROCEDURE  ChangeMgr(NewMgr IN INTEGER) IS
  3      BEGIN
  4        BldgMgr := NewMgr;
  5      END;
  6  
  7    ORDER MEMBER FUNCTION Compare (OtherBuilding IN building)
  8    RETURN INTEGER IS
  9        BldgName1     VARCHAR2(40);
 10        BldgName2     building.BldgName%TYPE;            ------------> '변수이름 테이블이름.컬럼이름%TYPE' 의 형식을 같습니다..
 11      BEGIN
 12        --Grab the two building names for comparison.
 13        --Make sure that we don't get messed up by leading/trailing
 14        --spaces or by case.
 15        BldgName1 := upper(ltrim(rtrim(BldgName)));         ---------------> 앞뒤 공백을 모두 없애고 대문자로 치환해서 BldgName1에 넣구 있슴다..
 16        BldgName2 := upper(ltrim(rtrim(OtherBuilding.BldgName)));
 17  
 18        --Return the appropriate value to indicate the order of
 19        --this object vs OtherBuilding.
 20        IF BldgName1 = BldgName2 THEN                -----------------> 두 값의 비교를 위한 제어문입니다..
 21          RETURN 0;
 22        ELSIF BldgName1 < BldgName2 THEN
 23          RETURN -1;
 24        ELSE
 25          RETURN 1;
 26        END IF;
 27      END;
 28  END;
 29  /

유형 본문이 생성되었습니다.

SQL> CREATE TABLE buildings OF building;         ---------> 객체형 테이블 생성

테이블이 생성되었습니다.

SQL> INSERT INTO buildings
  2    values (building('Victor Building',
  3            address('203 Washington Square',' ','Lansing',
  4                    'MI','48823',' '),
  5            597));

1 개의 행이 만들어졌습니다.

SQL> 
SQL> INSERT INTO buildings
  2    values (building('East Storage Shed',
  3            address('1400 Abbott Rd','','Lansing','MI','48823',''),
  4            598));

1 개의 행이 만들어졌습니다.

SQL> INSERT INTO buildings
  2    values (building('Headquarters Building',
  3            address('150 West Jefferson','','Detroit','MI','48226',''),
  4            599));

1 개의 행이 만들어졌습니다.

SQL> 
SQL> SELECT * from buildings;

BLDGNAME                                 BLDGADDRESS(STREET_1, STREET_2, CITY, STATE_ABBR, ZIP_CODE, PHONE_NUMBER) BLDGMGR
---------------------------------------- ------------------------------------------------------------------------- ----------
Victor Building                          ADDRESS('203 Washington Square', ' ', 'Lansing', 'MI', '48823', ' ')       597
East Storage Shed                        ADDRESS('1400 Abbott Rd', NULL, 'Lansing', 'MI', '48823', NULL)            598
Headquarters Building                    ADDRESS('150 West Jefferson', NULL, 'Detroit', 'MI', '48226', NULL)        599

SQL> COMMIT;

커밋이 완료되었습니다. 


다음은 객체형 테이블에 대한 검색을 하는 또다른 예를 보겠습니다...
객체형 테이블을 검색하는 또다른 방법은 VALUE라는 연산자를 사용하는 것입니다..
SQL> select value(e) from buildings e;

VALUE(E)(BLDGNAME, BLDGADDRESS(STREET_1, STREET_2, CITY, STATE_ABBR, ZIP_CODE, PHONE_NUMBER), BLDGMG
----------------------------------------------------------------------------------------------------
BUILDING('Victor Building', ADDRESS('203 Washington Square', ' ', 'Lansing', 'MI', '48823', ' '), 59
BUILDING('East Storage Shed', ADDRESS('1400 Abbott Rd', NULL, 'Lansing', 'MI', '48823', NULL), 598)
BUILDING('Headquarters Building', ADDRESS('150 West Jefferson', NULL, 'Detroit', 'MI', '48226', NULL

SQL> desc building
 이름                                                                                                              널?      유형
 ---------------------------------------------------------------------------------------------------
 BLDGNAME                                                                                                                   VARCHAR2(40)
 BLDGADDRESS                                                                                                                ADDRESS
 BLDGMGR                                                                                                                    NUMBER(38)

METHOD
------
 MEMBER PROCEDURE CHANGEMGR
 인수명                         유형                    기본 내부/외부?
 ------------------------------ ----------------------- --------- --------
 NEWMGR                         NUMBER                  IN

METHOD
------
 ORDER MEMBER FUNCTION COMPARE RETURNS NUMBER
 인수명                         유형                    기본 내부/외부?
 ------------------------------ ----------------------- --------- --------
 OTHERBUILDING                  BUILDING                IN

SQL> 


다음은 이러한 객체형의 문제점을 살펴보구 오라클이 이 문제점을 해결하는 방안을 살펴보겠습니다...
예를 들면, 어떤 빌딩에 1000명의 사원이 있다면, 각 사원대 해새 하나씩 1000개의 독립된 building 객체가 있게 됩니다..
결국 너무 많은 정보를 갖게 되는 것이죠...
이런한 것을 해결하기 위해 오라클은 오브젝트에 대한 참조만 저장합니다..참조의 생성은 REF를 사용합니다..
즉, 객체를 사용하는 것이 아니라 참조만 한다는 뜻입니다..
오라클 관리 부분에도 언급되었던 것이죠..
참조를 생성해 봅시다...

SQL> ALTER TABLE employee
  2    ADD (emp_bldg    REF building);

테이블이 변경되었습니다.

예에서 보면 building 객체를 사용하는 것이 아니라 참조합니다...즉, 오라클은 객체 식별자를 처리하는데 REF와 DEREF 연산자를
사용한다는 것입니다..
REF 연산자는 오브젝트에 대한 오브젝트 식별자를 반환하는 SQL문에서 사용되고,
DEREF 연산자는 오브젝트 식별자에 의해 참조되는 실제 오브젝트를 검색하는 SQL문에서 사용합니다.

REF연산자와 DEREF 연산자를 사용하는 예를 연속으로 보겠씁니다..

SQL> CREATE OR REPLACE PROCEDURE AssignEmpToBldg (
  2    EmpNumIn IN employee.emp_id%TYPE,
  3    BldgNameIn IN buildings.BldgName%TYPE
  4    ) AS
  5  BEGIN
  6    UPDATE employee
  7       SET emp_bldg = (SELECT REF(b)             -------------> REF연산자 사용, buildings 테이블에서 사용한 객체에 대한 참조만 한다.
  8                        FROM buildings B
  9                       WHERE BldgName = BldgNameIn)
 10     WHERE emp_id = EmpNumIn;
 11  
 12    --Raise an error if either the employee number or
 13    --building name is invalid.
 14    IF SQL%NOTFOUND THEN
 15      RAISE_application_error(-20000,'Employee ' || EmpNumIn
 16                             || ' could not be assigned to building '
 17                             || BldgNameIn);
 18    END IF;
 19  END;
 20  /

프로시저가 생성되었습니다.

SQL> 
SQL> BEGIN
  2    AssignEmpToBldg (598,'Victor Building');
  3    AssignEmpToBldg (597,'East Storage Shed');
  4  END;
  5  /

PL/SQL 처리가 정상적으로 완료되었습니다.

SQL> CREATE OR REPLACE FUNCTION GetEmpBldgName (
  2    EmpNumIn IN employee.emp_id%TYPE
  3    ) RETURN VARCHAR2 AS
  4  TheBldg     building;
  5  BEGIN
  6    --Select the building object reference from this employee's record.
  7    SELECT DEREF(emp_bldg) INTO TheBldg                   -------------------> 실제 객체에 대한 검색을 한다.즉, 참조를 해제한다.
  8      FROM employee
  9     WHERE emp_id = EmpNumIn;
 10  
 11    IF TheBldg IS NULL THEN
 12      RETURN 'No Building Assigned';
 13    ELSE
 14      RETURN TheBldg.BldgName;
 15    END IF;
 16  END;
 17  /

함수가 생성되었습니다.

SQL> 
SQL> BEGIN
  2    dbms_output.put_line(GetEmpBldgName(598));
  3    dbms_output.put_line(GetEmpBldgName(597));
  4  END;
  5  /
Victor Building
East Storage Shed

PL/SQL 처리가 정상적으로 완료되었습니다.

주의깊게 볼것은 어떤 것이 참조를 하는지 알아서 참조해제를 하는 것이냐? 입니다..
DEREF 연산자에 의해서 사용되는 오브젝트 참조에는 오라클이 객체를 찾는데 필요한 모든 정보가 
들어 있습니다..그래서 관계형 테이블에서 사용되는 조인문에서와 같이 엑세스할 테이블을 지정하지 
않아도 되는 것입니다..

참고로 메뉴얼의 문법을 보면 SELF 파라미터가 있는데...이것은 메소드를 작성시 기본값입니다.
호출되는 객체의 속성을 참조하는데 사용됩니다.

이제 객체타입에 대한 기나긴 장정의 마지막인 객체 비교하는 것을 해보구....제약사항을 알아 보겠습니다..
객체 비교에는 두가지 메소드를 지원합니다...
그 두가지는 앞에서 잠시 살펴본 ORDER와 MAP 메소드입니다..
먼저 ORDER에 대해서 살펴보게습니다..

앞에서 살펴본 예제에서 다음과 같은 구문이 있었습니다..

ORDER MEMBER FUNCTION Compare (OtherBuilding IN building) 
        RETURN INTEGER         

ORDER 예약어는 Compare 함수가 한 building 객체를 다른 building 객체와 비교할 때 호출되도록 특별히 작성된 
함수라는 것을 말해주는 것입니다..
함수가 취하는 인수는 오브젝트와 같은 형이어야 합니다..
ORDER함수는 SELF 파라미터에서 알아본 것처럼 비교하여 다음과 같은 값을 반환합니다..

반환값           의미
------------     ------------------
     -1          SELF가 인수보다 작다.
     0           SELF가 인수와 같다.
     1           SELF가 인수보다 크다.

다음은 예입니다..

SQL> set serveroutput on
SQL> 
SQL> --A demonstration of the ORDER function.
SQL> DECLARE
  2    bldg_a      building;   --will be less than bldg_b
  3    bldg_b      building;
  4    bldg_b2     building;
  5    bldg_c      building;
  6  BEGIN
  7    --First, create four building objects.
  8    bldg_a := building('A Building',null,null);
  9    bldg_b := building('Another Building',null,null);
 10    bldg_b2 := building('Another Building',null,null);
 11    bldg_c := building('Cosmotology Research Lab',null,null);
 12  
 13    --Now compare the building objects and display the results;
 14    IF bldg_a < bldg_b THEN
 15      dbms_output.put_line('bldg_a < bldg_b');
 16    END IF;
 17  
 18    --These two have the same name, so should be equal.
 19    IF bldg_b = bldg_b2 THEN
 20      dbms_output.put_line('bldg_b = bldg_b2');
 21    END IF;
 22  
 23    IF bldg_c > bldg_b2 THEN
 24      dbms_output.put_line('bldg_c > bldg_b2');
 25    END IF;
 26  END;
 27  /
bldg_a < bldg_b
bldg_b = bldg_b2
bldg_c > bldg_b2

PL/SQL 처리가 정상적으로 완료되었습니다.

MAP 메소드는 ORDER 함수대신 사용할 수 있는 다른 방법입니다..
MAP 함수는 객체의 하나 또는 그 이상의 속성에 따라 하나의 값을 계산합니다..그리고
그 값을 한 객체와 다른 객체(같은 객체형)를 비교하는데 사용합니다...
MAP함수의 결과는 다음 데이타형중 하나이어야 합니다..

- NUMBER
- DATE
- VARCHAR2

마지막으로 오라클의 객체타입에 대한 제약사항을 알보구 마치겠씁니다..

- 상속성을 지원하지 않는다.    --------------> 책에 나온 내용인데...이상함돠..제가 알기론 상속성을 지원하는데 말이죠..아무래도 완벽하지는 않은듯..
- PRIVATE 속성을 지원하지 않는다.
- 사용자 정의 생성자를 지원하지 않는다.
- 객체형은 데이타베이스 수준에서 정의되어야 하며, PL/SQL 함수나 프로시저 내에서 정의할 수 없다.
- 일부 데이타형은 속성으로 사용될 수 없다.
- 객체의 속성의 개수를 최대 1000개 이상 사용할 없다.
     



감사기능(Auditing)

Contents

1 개요
2 문장 감사
3 권한감사
4 객체 감사



1 개요 #

데이타베이스가 좀 크다 싶으면 관리자는 주기적인 모니터링이 필요합니다. 이러한 데이타베이스 관리 시스템에서 일어난 동작들을 모니터링하는 짓을 Auditing이라고 합니다. 감사의 종류는 문장감사, 권한 감사, 객체 감사 이렇게 3가지로 구분합니다. 이렇게 야그하면 먼지 감은 잡히는데 어케해야하는지 도저히 모르겠습니다. 하시는 분도 계실겁니다. 데이타베이스 시스템은 항상 변하고 있는데 모니터링해도 어디엔가 기록이 남아야 분석을 할 수 있을겁니다. 오라클은 그짓을 Audit Trail에 저장하게 됩니다. 중요한것은 감사의 방법이 아니라 감사의 목적에 맞는 제대로된 감사를 하는 것입니다. 의심스러운 사용자의 액션을 추적하고, 감사 추적 데이타를 보호해야 합니다. 무작정 감사 추적만 한다고 되는 것은 아니지여. 물론 데이타에 대한 감사는 트리거로 하고 또한 필요한 조치를 취하게 됩니다.


감사도 역시 활성화/비활성화 됩니다. 무작정 감사를 한다면 시스템에 또다른 오버헤드로 작용하겠지여. 그러나 무작정 하는 감사도 있습니다. 그 무작정 감사는 오라클 인스턴스의 startup/shutdown, 데이타베이스 관리자 권한을 가진 사용자가 접속할때 입니다. 오라클은 파라미터 파일에 AUDIT_TRAIL 값을 명시함으로써 감사를 활성화 시킬수 있습니다.

AUDIT_TRAIL = DB | OS | NONE

3가지 값이 들어갈수가 있습니다. NONE은 파라미터의 기본값이구여...DB 파라미터값으로 설정하면 감사 추적데이타는 sys.AUD$ 데이타 사전뷰를 통해 볼수가 있습니다. OS 파라미터값으로 설정하면 추적 파일이 운영체제상에 저장됩니다. 역시 해당세션에서도 감사의 활성화/비활성화가 가능합니다. 파라미터 파일에 감사의 활성화라는 명시가 있을 때만입니다. 세션이 끝나면 감사도 끝이라는 뜻이지여. AUDIT 와 NOAUDIT 문으로 활성화/비활성화 시킵니다. 이 문장을 수행하려면 AUDIT SYSTEM 권한이 있어야 하구여.

일단 감사를 하려면 관리자를 위한 스크립트하나를 실행해야 합니다. 즉, 감사를 위한 테이블과 뷰를 생성하는 것이지여. 이것은 $ORACLE_HOME/rdbms/admin/cataudit.sql 을 실행하면 됩니다. 감사가 끝난후 필요치 않은 테이블과 뷰의 삭제는 $ORACLE_HOME/rdbms/admin/catnoaud.sql을 실행하면 됩니다.

감사를 일단 시작하게 되면 감사의 범위를 결정한 후에 감사를 시작해야 겠지여. 너무 많은 내용의 감사 정보가 저장된다면 이거또한 분석하기 힘들어 질겁니다. 그러니 범위의 결정도 매우 중요한 사항입니다. 범위는 SQL 실행의 성공여부에 따라서 또는 세션당 또는 데이타베이스의 접근에 대한 감사등으로 구분지어 감사하고자 하는 범위를 좁혀야 합니다.

그럼 앞에서 야그한 감사의 종류 3가지에 대한 것을 각각 해보겠습니다.


2 문장 감사 #

문장감사는 SQL문에 대한 감사입니다. 모든 문장이 감사가 가능하지만 원격 데이타베이스에서 이루어지는 데이타베이스의 액션은 감사의 대상에서 제외입니다. 문장감사의 문법은 다음과 같습니다.

AUDIT statement_option
BY user
[BY SESSION | ACCESS]
[WHENEVER NOT] SUCCESSFUL

BY SESSION 옵션으로 감사를 수행할때는 세션별 감사이므로 DML 문장이 실행될 경우에만 해당하고, BY ACCESS 옵션으로 감사를 수행할 때는 SQL문이 행해지는 객체별로 감사를 하게 됩니다. WHENEVER NOT SUCCESSFUL는 문장의 성공/실패에 따라서 감사의 여부를 결정할 수 있습니다. 확인은 DBA_STMT_ADUIT_OPTS

3 권한감사 #

권한 감사는 오라클의 모든 권한에 대해서 감사를 하게 됩니다. 상당히 많은 부분입니다. 아까도 야그했듯이 감사에서 중요한 방법은 범위를 최대한 축소시키는 것입니다. 문법은 문장감사와 비슷합니다.

AUDIT system_privileges
BY user
[BY SESSION | ACCESS]
[WHENEVER NOT] SUCCESSFUL

system_privileges에 ALL PRIVILEGE를 지정하면 모든 권한에 대한 감사를 실시합니다. 실행된 권한 감사의 확인은 DBA_PRIV_AUDIT_OPTS 뷰로 확인합니다.

4 객체 감사 #

제목 그대로 객체에 대한 감사입니다. 즉, 객체 감사는 DML 문장과 GRANT, REVOKE에 대한 감사입니다. 따라서 객체 감사는 모든 사용자를 대상으로 결정됩니다. 문법은 다음과 같습니다.

AUDIT object_opt ON [[schema.]object | DEFAULT]
[BY SESSION | ACCESS]
[WHENEVER NOT] SUCCESSFUL

실행된 권한 감사의 확인은 DBA_OBJ_AUDIT_OPTS 뷰로 확인합니다. 실제로 해봅시다.

SQL> connect internal                                   
연결되었습니다.                                         		
SQL> shutdown                                           
데이터베이스가 닫혔습니다.           
데이터베이스가 마운트 해제되었습니다.
ORACLE 인스턴스가 종료되었습니다.    

SQL> !vi $ORACLE_HOME/dbs/initORCL.ora
                         .
                         .
                         .
                         .
# estimates for computer memory availability. You should adjust values upward  
# for modern machines.                                                         
#                                                                              
###############################################################################
                                                                               
AUDIT_TRAIL =DB                  <------------- 추가 시켜줍니다...                                                      
db_name = "ORCL"                                                               
db_domain = yasicom                                                            
                                                                               
instance_name = ORCL                                                           
                                                                               
service_names = ORCL.yasicom                                                   
                          .
                          .
                          .
                          .

SQL> startup                                                       
ORACLE 인스턴스가 시작되었습니다.        
                                         
Total System Global Area   87818400 bytes
Fixed Size                    73888 bytes
Variable Size              70795264 bytes
Database Buffers           16777216 bytes
Redo Buffers                 172032 bytes
데이터베이스가 마운트되었습니다.         
데이터베이스가 열렸습니다.               

SQL> @$ORACLE_HOME/rdbms/admin/cataudit.sql
                .
                .
                .
                .
                .
                .
SQL> audit create table                  
  2  by scott            
  3  by access           
  4  whenever successful;
                         
감사 성공입니다.         

SQL> connect scott/tiger            
연결되었습니다.                     
SQL> drop table test;               
                                    
테이블이 삭제되었습니다.            
                                    
SQL> create table test ( id number);
                                    
테이블이 생성되었습니다.            
                                    
SQL> connect system/manager         
연결되었습니다.                     
SQL> connect system/manager                                        
연결되었습니다.                                                    
SQL> select username, timestamp, action_name                       
  2  from dba_audit_trail;                                         
                                                                   
USERNAME                       TIMESTAM ACTION_NAME                
------------------------------ -------- ---------------------------
SCOTT                          01/12/06 LOGOFF                     
SCOTT                          01/12/06 DROP TABLE                 
SCOTT                          01/12/06 CREATE TABLE               

SQL> select username,                                                           
  2  to_char(timestamp, 'mm-dd-yy hh24:mi:ss') login_time,                      
  3  to_char(logoff_time, 'mm-dd-yy hh24:mi:ss') logoff_time,                   
  4  logoff_lread logical_reads,                                                
  5  logoff_pread physical_reads,                                               
  6  logoff_lread + logoff_pread total_reads                                    
  7  from sys.dba_audit_session;                                                
                                                                                
USERNAME                       LOGIN_TIME        LOGOFF_TIME       LOGICAL_READS PHYSICAL_READS TOTAL_READS 
------------------------------ ----------------- ----------------- ------------- -------------- ----------- 
                                                      
SCOTT                          12-06-01 20:03:32 12-06-01 20:04:05           660            40         700                                                      

SQL> desc sys.aud$
 이름                                      널?      유형
 ----------------------------------------- -------- ---------------------------
 SESSIONID                                 NOT NULL NUMBER
 ENTRYID                                   NOT NULL NUMBER
 STATEMENT                                 NOT NULL NUMBER
 TIMESTAMP#                                NOT NULL DATE
 USERID                                             VARCHAR2(30)
 USERHOST                                           VARCHAR2(128)
 TERMINAL                                           VARCHAR2(255)
 ACTION#                                   NOT NULL NUMBER
 RETURNCODE                                NOT NULL NUMBER
 OBJ$CREATOR                                        VARCHAR2(30)
 OBJ$NAME                                           VARCHAR2(128)
 AUTH$PRIVILEGES                                    VARCHAR2(16)
 AUTH$GRANTEE                                       VARCHAR2(30)
 NEW$OWNER                                          VARCHAR2(30)
 NEW$NAME                                           VARCHAR2(128)
 SES$ACTIONS                                        VARCHAR2(19)
 SES$TID                                            NUMBER
 LOGOFF$LREAD                                       NUMBER
 LOGOFF$PREAD                                       NUMBER
 LOGOFF$LWRITE                                      NUMBER
 LOGOFF$DEAD                                        NUMBER
 LOGOFF$TIME                                        DATE
 COMMENT$TEXT                                       VARCHAR2(4000)
 SPARE1                                             VARCHAR2(255)
 SPARE2                                             NUMBER
 OBJ$LABEL                                          RAW(255)
 SES$LABEL                                          RAW(255)
 PRIV$USED                                          NUMBER

SQL> noaudit create table
  2  by scott
  3  by access
  4  whenever successful;
by access
   *
3행에 오류:
ORA-01718: BY ACCESS | SESSION절은 NOAUDIT에 대해서는 허용되지 않습니다


SQL> noaudit create table
  2  by scott;

감사 해제 성공입니다.

SQL> audit all privilege                      <---------------------모든 권한에 대해서 감사를 수행한다.
  2  by access;

감사 성공입니다.

SQL> connect scott/tiger
연결되었습니다.
SQL> create user test_user
  2  identified by test_user;
identified by test_user
              *
2행에 오류:
ORA-01031: 권한이 불충분합니다


SQL> select username, timestamp, action_name
  2  from dba_audit_trail;
from dba_audit_trail
     *
2행에 오류:
ORA-00942: 테이블 또는 뷰가 존재하지 않습니다


SQL> connect system/manager
연결되었습니다.
SQL> select username, timestamp, action_name
  2  from dba_audit_trail;

USERNAME                       TIMESTAM ACTION_NAME
------------------------------ -------- ---------------------------
SCOTT                          01/12/06 LOGOFF
SCOTT                          01/12/06 DROP TABLE
SCOTT                          01/12/06 CREATE TABLE
SCOTT                          01/12/06 LOGOFF
SCOTT                          01/12/06 EXECUTE PROCEDURE
SCOTT                          01/12/06 EXECUTE PROCEDURE
SCOTT                          01/12/06 CREATE USER
SCOTT                          01/12/06 SELECT
SYSTEM                         01/12/06 LOGON
SYSTEM                         01/12/06 EXECUTE PROCEDURE
SYSTEM                         01/12/06 EXECUTE PROCEDURE

11 개의 행이 선택되었습니다.

SQL> noaudit all privilege;

감사 해제 성공입니다.

SQL> audit select, insert, update, delete
  2  on scott.dept
  3  by access
  4  whenever successful;

감사 성공입니다.

SQL> select * from scott.dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING     NEW YORK
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS     BOSTON

SQL> select username, timestamp, action_name
  2  from dba_audit_trail;

USERNAME                       TIMESTAM ACTION_NAME
------------------------------ -------- ----------------------
SCOTT                          01/12/06 LOGOFF
SCOTT                          01/12/06 DROP TABLE
SCOTT                          01/12/06 CREATE TABLE
SCOTT                          01/12/06 LOGOFF
SCOTT                          01/12/06 EXECUTE PROCEDURE
SCOTT                          01/12/06 EXECUTE PROCEDURE
SCOTT                          01/12/06 CREATE USER
SCOTT                          01/12/06 SELECT
SYSTEM                         01/12/06 LOGON
SYSTEM                         01/12/06 EXECUTE PROCEDURE
SYSTEM                         01/12/06 EXECUTE PROCEDURE
SYSTEM                         01/12/06 SYSTEM NOAUDIT
                        .
                        .
                        .
                        .
                        .
                        .
                        .
                        .
                                                
SYSTEM                         01/12/06 SYSTEM NOAUDIT
SYSTEM                         01/12/06 SYSTEM NOAUDIT
SYSTEM                         01/12/06 SYSTEM NOAUDIT
SYSTEM                         01/12/06 AUDIT OBJECT
SYSTEM                         01/12/06 SELECT                   <-------------기록되었다..

129 개의 행이 선택되었습니다.

SQL> noaudit select, insert, update, delete
  2  on scott.dept;

감사 해제 성공입니다.

감사를 추적할때는 꼭 필요할 때만 감사를 실행 해야 합니다. 저는 셀400에 램 128 컴에다가 솔라리스8 버전깔구 오라클8i(8.1.7) 깔아서 그런지 몰라두 그 속도차가 느껴지네여. 헐헐~ 네트웍 환경에서 작업하구 있어서 인지는 몰겠습니다. 암튼 감사의 범위도 줄이고 해서 감사의 실행이 시스템 전체의 성능에 미치는 영향을 최대한 줄여주어야 합니다. 그리고 감사 추적을 위한 공간도 충분히 마련되어야 합니다. 저장공간이 꽉차게 되면 오라클이 감사추적 데이타를 sys.AUD$ 에 입력해야 하는데 입력하지 못해서 에러를 리턴하게 됩니다. 주의하시구여.

감사추적의 관리는 주기적으로 감사 추적 데이타를 제거해야 하는 불편함이 있습니다. 몇가지 방법이 있습니다. 첫번째 방법은 선택적으로 삭제하는 방법, 두번째는 테이블자체를 다른 테이블로 복사해서 옮긴후 감사 추적 데이타를 삭제하는 방법, 세번째는 운영체제 상의 파일로 복사하는 방법입니다. 다음은 그 예입니다.

SQL> create table backup_audit_trail            -- 15일이 지난 감사추적 데이타를 복사한다.
  2  as
  3  select * from sys.AUD$
  4  where timestamp# < sysdate - 15;

테이블이 생성되었습니다.

yasicom% exp system/manager tables = sys.AUD$ file = /export/home/oracle/audit_trail_1.dmp             -- 필요시 임포트 툴을 써서 임포트 시킨다.

Export: Release 8.1.7.0.0 - Production on 목 Dec 6 20:47:01 2001

(c) Copyright 2000 Oracle Corporation.  All rights reserved.


접속 대상: Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production
KO16KSC5601 문자 설정과 KO16KSC5601 NCHAR 문자 설정에서 엑스포트가 종료되었습니다

지정된 테이블을 엑스포트하려고 합니다 via 규정 경로...
현재 사용자는 SYS로 변경되었습니다
. . 테이블                           AUD$(를)을 엑스포트 중        131 행이 엑스포트됨
엑스포트가 경고 없이 정상적으로 종료되었습니다.
yasicom% ls -l /export/home/oracle/audit_trail_1.dmp
-rw-r--r--   1 oracle   dba        16384 12월  6일  20:47 /export/home/oracle/audit_trail_1.dmp
yasicom%
yasicom% sqlplus system/manager

SQL*Plus: Release 8.1.7.0.0 - Production on 목 Dec 6 20:48:25 2001

(c) Copyright 2000 Oracle Corporation.  All rights reserved.


Connected to:
Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production
With the Partitioning option
JServer Release 8.1.7.0.0 - Production

SQL> truncate table sys.AUD$;

테이블이 잘렸습니다.

SQL> select count(*) from sys.AUD$;

  COUNT(*)
----------
         0

다음은 감사정보를 조회하는 데이타 사전뷰 입니다.

* DBA_PRIV_AUDIT_OPTS
* DBA_STMT_AUDIT_OPTS
* DBA_OBJ_AUDIT_OPTS
* STMT_AUDIT_OPTION_MAP
* AUDIT_ACTIONS
* ALL_DEF_AUDIT_OPTS
* USER_OBJ_AUDIT_OPTS
* DBA_AUDIT_TRAIL
* USER_AUDIT_TRAIL
* DBA_AUDIT_SESSION
* USER_AUDIT_SESSION
* DBA_AUDIT_STATEMENT
* USER_AUDIT_STATEMENT
* DBA_AUDIT_OBJECT
* USER_AUDIT_OBJECT
* DBA_AUDIT_EXISTS

관리자인증

Contents

1 인증 방법의 선택
2 운영체제 인증의 사용
3 암호파일의 사용
4 암호파일의 관리



1 인증 방법의 선택 #

  • 운영체제 인증

  • 암호파일의 사용


원격 데이터베이스이건 지역 데이터베이스이건 보안 접속을 가지고 있으거나 운영체제 인증을 사용하면 운영체제 인증을 사용하고, 그렇지 않으면 암호파일을 사용한다.

2 운영체제 인증의 사용 #

절차는 다음과 같다.

  1. 운영체제가 사용자를 인증하도록 설정
  2. 초기화 매개변수 REMOTE_LOGIN_PASSWORD=NONE으로 설정
  3. 다음 명령중의 하나로 접속한다.
    CONNECT / AS SYSOPER
    CONNECT / AS SYSDBA

주의 할 것은 SYSOPER, SYSDBA로 접속할 때SYSOPER, SYSDBA 시스템 권한이 있어야 하는 것은 아니다. 다만 서버는 운영체제 레벨에서 데이터베이스 관리자에게 적합한 OSOPER나 OSDBA 롤이 부여됬는지 확인한다. OSOPER와 OSDBA의 권한 차이는 OSOPER가 ADMIN OPTION 과 OSOPER 롤, 모든 시스템권한을 가진다는 것이다. OSOPER, OSDBA는 운영체제 만이 사용자에게 부여할 수 있다. GRANT명령으로 위의 두 롤을 부여하거나 취소 또는 삭제 할 수 없다. 일단 접속시에 OSDBA의 활성화를 시도하고 실패하면 OSOPER를 시도한다. 두 시도가 실패로 끝나면 접속을 실패이다. 다음은 윈2000에서 ORA_DBA그룹이 존재하는 모습이다.
auth01.jpg

그리고 REMOTE_LOGIN_PASSWORDFILE은 나의 경우 지금 EXCLUSIVE로 되어 있다. 이것을 NONE으로 바꾸어 운영체제 인증을 하도록 한다.

3 암호파일의 사용 #

절차는 다음과 같다.

  1. ORAPWD 유틸리티를 사용하여 암호파일을 만든다.
  2. 초기화 매개변수는REMOTE_LOGIN_PASSWORDFILE=EXCLUSIVE로 설정한다.
  3. 데이터베이스 관리 작업을 수행할 각 사용자에게 적합한 권한을 추가하려면 SQL을 사용하여 암호 파일에 사용자를 추가한다.

    GRANT SYSDBA TO scott
    GRATN SYSOPER TO scott

  4. 접속시는 다음과 같은 명령으로 접속한다.

    CONNECT scott/tiger@192.168.0.101 AS SYSDBA

4 암호파일의 관리 #

다음은 ORAPWD 유틸리티를 실행했을때의 모습이다.
auth02.jpg

다음은 그 예제이다. 5명의 관리자를 두는 것이다.
auth03.jpg

이런식으로 만들게 되면 패스워드 파일의 경로가 C:\으로 만들어진다.이 경로는 보안상 중요하며, 관리자의 권한이 주어진 사람들에게 필히 협조를 구해야한다.보안레벨을 강화하려면 암호파일 작성 즉시 REMOTE_LOGIN_PASSWORD=EXCLUSIVE 로 설정해야한다.

이밖에도 REMOTE_LOGIN_PASSWORD 파라미터의 옵션은 SHARD가 있는데 이것은 여러 데이터베이스에서 사용하는 것으로, 인식되는 사용자는 SYS와 INTERNAL뿐이다. 또, 사용자를 추가할 수도 없다. 암호파일을 작성하여 새 사용자를 추가할때는 SYSDBA, INTERNAL 시스템 권한의 사용자만이 추가
를 할수 있다. 즉, 이는 시스템 권한을 부여할수 있다는 소리이다.

GRANT SYSDBA TO scott;

중요한 것은 사용자들이 OS 인증 조건을 만족하는 경우 암호 파일의 사용은 OS 에서 인증한 사용자들의 접속을 막을 수 없다. 이와 관련된 시스템 카달로그는 V$PWFILE_USERS 뷰다. 다음은 그 예이다.
  1. SQL> DESC v$pwfile_users
  2.  
  3.  이름                                      널?      유형
  4.  ----------------------------------------- -------- ----------------------------
  5.  USERNAME                                           VARCHAR2(30)
  6.  SYSDBA                                             VARCHAR2(5)
  7.  SYSOPER                                            VARCHAR2(5)
  8.  
  9. SQL> SELECT * FROM v$pwfile_users;
  10.  
  11. USERNAME                       SYSDB SYSOP
  12. ------------------------------ ----- -----
  13. INTERNAL                       TRUE  TRUE
  14. SYS                            TRUE  TRUE
  15.  
* IE���� �ҽ� ����� �ٹٲ� �ȵ�. MS-Word � �ٿ� ��������. �ٹٲ� �Ǵ� ������: Chrome, Opera, Safari

다음은GRANT SYSDBA TO scott; 문장을 수행한 후의 뷰에 추가된 모습니다.
  1. SQL> connect internal
  2. 연결되었습니다.
  3.  
  4. SQL> GRANT sysdba TO scott;
  5. 권한이 부여되었습니다.
  6.  
  7. SQL> SELECT * FROM v$pwfile_users;
  8.  
  9. SQL> SELECT * FROM v$pwfile_users;
  10.  
  11. USERNAME                       SYSDB SYSOP
  12. ------------------------------ ----- -----
  13. INTERNAL                       TRUE  TRUE
  14. SYS                            TRUE  TRUE
  15. SCOTT                          TRUE  FALSE
  16.  
* IE���� �ҽ� ����� �ٹٲ� �ȵ�. MS-Word � �ٿ� ��������. �ٹٲ� �Ǵ� ������: Chrome, Opera, Safari

운영체제의 사용자명으로 데이터베이스를 사용할 수 있게 해보자. 일반적인 사용자이다. 먼저 오라클 파라미터 파일에 다음을 추가해 준다.

OS_AUTHENT_PREFIX = 'OPS$'

데이터베이스를 종료후 다시 시작한다.

  1. SQL> connect internal
  2. 연결되었습니다.
  3.  
  4. SQL> shutdown
  5. 데이터베이스가 닫혔습니다.
  6. 데이터베이스가 마운트 해제되었습니다.
  7. ORACLE 인스턴스가 종료되었습니다.
  8.  
  9. SQL> startup
  10. ORACLE 인스턴스가 시작되었습니다.
  11. 시스템 글로벌 영역                        237008140 바이트 합계
  12. Fixed Size                                          70924 바이트
  13. Variable Size                                    79572992 바이트
  14. DATABASE Buffers                                157286400 바이트
  15. Redo Buffers                                        77824 바이트
  16. 데이터베이스가 마운트되었습니다.
  17. 데이터베이스가 열려졌습니다.
  18.  
  19.  
  20. SQL> CREATE user OPS$ORCL
  21.  2   IDENTIFIED BY externally
  22.  3   DEFAULT tablespace users
  23.  4   ;
  24. 사용자가 생성되었습니다.
  25.  
  26. SQL> GRANT connect, resource TO OPS$ORCL; --ORCL은 데이터베이스 이름이다.
  27. 권한이 부여되었습니다.
  28.  
  29. SQL> connect OPS$ORCL/externally
  30. 연결되었습니다.
  31.  
* IE���� �ҽ� ����� �ٹٲ� �ȵ�. MS-Word � �ٿ� ��������. �ٹٲ� �Ǵ� ������: Chrome, Opera, Safari