11g이전 버전에서는 SQL_TRACE, 10046 등의 event는 session이나 instance level로만 설정할 수 있었습니다. 
특정 SQL에 대한 dump를 수행하려면 해당 SQL을 수행하는 세션이나 program을 찾아서 
sqlplus나 oradebug 등을 통해 sql_trace, 10046 event 를 걸어야 했죠. 

다음의 SQL_ID에 대해 event 거는 방안은 sql monitoring에 대단히 유용해 보이네요. 

1. SQL_ID 찾기
SQL> select sql_id,sql_text from v$sql where sql_text like 'select /*+ test */%';
SQL_ID               SQL_TEXT
----------------  ----------------------------------------------- 
6vnyysxnuud8b   select /*+ test */ * from scott.emp

2. 해당 SQL_ID에 sql_trace event 걸기 
SQL> alter session set events 'sql_trace [sql:sql_id=6vnyysxnuud8b]';
Session altered.

3. SQL 수행 
SQL> select * from scott.dept;
SQL> select * from scott.emp;
SQL> select /*+ test */ * from scott.emp;

*** 2011-01-27 13:03:14.561
*** SESSION ID:(10979.4995) 2011-01-27 13:03:14.561
*** CLIENT ID:() 2011-01-27 13:03:14.561
*** SERVICE NAME:(SYS$USERS) 2011-01-27 13:03:14.561
*** MODULE NAME:(SQL*Plus) 2011-01-27 13:03:14.561
*** ACTION NAME:() 2011-01-27 13:03:14.561

=====================
PARSING IN CURSOR #5 len=97 dep=1 uid=0 oct=3 lid=0 tim=5959428212907 hv=2759248297 ad='c0000000c0ee11a8' sqlid='aa35g82k7dkd9'
select binaryDefVal, length(binaryDefVal) from ecol$           where tabobj# = :1 and colnum = :2
END OF STMT
PARSE #5:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=4,plh=0,tim=5959428212907
EXEC #5:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=4,plh=2830718548,tim=5959428244106
FETCH #5:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,plh=2830718548,tim=5959428244106
STAT #5 id=1 cnt=1 pid=0 pos=1 obj=123 op='TABLE ACCESS BY INDEX ROWID ECOL$ (cr=2 pr=0 pw=0 time=0 us cost=1 size=2028 card=1)'
STAT #5 id=2 cnt=1 pid=1 pos=1 obj=126 op='INDEX RANGE SCAN ECOL_IX1 (cr=1 pr=0 pw=0 time=0 us cost=1 size=0 card=1)'
CLOSE #5:c=0,e=0,dep=1,type=3,tim=5959428244106
=====================
PARSING IN CURSOR #2 len=35 dep=0 uid=5 oct=3 lid=5 tim=5959428244106 hv=1772958987 ad='c0000000c0ed2e48' sqlid='6vnyysxnuud8b'
select /*+ test */ * from scott.emp
END OF STMT
EXEC #2:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=2872589290,tim=5959428244106
FETCH #2:c=0,e=0,p=0,cr=5,cu=0,mis=0,r=1,dep=0,og=1,plh=2872589290,tim=5959428244106
FETCH #2:c=0,e=0,p=0,cr=1,cu=0,mis=0,r=15,dep=0,og=1,plh=2872589290,tim=5959428244106
FETCH #2:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=12,dep=0,og=1,plh=2872589290,tim=5959428244106
STAT #2 id=1 cnt=28 pid=0 pos=1 obj=36463 op='TABLE ACCESS FULL EMP (cr=9 pr=0 pw=0 time=0 us cost=3 size=39673 card=409)'

*** 2011-01-27 13:03:25.877
CLOSE #2:c=0,e=0,dep=0,type=0,tim=5959439294741

> tkprof TEST_ora_17091.trc TEST_ora_17091.out explain=scott/tiger sys=no
TKPROF: Release 11.1.0.7.0 - Production on Thu Jan 27 13:05:37 2011
Copyright (c) 1982, 2007, Oracle.  All rights reserved.

TKPROF: Release 11.1.0.7.0 - Production on Thu Jan 27 13:05:37 2011
Copyright (c) 1982, 2007, Oracle.  All rights reserved.

Trace file: TEST_ora_17091.trc
Sort options: default

********************************************************************************
count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing 
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers from disk
query    = number of buffers gotten for consistent read
current  = number of buffers gotten in current mode (usually for update)
rows     = number of rows processed by the fetch or execute call
********************************************************************************

SQL ID: 6vnyysxnuud8b
Plan Hash: 2872589290
select /*+ test */ * 
from
 scott.emp

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        3      0.00       0.00          0          9          0          28
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          9          0          28

Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 5  (SYSTEM)

Rows     Row Source Operation
-------  ---------------------------------------------------
     28  TABLE ACCESS FULL EMP (cr=9 pr=0 pw=0 time=0 us cost=3 size=39673 card=409)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   MODE: ALL_ROWS
     28   TABLE ACCESS (FULL) OF 'EMP' (TABLE)

********************************************************************************

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        0      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        3      0.00       0.00          0          9          0          28
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          9          0          28

Misses in library cache during parse: 0

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0          2          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          2          0           1

Misses in library cache during parse: 1
Misses in library cache during execute: 1

    1  user  SQL statements in session.
    1  internal SQL statements in session.
    2  SQL statements in session.
    1  statement EXPLAINed in this session.
********************************************************************************
Trace file: TEST_ora_17091.trc
Trace file compatibility: 11.1.0.7
Sort options: default

       1  session in tracefile.
       1  user  SQL statements in trace file.
       1  internal SQL statements in trace file.
       2  SQL statements in trace file.
       2  unique SQL statements in trace file.
       1  SQL statements EXPLAINed using schema:
           SCOTT.prof$plan_table
             Default table was used.
             Table was created.
             Table was dropped.
      44  lines in trace file.
       0  elapsed seconds in trace file.




시스템을 운영하면 가끔 특정 프로세스가 CPU를 점유해서 다른 프로세스에 영향을 끼치곤 합니다. 
이런 경우 kill -9 <process id>로 해당 프로세스를 정리하면 문제가 해결되곤 하죠. 
물론 CPU를 99%를 소모하는 프로세스는 거의 정상적이지 않은 프로세스이므로 kill 하는 게 맞지만, 
가끔 특정 batch 작업 등 많은 시간동안 수행되어 kill 시키기 아까운 프로세스들도 존재합니다. 

아래의 아이디어는 자원을 많이 먹는 프로세스를 kill 명령으로 죽이지 않고 잠시 멈춰두는 방법입니다. 

If that oracle process uses high CPU%, and don't want to kill it yet. we may use "oradebug" to suspend it.

$ ps -e -o pcpu,user,pid,args | sort -k 1 | grep 2904
62.8 oracle 2904 oracleorcl (LOCAL=NO)

SQL> oradebug suspend
Statement processed.

$ ps -e -o pcpu,user,pid,args | sort -k 1 | grep 2904
6.0 oracle 2904 oracleorcl (LOCAL=NO)

참고: http://surachartopun.com/2011/01/oradebug-man.html

물론 kill 명령도 signum (24/26)을 주면 동일하게 suspend를 시킬 수 있습니다. 


   signum   signame   Name            Description
   ___________________________________________________________________________
      0     SIGNULL   Null            Check access to pid
      1     SIGHUP    Hangup          Terminate; can be trapped
      2     SIGINT    Interrupt       Terminate; can be trapped
      3     SIGQUIT   Quit            Terminate with core dump; can be trapped
      9     SIGKILL   Kill            Forced termination; cannot be trapped
     15     SIGTERM   Terminate       Terminate; can be trapped
     24     SIGSTOP   Stop            Pause the process; cannot be trapped
     25     SIGTSTP   Terminal stop   Pause the process; can be trapped
     26     SIGCONT   Continue        Run a stopped process

이런 기능을 운영 서버에 사용하시려면 먼저 정상적으로 수행되는지는 테스트 해봐야 겠죠? 
대부분 요런 기능은 hang 같은 테스트 환경 만들 때 주로 쓰긴 합니다..만 알아서 하시길... ^^;

P.S. 
OS kill 명령에 의한 stop과 oradebug를 이용한 suspend의 차이는 뭘까요? 
suspend 된 세션은 v$session 등에서 어떤 상태로 보여질까요? 



간단히 UTL_FILE을 이용한 scott.emp 테이블 데이터 unloading 하기
create or replace procedure utl_file_test_write (
  filename   in varchar2
  )
is
   v_file            UTL_FILE.FILE_TYPE;
   v_text            VARCHAR2 (20);
   v_filename        VARCHAR2 (200);
   v_delimitedchar   CHAR (1);
BEGIN
   v_delimitedchar := CHR (124);
   v_file := UTL_FILE.FOPEN ('DATA_PUMP_DIR', filename, 'W');
   FOR rec IN
      (SELECT   empno,ename,job,mgr,hiredate,sal,comm,deptno FROM scott.emp)
   LOOP
      UTL_FILE.PUT_LINE (v_file,rec.empno|| v_delimitedchar|| rec.ename||v_delimitedchar|| 
                         rec.job||v_delimitedchar|| rec.mgr||v_delimitedchar||rec.hiredate||
                         v_delimitedchar|| rec.sal ||v_delimitedchar|| rec.comm|| v_delimitedchar|| 
                         rec.deptno  );
   END LOOP; 
   UTL_FILE.FCLOSE (v_file);
END;
/

수행 및 결과
SQL> exec utl_file_test_write ('TEST01');
SQL> !cat /oracle_home/TEST01
7369|SMITH|CLERK|7902|1980-12-17|800||20
7499|ALLEN|SALESMAN|7698|1981-02-20|1600|300|30
7521|WARD|SALESMAN|7698|1981-02-22|1250|500|30
7566|JONES|MANAGER|7839|1981-04-02|2975||20
7654|MARTIN|SALESMAN|7698|1981-09-28|1250|1400|30
7698|BLAKE|MANAGER|7839|1981-05-01|2850||30
7782|CLARK|MANAGER|7839|1981-06-09|2450||10
7788|SCOTT|ANALYST|7566|1982-12-09|3000||20
7839|KING|PRESIDENT||1981-11-17|5000||10
7844|TURNER|SALESMAN|7698|1981-09-08|1500|0|30
7876|ADAMS|CLERK|7788|1983-01-12|1100||20
7900|JAMES|CLERK|7698|1981-12-03|950||30
7902|FORD|ANALYST|7566|1981-12-03|3000||20


블로그 구경다니다가 11g에서 쓸만한 hint 하나 확인해서 공유합니다. 
구구절절한 설명 보다 아래의 예를 보는게 가장 이해하기 쉽겠네요. 


However, Oracle11g allows us to use the IGNORE_ROW_ON_DUPKEY_INDEX hint, which will silently deal with the unique constraint violation errors by simply ignoring and not inserting any row in which the unique values already exist in the table.



SQL> insert into radiohead select rownum, 'OK COMPUTER' from dual connect by level <= 12;
insert into radiohead select rownum, 'OK COMPUTER' from dual connect by level <= 12
*
ERROR at line 1:
ORA-00001: unique constraint (BOWIE.RADIOHEAD_PK_I) violated

SQL> insert /*+ ignore_row_on_dupkey_index(radiohead,radiohead_pk_i) */ into radiohead select rownum, 
'OK COMPUTER' from dual connect by level <= 12;
2 rows created.

SQL> commit;
Commit complete.

위의 예를 보면 알겠지만 unique key 값이 없는 row에 대해서만 insert가 발생합니다. 에러는 생략되고요.. 
즉 새로 입력되는 값이 기존의 unique 값과 constraint violation이 발생한다면 무시되는 거죠.. 

이 hint는 update에는 사용할 수 없습니다. 또 parallel DML과 APPEND mode일 경우에는 무시됩니다. 
몇몇 요구사항에 맞지 않는다면 ORA-38912, ORA-38915, ORA-38913이 발생합니다. 

자세한 내용은 아래 메뉴얼에서...

interval partition을 drop할 때 ORA-14758 에러가 발생하는 군요. 
메세지로만 봐서는 마지막 partition은 drop될 수 없다라는데...
분명 마지막 range의 partition도 아니고.

아래 노트를 보면 명확히 설명되어 있네요. 
마지막 partition이라는 게 처음 table을 생성할 때 만들어진 마지막 partition이며 
이후 DB에서 자동으로 interval partition을 만들때 이 partition을 참조하기 때문에 삭제하면 안된답니다. 

그럼 처음 interval partition을 만들때 마지막 partition은 안지워지니까 
주기적인 데이터 삭제용도로 사용하시려면 마지막 partition의 range에 대해 고려를 좀 해보셔야 겠죠? 

ORA-14758: Last Partition In The Range Section Cannot Be Dropped (Doc ID 882681.1)

Symptoms

You drop a partition and receive the following error:
ORA-14758: Last partition in the range section cannot be dropped

Cause

In case of interval partitioning the partitions which are created automatically depends on the last partition created as a reference while creating a table. So the last partition can not be dropped.

Example:

create table test.orders
(order_id number(12),
order_date date
)
partition by range(order_date)
interval(numtoyminterval(1, 'month'))
(partition p1 values less than (to_date('2000-01-01', 'YYYY-MM-DD')),
partition p2 values less than (to_date('2000-04-01', 'YYYY-MM-DD')),
partition p3 values less than (to_date('2000-07-01', 'YYYY-MM-DD')));

insert into test.orders values(1, to_date('2000-02-15', 'YYYY-MM-DD')) ;
insert into test.orders values(1, to_date('2000-03-15', 'YYYY-MM-DD')) ;
insert into test.orders values(1, to_date('2000-05-15', 'YYYY-MM-DD')) ;
insert into test.orders values(1, to_date('2000-06-15', 'YYYY-MM-DD')) ;
insert into test.orders values(1, to_date('2000-08-15', 'YYYY-MM-DD')) ;
insert into test.orders values(1, to_date('2000-09-15', 'YYYY-MM-DD')) ;

commit;

SQL> select table_name,partition_name,high_value from dba_tab_partitions where table_name='ORDERS';

TABLE_NAME PARTITION_NAME HIGH_VALUE
------------------------------ ------------------------------ --------------------------------------------------------------------------------
ORDERS P1 TO_DATE(' 2000-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
ORDERS P2 TO_DATE(' 2000-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
ORDERS P3 TO_DATE(' 2000-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
ORDERS SYS_P115 TO_DATE(' 2000-09-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
ORDERS SYS_P116 TO_DATE(' 2000-10-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA

SQL> alter table test.orders drop partition SYS_P116;

Table altered.

SQL> alter table test.orders drop partition P2;

Table altered.

SQL> alter table test.orders drop partition P3;
alter table test.orders drop partition P3
*
ERROR at line 1:
ORA-14758: Last partition in the range section cannot be dropped

Solution

This is expected behaviour.
Do not drop the last partition on which the interval partitions are based.

오류에 대해 좀 찾아 보니 interval을 임시로 바꿔 지우는 방법이 나와 있습니다. 
하지만 오라클쪽 자료(metalink)에는 그런 내용은 없는 것 같습니다. 
요 방법 써도 괜찮을라나? 


임시 테이블은 사용자가 DML(update, insert, delete) 문을 실행한 후, 트랜젝션을 종료(commit)하면 변경된 데이터들이 테이블에 저장되지 않는 테이블이다. 어떤 응용을 개발할 때 데이터를 잠시 저장하는 변수와 같은 유형이다.

temporary table은 특정 session 또는 transaction 동안만 data를 저장한다. 

on commit delete rows : 
트랜젝션을 발생시킨 후, commit문을 실행할 때 없어지는 방법. 한 transaction 동안만 data를 저장(default임)

on commit preserve rows : 
트랜젝션을 종료하면(commit) 테이블 내에 데이터가 저장되었다가 세션을 종료하면 임시 테이블에 저장되었던 데이터들이 모두 없어지는 방법. 한 session 동안만 data를 저장

temporary table은 다음과 같은 성격을 갖는다. 

TABLE의 definition 정보는 data dictionary에 영구적으로 저장되지만, data는 일시적으로 저장된다.
CREATE로 TABLE 생성시 SEGMENT가 할당되는 것이 아니라 첫 번째 INSERT시 할당된다.
DML 작업시 해당 data에 대해 LOCK을 걸지 않는다.
data 변경시 redo log에 기록이 남지 않는다.

다음은 간단한 테스트 이다. 

1. global temporary table 생성 

SQL> create global temporary table gtt ( x int );

Table created.

2. object 생성 확인 

SQL> select dbms_metadata.get_ddl( 'TABLE', 'GTT' ) from dual;

DBMS_METADATA.GET_DDL('TABLE','GTT')
--------------------------------------------------------------------------------
  CREATE GLOBAL TEMPORARY TABLE "SCOTT"."GTT"
   (    "X" NUMBER(*,0)
   ) ON COMMIT DELETE ROWS

SQL> select object_name,object_type from user_objects where object_name = 'GTT';

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------
GTT                            TABLE

SQL> select table_name from user_tables where table_name = 'GTT';

TABLE_NAME
------------
GTT

SQL> select table_name,temporary,duration from user_tables where table_name = 'GTT';

TABLE_NAME           TEM DURATION
-------------------- --- ---------------------------------------------
GTT                  Y   SYS$TRANSACTION

SQL> select segment_name from user_segments where segment_name = 'GTT';

no rows selected

SQL> drop table gtt;

Table dropped.

3. global temporary table 생성 

SQL> create global temporary table gtt ( x int ) on commit preserve rows;

Table created.

4. object 생성 확인 

SQL> select dbms_metadata.get_ddl( 'TABLE', 'GTT' ) from dual;

DBMS_METADATA.GET_DDL('TABLE','GTT')
--------------------------------------------------------------------------------
  CREATE GLOBAL TEMPORARY TABLE "SCOTT"."GTT"
   (    "X" NUMBER(*,0)
   ) ON COMMIT PRESERVE ROWS


SQL> select object_name,object_type from user_objects where object_name = 'GTT';

OBJECT_NAME                    OBJECT_TYPE
------------------------------ ------------- 
GTT                            TABLE

SQL> select table_name from user_tables where table_name = 'GTT';

TABLE_NAME
-------------------- 
GTT

SQL> select table_name,temporary,duration from user_tables where table_name = 'GTT';

TABLE_NAME           TEM DURATION
-------------------- --- ---------------------------------------------
GTT                  Y   SYS$SESSION

SQL> select segment_name from user_segments where segment_name = 'GTT';

no rows selected

SQL> drop table gtt;

Table dropped.



HOT backup mode에서 왜 redo log에 추가 정보가 쓰일까요? 
ALTER DATABASE BEGIN BACKUP과 ALTER TABLESPACE BEGIN BACKUP의 차이는? 

아래 두개의 POST에 모든 답이 있네요.. 

Why is excessive redo generated during an Online/Hot Backup

There is not excessive redo generated, there is additional information logged into the online redo log during a hot backup the first time a block is modified in a tablespace that is in hot backup mode.  

in hot backup mode only 2 things are different:

o the first time a block is changed in a datafile that is in hot backup mode, the ENTIRE BLOCK is written to the redo log files, not just the changed bytes.  Normally only the changed bytes (a redo vector) is written. In hot backup mode, the entire block is logged the FIRST TIME.  This is because you can get into a situation where the process copying the datafile and DBWR are working on the same block simultaneously.  Lets say they are and the OS blocking read factor is 512bytes (the OS reads 512 bytes from disk at a time).

The backup program goes to read an 8k Oracle block.  The OS gives it 4k.  Meanwhile -- DBWR has asked to rewrite this block.  the OS schedules the DBWR write to occur right now.  The entire 8k block is rewritten.  The backup program starts running again (multi-tasking OS here) and reads the last 4k of the block.  The backup program has now gotten an impossible block -- the head and tail are from two points in time.  We cannot deal with that during recovery.  Hence, we log the entire block image so that during recovery, this block is totally rewritten from redo and is consistent with itself at least.  We can recover it from there.

o the datafile headers which contain the SCN of the last completed checkpoint are NOT updated while a file is in hot backup mode.  This lets the recovery process understand what archive redo log files might be needed to fully recover this file.

Why is excessive redo generated during an Online/Hot Backup

What about ALTER DATABASE BEGIN BACKUP ?
That command put all database tablespaces in backup mode at the same time. As seen previously, it is a bad idea to put all tablespaces in backup mode, as it is better to do it one by one in order to minimize the supplemental redo logging overhead. Oracle introduces this ‘shortcut’ for one reason only: when doing backup with a mirror split (BCV, Flashcopy, etc), the copy gets all the datafiles at the same time, and the copy lasts only few seconds. In that case, it is easier to use that command to put all tablespaces in backup mode during the operation.

http://narashimreddy.wordpress.com/2009/09/11/oracle-backup-and-recovery-description-of-begin-backup-end-backup/


Oracle LOCAL INDEX를 drop하는 방법은 없습니다. ㅋ
아래 테스트를 보시면 local partition index로 만들어진 index는 drop이 되지 않는 군요.. 

LOCAL partition index는 partition table의 구조를 참조해서 만들어 지기 때문에 drop이 되지 않습니다. 
그러나 Global partition index는 자신만의 구조를 갖기 때문에 drop이 가능합니다.. 

+ LOCAL INDEX

SQL> create table p_emp (sal number(10))
 2  partition by range (sal)
 3   (partition emp_p1 values less than (2000),
 4    partition emp_p2 values less than (3000),
 5    partition emp_max values less than (maxvalue));

Table created.

SQL> create index p_emp_i on p_emp (sal) local;

Index created.

SQL> alter index p_emp_i drop partition emp_p1;
alter index p_emp_i drop partition emp_p1
           *
ERROR at line 1:
ORA-14076: submitted alter index partition/subpartition operation is not valid for local partitioned index

+ GLOBAL INDEX

 1  create table p_emp (empno number(4), sal number(10))
 2  partition by range (sal)
 3   (partition emp_p1 values less than (2000),
 4    partition emp_p2 values less than (3000),
 5*   partition emp_pmax values less than (maxvalue))
SQL> /

Table created.

 1  create index p_emp_i on p_emp (sal)
 2  global  partition by range (sal)
 3   (partition emp_p1 values less than (2000),
 4    partition emp_p2 values less than (3000),
 5*   partition emp_max values less than (maxvalue))
SQL> /

Index created.

SQL>
SQL> alter index p_emp_i drop partition emp_p2;

Index altered.

+ 참고

Truss 명령으로 system call 확인

% tusc -faepo /tmp/tusc.out -T %H:%M:%S -p <pid>

SQLNET tracing 

Client tracing
--------------
SQL*Net이 설치된 client 쪽 $ORACLE_HOME/network/admin/sqlnet.ora 파일에 다음을 추가해 주십시오.
TRACE_LEVEL_client=16
TRACE_FILE_client=client
TRACE_DIRECTORY_client=c:\temp /* trace 파일이 생성될 디렉토리 */
TRACE_UNIQUE_client=YES
TRACE_TIMESTAMP_client=ON

Listener tracing
-----------------
LISTENER.ORA 에 다음을 지정후 restart 합니다
TRACE_LEVEL_<listener_name>=16
TRACE_DIRECTORY_<listener_name>=/tmp
TRACE_FILE_<listener_name>=lsnr.trc
TRACE_TIMESTAMP_<listener name>=ON



UTL_FILE을 이용해 file을 loading 하는 아주 간단한 example. 

declare
   fp utl_file.file_type;
   str varchar2(48);
   VAL SCOTT.TEST%ROWTYPE;   
begin
   fp := utl_file.fopen('DATA_PUMP_DIR','sample_data.txt','r');
   loop
    utl_file.get_line(fp,str);
    VAL.CUST_ID := TRIM(SUBSTR(str,1,9));
    VAL.FC_ID := TRIM(SUBSTR(str,11,20));
    insert into SCOTT.TEST values(VAL.CUST_ID,VAL.FC_ID);
   end loop;
exception
when no_data_found then
   commit;
end;
/


네이버 지식인에 DBMS_JOB으로 만들어진 JOB의 interval을 1초로 지정해도 
5초 정도의 interval을 갖는다는 질문이 있어 간단히 테스트 해 봤습니다. 

DBMS_JOB package로 job을 생성합니다 
SQL> variable jobno number;
SQL> begin
SQL> dbms_job.submit(:jobno,'insert into test values (sysdate);',
SQL> sysdate,'sysdate+1/60/24/50',FALSE);
SQL> end;
SQL> /

PL/SQL procedure successfully completed.

SQL> print jobno
     JOBNO
----------
        21

SQL> exec dbms_job.run(21);
PL/SQL procedure successfully completed.

SQL> commit;
Commit complete.

SQL> select * from user_jobs;

       JOB LOG_USER                       PRIV_USER                      SCHEMA_USER                    LAST_DATE LAST_SEC         THIS_DATE THIS_SEC         NEXT_DATE NEXT_SEC         TOTAL_TIME B
---------- ------------------------------ ------------------------------ ------------------------------ --------- ---------------- --------- ---------------- --------- ---------------- ---------- -
INTERVAL
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  FAILURES
----------
WHAT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
NLS_ENV
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
MISC_ENV                                                           INSTANCE
---------------------------------------------------------------- ----------
        21 SYS                            SYS                            SYS                            12-OCT-10 16:50:28                                    12-OCT-10 16:50:29                  0 N
sysdate+1/60/24/50
         0
insert into test values (sysdate);
NLS_LANGUAGE='AMERICAN' NLS_TERRITORY='AMERICA' NLS_CURRENCY='$' NLS_ISO_CURRENCY='AMERICA' NLS_NUMERIC_CHARACTERS='.,' NLS_DATE_FORMAT='DD-MON-RR' NLS_DATE_LANGUAGE='AMERICAN' NLS_SORT='BINARY'
0102000202000000                                                          0

JOB 내역을 보면 정상적으로 NEXT_DATE는 LAST_DATE + 1초로 나옵니다 

SQL> alter session set nls_date_format = 'YYYY/MM/DD HH24:MI:SS';
Session altered.

SQL> select * from test;
COMMIT_DATE
-------------------
2010/10/12 16:50:04
2010/10/12 16:50:08
2010/10/12 16:50:13
2010/10/12 16:50:18
2010/10/12 16:50:23
2010/10/12 16:50:28
2010/10/12 16:50:33
2010/10/12 16:50:38
2010/10/12 16:50:43
2010/10/12 16:50:48
2010/10/12 16:50:53
2010/10/12 16:50:58
2010/10/12 16:51:03
2010/10/12 16:51:08

14 rows selected.

그러나 table에 들어간 시간을 보니 4초 차이가 발생하는 군요...

SQL> exec dbms_job.broken(21,TRUE);
PL/SQL procedure successfully completed.

SQL> exec dbms_job.remove(21);
PL/SQL procedure successfully completed.

SQL> delete from test;
25 rows deleted.

SQL> commit;
Commit complete.

이번엔 DBMS_SCHEDULER로 테스트 해봤습니다. 

SQL> begin
  2  dbms_scheduler.create_schedule(
schedule_name  => 'INTERVAL_EVERY_1_SECONDS',
  start_date    => trunc(sysdate),
  repeat_interval => 'freq=SECONDLY;interval=1',
  comments     => 'Runtime: Every day all 1 SECONDS');  3    4    5    6  
  7  end;
  8  /

PL/SQL procedure successfully completed.

1초에 한번씩 수행할 Schedule을 생성합니다 

SQL> begin
dbms_scheduler.create_program
(program_name=> 'TEST_PROC01',
 program_type=> 'STORED_PROCEDURE',
 program_action=> 'SYS.TEST_PROC',
 enabled=>true,
 comments=>'job interval test'
 );
end;
PL/SQL procedure successfully completed.

수행할 Program을 생성합니다. 
TEST_PROC는 test table에 sysdate를 입력하는 procedure 입니다.

SQL> begin
dbms_scheduler.create_job
 (job_name => 'JOB_TEST',
  program_name=> 'TEST_PROC01',
  schedule_name=>'INTERVAL_EVERY_1_SECONDS',
  enabled=>true,
  auto_drop=>false,
  comments=>'job test');
end;
/  
PL/SQL procedure successfully completed.

이제 먼저 생성한 schedule과 program을 이용해 JOB을 생성합니다 

SQL> begin
dbms_scheduler.run_job('JOB_TEST',TRUE);
end; 
/
PL/SQL procedure successfully completed.



SQL> select * from test;

COMMIT_DATE
-------------------
2010/10/12 17:12:38
2010/10/12 17:12:39
2010/10/12 17:12:40
2010/10/12 17:12:41
2010/10/12 17:12:42
2010/10/12 17:12:43
2010/10/12 17:12:44
2010/10/12 17:12:45
2010/10/12 17:12:46
2010/10/12 17:12:47
2010/10/12 17:12:48
2010/10/12 17:12:49
2010/10/12 17:12:50
2010/10/12 17:12:51
2010/10/12 17:12:52
2010/10/12 17:12:53
2010/10/12 17:12:54
2010/10/12 17:12:55
2010/10/12 17:12:55
2010/10/12 17:12:56
2010/10/12 17:12:57
2010/10/12 17:12:58
2010/10/12 17:12:59
2010/10/12 17:13:00
2010/10/12 17:13:01
...

table에 들어간 데이터를 보니 1초에 한번씩 수행하고 있군요.. 

DBMS_JOB을 이용한 job은 interval에 제한이 있는 걸까요? 그런 자료는 못 본것 같은데..

예전 oracle 8에서 job_queue_interval parameter가 있을때
수행 시간이 job_queue_interval 주기때문에 job interval이 정확히 계산되지 않았던 것 같은데... 
혹시 job_queue_process가 wake-up 하는 시간때문일까요?  






10. 10. 6 작성자: Laurent Schneider의 Laurent Schneider

Pretty straightforward, check if dbms_transaction.step_id is null!

SQL> select dbms_transaction.step_id from dual;
        STEP_ID
---------------

SQL> insert into t values (1);
1 row created.

SQL> select dbms_transaction.step_id from dual;
        STEP_ID
---------------
114352430549782

SQL> commit;
Commit complete.

SQL> select dbms_transaction.step_id from dual;
        STEP_ID
---------------


STEP_ID가 어떤의미일까요?
메뉴얼엔 step_id function에 대해 아래와 같이 정의도어 있는데, 무슨 말인지 잘 모르겠네요. 

"This function returns local (to local transaction) unique positive integer that orders the DML operations of a transaction."

테스트를 해보자면...

SQL> delete from yhcho_temp where col1 = 'TURNER';
1 row deleted.

SQL> select dbms_transaction.step_id from dual;
                   STEP_ID
--------------------------
         37783619732783637

SQL> savepoint A ;
Savepoint created.

SQL> delete from yhcho_temp where col1 = 'KING';
1 row deleted.

SQL> select dbms_transaction.step_id from dual;
                   STEP_ID
--------------------------
         37783619732783639

SQL> rollback to savepoint A;
Rollback complete.

SQL> select dbms_transaction.step_id from dual;
                   STEP_ID
--------------------------
         37783619732783639

savepoint A까지 rollback 해도 step_id는 바뀌지 않는 군요.. 
단순히 transaction step에 대한 sequence 일까요? 

SQL> delete from yhcho_temp where col1 = 'KING';
1 row deleted.

SQL> select dbms_transaction.step_id from dual;
                   STEP_ID
--------------------------
         37783619732783641

특정 transaction을 가리키는 것 같진 않고 
위의 post 처럼 step_id로는 단순히 transaction이 있는지 여부만 확인할 수 있을 것 같네요..

SQL> desc dbms_transaction        
PROCEDURE ADVISE_COMMIT
PROCEDURE ADVISE_NOTHING
PROCEDURE ADVISE_ROLLBACK
PROCEDURE BEGIN_DISCRETE_TRANSACTION
PROCEDURE COMMIT
PROCEDURE COMMIT_COMMENT
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 CMNT                           VARCHAR2                IN
PROCEDURE COMMIT_FORCE
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 XID                            VARCHAR2                IN
 SCN                            VARCHAR2                IN     DEFAULT
FUNCTION LOCAL_TRANSACTION_ID RETURNS VARCHAR2
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 CREATE_TRANSACTION             BOOLEAN                 IN     DEFAULT
PROCEDURE PURGE_LOST_DB_ENTRY
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 XID                            VARCHAR2                IN
PROCEDURE PURGE_MIXED
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 XID                            VARCHAR2                IN
PROCEDURE READ_ONLY
PROCEDURE READ_WRITE
PROCEDURE ROLLBACK
PROCEDURE ROLLBACK_FORCE
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 XID                            VARCHAR2                IN
PROCEDURE ROLLBACK_SAVEPOINT
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SAVEPT                         VARCHAR2                IN
PROCEDURE SAVEPOINT
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SAVEPT                         VARCHAR2                IN
FUNCTION STEP_ID RETURNS NUMBER
PROCEDURE USE_ROLLBACK_SEGMENT
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 RB_NAME                        VARCHAR2                IN


library cache lock/pin은 library cache내의 object에 대한 매우 빠른 시간내에 처리되는 lock 메카니즘입니다.

따라서 일반적인 운영상황에서는 library cache lock/pin 등의 contention을 dump 등으로 정보를 수집하기는 엄청어렵죠.
뭐 운영중 DDL을 수행하면 library cache lock/pin을 유발할 순 있겠지만..

다음의 시나리오는 2006년 가을호 오라클 매거진에 있는 hang을 유발하는 시나리오 입니다.
(보고 싶은신 분들은 www.oracle.com에 가면 아마~ 있을 겁니다.)
오라클 매거진에서는 system state dump를 설명하고 있지만, 
요 시나리오를 이용해서 library cache dump를 떠보겠습니다. 

욱짜님의 오라클 블로그에 보면 "library cache pin self deadlock 만들기" 방법도 있습니다..

* 준비 과정

create table employee (empno number,ename varchar2(200), deptno number);
insert into employee values (1,'Alice',10);
commit;
create or replace procedure deleteEmployee(eno number)
is
begin
delete employee where empno = eno;
end;
/

* 시나리오
T1 (SID 110) : delete employee where empno = 1;
T2 (SID 92)  : exec deleteEmployee(1);
T3 (SID 67)  : alter procedure deleteEmployee compile;
T4 (SID 36)  : exec deleteEmployee(2);

alter session set events 'immediate trace name LIBRARY_CACHE level 11';


위와 같이 수행하게 되면 SID 92, 7, 36은 sql prompt가 떨어지지 않고 기다리게 됩니다.
각각 어떤 resource를 기다리고 있을까요?

* wait event
       SID EVENT                                  P1 P1RAW                    P2 P2RAW                    P3
---------- ------------------------------ ---------- ---------------- ---------- ---------------- ----------
        36 library cache lock             4.4044E+12 000004017D44D868 4.4040E+12 0000040163A70D88        201
        67 library cache pin              4.4044E+12 000004017D44D868 4.4040E+12 0000040163D23718        301
        92 enq: TX - row lock contention  1415053318 0000000054580006     131076 0000000000020004      31074
       110 SQL*Net message from client    1650815232 0000000062657100          1 0000000000000001          0

* lock info

ADDR             KADDR                   SID TY        ID1        ID2      LMODE    REQUEST      CTIME      BLOCK
---------------- ---------------- ---------- -- ---------- ---------- ---------- ---------- ---------- ----------
000004017E512348 000004017E512368         92 TX     131076      31074          0          6        144          0
000004017BBA0DC0 000004017BBA0DE8        110 TM     107693          0          3          0        150          0
000004017BBA0EC0 000004017BBA0EE8         92 TM     107693          0          3          0        144          0
000004017BC45998 000004017BC459D0        110 TX     131076      31074          6          0        150          1

wait event를 보면

SID 110이 "delete employee where empno = 1"을 수행해서 employee table에 대한 TM lock과 empno가 1인 row에 대한 TX lock을 획득하고 있습니다. 물론 commit을 하지 않아 그 상태가 계속 유지가 되죠.

SID 92는 "deleteEmployee(1)"을 수행해 empno가 1인 row를 지우려 하지만 이미 해당 row에 대한 lock을 SID 110이 갖고 있어 이를 기다리게 됩니다. 이 상태에서 해당 procedure는 현재 수행 중이기 때문에 library cache pin을 갖고 있게 됩니다.

SID 67은 deleteEmployee procedure를 수정하려 하기 때문에 관련 library cache lock과 pin이 필요하게 됩니다. 그러나 library cache pin은 현재 SID 92에서 갖고 있기 때문에 이를 기다려야 합니다.

SID 36은 "deleteEmployee(2)"를 수행하려 합니다. 그러나 deleteEmployee procedure를 수행하기 위해서는 library cache lock에 대한sahred lock을 할당 받아야 합니다.

우선 wait event에 있는 library cache handle 먼저 찾아 보겠습니다. (library cache lock/pin event는 p1 값이 library cache handle을, p2 값이 각각 library cache lock/pin address를 가리킵니다.)

아래 그림을 보시면 SCOTT.DELETEEMPLOYEE library cache object의 lock과 pin의 holder, waiter 정보를 확인할 수 있습니다. 그리고 DEPENDENCIES 항목을 보면 library cache object의 관련 object들도 list-up 된 것을 확인 할 수 있습니다.



library cache dump 등을 장애때 직접 수행해서 관련 정보를 분석할 일은 별로 없긴 하지만,
이를 통해 조금 더 오라클 내부 구조나 hang/lock 현상 등을 좀더 잘 이해 할 수 있는 과정이 될 수 있지 않을까 생각합니다.




oracle의 logminer는 말그대로 redo log 혹은 archive file내에서 사용된 sql을 캐내는 툴이다. 

가끔 operation 실수나 해킹(?)으로 인해 데이터 이상이 생겼을 때 어떤 시점에 어떤 operation이 발생했고 그에 대한 undo sql이 무엇인지 등을 보여 준다.

1. log 생성 database 정보의 내부 정보 수집.
exec dbms_logmnr_d.build('logmnr_dic.ora','/home');

2. 추출할 archive file이나 redo log file  지정. 
exec dbms_logmnr.add_logfile('/archive/archive_1_1000.arch',dbms_logmnr.NEW);
exec dbms_logmnr.add_logfile('/archive/archive_1_1001.arch',dbms_logmnr.ADDFILE);

3. logminer 시작.
exec dbms_logmnr.start_logmnr(dictfilename=>'/home/logmnr_dic.ora');

4. 정보 추출. 
v$logmnr_contents

 위의 작업은 한 세션에서만 해당 내용을 볼 수 있다. 즉 위에서 발생하는 데이터는 오라클 데이터베이스 내에 저장되는 것이 아니라 데이터베이스 외부에서 발생하고 보여주는 데이터 이다..

더 자세한 내용은 요기서..

Oracle Logminer의 활용 Tips

[추가] oracle 10g에서 online catalog를 사용하는 방법이다.

1. add Archive Files which were analyzed 
execute DBMS_LOGMNR.ADD_LOGFILE (LOGFILENAME => '/a01b/TGDOM/arch/TGDOM_1_217_630787281.dbf',OPTIONS => DBMS_LOGMNR.NEW); 
execute DBMS_LOGMNR.ADD_LOGFILE (LOGFILENAME => '/a01b/TGDOM/arch/TGDOM_1_218_630787281.dbf',OPTIONS => DBMS_LOGMNR.ADDFILE); 
execute DBMS_LOGMNR.ADD_LOGFILE (LOGFILENAME => '/a01b/TGDOM/arch/TGDOM_1_219_630787281.dbf',OPTIONS => DBMS_LOGMNR.ADDFILE);

2. Start LogMiner With  Online Catalog 
SQL> EXECUTE DBMS_LOGMNR.START_LOGMNR(OPTIONS => DBMS_LOGMNR.DICT_FROM_ONLINE_CATALOG);
PL/SQL procedure successfully completed.

3. Query v$logminr_contents 
SQL> select username, operation, SQL_REDO,SQL_UNDO FROM V$LOGMNR_CONTENTS where SEG_OWNER='SCOTT';
no rows selected

4. End LogMiner 
SQL> SQL> execute dbms_logmnr.end_logmnr ;
PL/SQL procedure successfully completed.


  


10. 8. 16 작성자: April C Sims의 Oracle High Availability

New error logging facility in 11g…spool errors from a table that traps them. Table is automatically created after using the SET command in SQLPLUS.

Data stored in the sperrorlog persists between sessions and whether it is interactive or non-interactive such as a script.

Should you turn on errorlogging for SYS?  Sort of a catchall auditing for errors not ordinarily trapped? I am turning on sperrorlog for SYS in a non-production 11gR2 database to see what happens.

New 11g SQLPLUS Parameter ERRORLOGGING [ID 471066.1]


11g에서는 sqlplus에서 발생하는 에러를 logging 할 수 있다는 군요. 
(이 사람들은 새로운 버전 나올때 마다 모든 메뉴얼을 다 보는 것일까요? 존경스럽습니다.. )

위의 내용보고 따라해 봤습니다..

$ sqlplus system
SQL> set errorlogging on
SQL> select * from sperrorlog;

no rows selected

SQL> select * from scott.emp;
select * from scott.emp
                    *
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> set pagesize 200
SQL> select * from sperrorlog;

USERNAME
--------------------------------------------------------------------------------
TIMESTAMP
---------------------------------------------------------------------------
SCRIPT
--------------------------------------------------------------------------------
IDENTIFIER
--------------------------------------------------------------------------------
MESSAGE
--------------------------------------------------------------------------------
STATEMENT
--------------------------------------------------------------------------------
SYSTEM
2010-09-17-13.53.55.000000


ORA-00942: table or view does not exist
select * from scott.emp

잘 되는 군요..
sqlplus를 재 접속해도 위의 에러정보는 계속 남습니다. 

그러면 purge는 어떻게 할까요? 
따로 나와있는 내용이 없는 거 같으니, 아마 delete나 truncate로 해야하는 거 같긴한데..
요건 좀 나중에 확인해 보고..

user가 table을 만들고 이걸 지정할 수 도 있답니다. 

SQL>  SET ERRORLOGGING ON TABLE enduser_sperrorlog;

위의 블로그 한번 들러 보시지요~ ^^

사용자 table 데이터 중에서 꼭 변경되면 안되는 데이터는 반드시 있기마련입니다. 
예를 들자면, 이름과 주민등록 번호, 학생과 학번, 등등..

위의 데이터 처럼 입력은 가능해야 하나 한번 입력된 특정 column을 변경되지 않게 하기 위해서는 어떤 방법이 있을까요? 
가장 쉽게 생각할 수 있는게 trigger 입니다. 
다음은 trigger를 이용해 특정 column을 변경하지 못하게 설정하는 trigger sample 입니다. 

(사실 trigger 이외에 딱히 생각나는 방법이 없네요 --;)

1. SCOTT user의 EMP table에 trigger를 설정합니다. 

CREATE TRIGGER emp_upd_trigger
BEFORE UPDATE on emp
declare
    v_error VARCHAR2(256);
begin
    if (updating('EMPNO') or updating('ENAME'))
     then
         v_error:='You cannot update the empno';
         raise_application_error (-20999,v_error);
     end if;
end;
/

2. trigger가 만들어 졌으면 간단히 테스트를 해볼까요? 

SQL> select * from emp;

     EMPNO ENAME                          JOB                                MGR HIREDATE          SAL       COMM     DEPTNO
---------- ------------------------------ --------------------------- ---------- ---------- ---------- ---------- ----------
      7900 JAMES                          CLERK                             7698 1981-12-03        950                    30
      7902 FORD                           ANALYST                           7566 1981-12-03       3000                    20
      7934 MILLER                         CLERK                             7782 1982-01-23       1300                    10

SQL> update emp set empno = 9999 where ename = 'MILLER';
update emp set empno = 9999 where ename = 'MILLER'
       *
ERROR at line 1:
ORA-20999: You cannot update the empno or ename
ORA-06512: at "SCOTT.EMP_UPD_TRIGGER", line 7
ORA-04088: error during execution of trigger 'SCOTT.EMP_UPD_TRIGGER'

SQL> update emp set ename = 'ORACLE' where empno = 7934;
update emp set ename = 'ORACLE' where empno = 7934
       *
ERROR at line 1:
ORA-20999: You cannot update the empno or ename
ORA-06512: at "SCOTT.EMP_UPD_TRIGGER", line 7
ORA-04088: error during execution of trigger 'SCOTT.EMP_UPD_TRIGGER'

이 방법 이외에 다른 방법 아시는 분.. 
좀 알려주세요.. ^^


* 여기저기서 좋다는 책 몇권 추천합니다. 고수가 되는 그날까지.. 파이팅!!




We promised in an earlier post to cover some of the questions from the Optimizer round table discussion at Oracle Open World. Here's our first in a series of posts that will address these questions. 

Q: When moving from 10g to 11g, should hints in existing SQL be removed? 

A: I was glad to see this question at the round table, since I think this is something people often overlook when adding hints to an application. Over time, hints can become stale. You may have added a hint to a query because of a weakness in an old version of the optimizer, but the hint might no longer be necessary to get the best plan. Even worse, the hint may force a plan which is suboptimal, for a couple of reasons:

이 질문은 오라클 데이터베이스 관계자라면 매우 자주 듣는 질문이죠. 
이전 버전의 SQL에 적용된 hint를 제거하는게 맞을까요? 아님 계속 사용하는게 맞을까요? 
물론 답은 당연히 제거하라 겠죠? 

새로운 버전에서, 그리고 변경되는 데이터 상에서 해당 hint가 적정할지,
또 hint를 사용한 사람이 선택한 plan이 과연 적정할지.. 
그 hint는 새로운 버전에서 계속 존재하는지..
이러한 판단이 가능한 사람이 있을까 모르겠네요.. 

위의 포스트는 오라클 optimizer 팀 blog의 post 입니다. open world에서 질문 나온걸 정리한다네요. 
한번 읽어 보세요 ~
baseline을 이용한 plan 고정하는 방법도 나오네요..

그나저나 _optimizer_ignore_hints parameter는 언제 생긴걸까? 
이거 쓸만할 거 같은데, 확인 좀 해 봐야겠네요~


* 여기저기서 좋다는 책 몇권 추천합니다. 고수가 되는 그날까지.. 파이팅!!



이 자료는 O'Reilly에서 나온 " Mastering Oracle SQL, 2nd Edition"을 공부하면서 몰랐던 부분을 정리하고 있습니다. 
"Mastering Oracle SQL, 2nd Edition"의 저자는 Alan Beaulieu, Sanjay Mishra 이며 2004년에 나온 책이네요. 
알라딘에서 구해 볼 수 있습니다. 

HAVING 절은 항상 쓰면서 따로 메뉴얼을 읽어본적은 없다.
그래서 아마 group 함수의 결과에 대해 조건을 주는거? 이렇게 생각하고 있었는데, 
역시 메뉴얼을 읽어야해 ~ 

HAVING 절은 GROUP BY 절에 의해 나오는 나올수 있는 결과에 대해 filtering 해주는 역활을 해준다. 
즉 내가 지금까지 생각하던 거와는 달리 
group 함수의 결과 컬럼에 대해서만 조건을 주는 게 아니라 
결과 SET group by 절에 의해 나올 수 있는 결과 set 에 대해 조건을 줄 수 있다. 

다음의 예를 보면 .. 
부서별 인원수를 구하는 SQL이다.
SQL> select deptno,count(*) from emp group by deptno;

    DEPTNO   COUNT(*)
---------- ----------
        30          6
        20          5
        10          3

이 중인 인원수가 4이상인 부서를 출력한다. 즉 group 함수에 의해 계산된 값에 대한 조건이다. 
SQL> select deptno,count(*) from emp group by deptno having count(*) > 4;

    DEPTNO   COUNT(*)
---------- ----------
        30          6
        20          5

아래의 SQL은 group by 에 의해 만들어진 결과에서 deptno가 20 이상인 부서만 출력한다. 
SQL> select deptno,count(*) from emp group by deptno having deptno > 20;

    DEPTNO   COUNT(*)
---------- ----------
        30          6

그러면 group by 절에 의한 결과에 대한 having과 select 절의 where 절은 바꿔 쓸 수 있을까?
아래의 결과를 보면 having deptno > 20 과 where deptno > 20의 결과는 같다. (당연한가?)

SQL> select deptno,count(*) from emp where deptno > 20 group by deptno;

    DEPTNO   COUNT(*)
---------- ----------
        30          6

그러나 having 절에 의한 조건은 filter 처리로 계산된 결과에서 특정 조건만을 걸러내고 있으나, 
where 절의 경우 access 처리로 scan의 범위를 줄이고 있음을 알 수 있다. 

SQL> select deptno,count(*) from emp group by deptno having deptno > 20;

------------------------------------------------------------------------------------
| Id  | Operation              | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |           |     2 |     6 |     3  (34)| 00:00:01 |
|*  1 |  FILTER                |           |       |       |            |          |
|   2 |   HASH GROUP BY        |           |     2 |     6 |     3  (34)| 00:00:01 |
|   3 |    INDEX FAST FULL SCAN| EMP_IDX04 |   449 |  1347 |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("DEPTNO">20)

SQL> select deptno,count(*) from emp where deptno > 20 group by deptno;

----------------------------------------------------------------------------------
| Id  | Operation            | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |           |     2 |     6 |     1   (0)| 00:00:01 |
|   1 |  SORT GROUP BY NOSORT|           |     2 |     6 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN   | EMP_IDX03 |   193 |   579 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("DEPTNO">20)

queyr의 결과는 동일하나 access 하는 범위 자체가 틀리니 이에 대한 주의가 필요하다. 
당연히 access 범위를 줄일 수 있는 조건은 where 절에 기술하는 것이 유리할 듯 !!

근데, 테스트 하다보니 아래처럼 수행해도 결과가 잘 나오더군요. 
select는 count를 했으나 having에는 sum으로 조건을 주어도 결과가 잘 나오네요. 

SQL> select deptno,count(*) from emp group by deptno having sum(sal) > 10000;

    DEPTNO   COUNT(*)
---------- ----------
        20          5

Execution Plan
----------------------------------------------------------
Plan hash value: 3110987654

----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |     4 |    28 |    13   (8)| 00:00:01 |
|*  1 |  FILTER             |      |       |       |            |          |
|   2 |   HASH GROUP BY     |      |     4 |    28 |    13   (8)| 00:00:01 |
|   3 |    TABLE ACCESS FULL| EMP  |   449 |  3143 |    12   (0)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(SUM("SAL")>10000)

그럼 group by에 의한 수행 결과에 대해 조건을 주는 것이 아니라 
group by에 의해 나올 수 있는 결과에 대해 조건을 줄 수 있다는 거군요..

count(*)와 sum(sal)은 operation 자체가 다른데, 이걸 다 계산하고 있다는 건가? 
아니면 having에 있는 sum()이 query 수행시 참조되는 건가? 

"The HAVING clause is closely associated with the GROUP BY clause. The HAVING clause is used to put a filter on the groups created by the GROUP BY clause. If a query has a HAVING clause along with a GROUP BY clause, the result set will include only the groups that satisfy the condition specified in the HAVING clause."

네, having 절에 있는 group operation도 참조되는 게 맞습니다.. ^^

SQL> select deptno from emp group by deptno having sum(sal) > 10000;

    DEPTNO
----------
        20



* 여기저기서 좋다는 책 몇권 추천합니다. 고수가 되는 그날까지.. 파이팅!!



이 자료는 O'Reilly에서 나온 " Mastering Oracle SQL, 2nd Edition"을 공부하면서 몰랐던 부분만을 정리하고 있습니다.
"Mastering Oracle SQL, 2nd Edition"의 저자는 Alan Beaulieu, Sanjay Mishra 이며 2004년에 나온 책이네요. 
알라딘에서 구해 볼 수 있습니다. 

Oracle GROUP 함수는 다중 사용을 지원한다. 몰랐다...
간단히 말하면 GROUP 함수 안에 GROUP 함수를 사용할 수 있다. 

예를 들자면 ..

부서별 연봉 합계이다. 
SQL> select deptno,sum(sal) from emp group by deptno;

    DEPTNO   SUM(SAL)
---------- ----------
        30       9400
        20      10875
        10       8750

이 연봉 합계가 가장 높은 연봉 합은 다음처럼 구할 수 있다..

SQL> select max(salary) from (
SQL> select deptno,sum(sal) salary from emp group by deptno;

MAX(SALARY)
-----------
      10875

이걸 중첩 GROUP operation을 쓰면 다음과 같이 쓰일 수 있다. 

SQL> select max(sum(sal)) from emp group by deptno;

MAX(SUM(SAL))
-------------
        10875

SQL> select max(sum(sal)),min(sum(sal)),avg(sum(sal)) from emp group by deptno;

MAX(SUM(SAL)) MIN(SUM(SAL)) AVG(SUM(SAL))
------------- ------------- -------------
        10875          8750          9675

그럼 중첩 GROUP operation은 몇개까지 중첩될 수 있을까? 
아래 SQL이 어떤 의미인지는 모르겠지만..

일단 3개 중첩!!

SQL> select max(sum(count(sal))) from emp group by deptno;
select max(sum(count(sal))) from emp group by deptno
               *
ERROR at line 1:
ORA-00935: group function is nested too deeply

SQL> !oerr ora 935
00935, 00000, "group function is nested too deeply"
// *Cause:
// *Action:

GROUP function에 dept의 제한이 있는게 확인 된다. 
그럼 SUBQUERY를 사용하게 되면.. 

SQL> select max(a) from (
SQL>   select sum(b) a from  (
SQL>    select count(sal) b from emp group by deptno ));

    MAX(A)
----------
        14

잘 되는 군요.

다들 아시는 내용이겠지만, 저는 몰랐기 때문에 정리합니다 
-,.-



* 여기저기서 좋다는 책 몇권 추천합니다. 고수가 되는 그날까지.. 파이팅!!



보통, Test 시스템에서 가동 시스템으로 데이타를 이관하는 경우, 데이타 뿐만 아니라 통계정보도 이관하여, Test계와 가동계를 동일하게 유지하고자 하는 경우가 있읍니다. 이러한 경우,데이타는 쉽게 옮겨도 통계치는 딱히 옮기는 방법이 없습니다. 이때 아래의 방법을 사용하시면 쉽게 올길수 있읍니다.

dbms_stats.export_table_stats(
ownname   VARCHAR2,                -- schema name
tabname   VARCHAR2,                -- table name
partname  VARCHAR2 DEFAULT NULL,   -- partition name
stattab   VARCHAR2,                -- stat table name
statid    VARCHAR2 DEFAULT NULL,   -- optional identifier
cascade   BOOLEAN  DEFAULT TRUE,   -- TRUE = indexes too
statown   VARCHAR2  DEFAULT NULL); -- stat table schema

exec dbms_stats.export_table_stats(USER, 'servers', NULL, 'STAT_TAB', NULL, TRUE);

dbms_stats.import_table_stats(
ownname       VARCHAR2,
tabname       VARCHAR2,
partname      VARCHAR2 DEFAULT NULL,
stattab       VARCHAR2,
statid        VARCHAR2 DEFAULT NULL,
cascade       BOOLEAN  DEFAULT TRUE,
statown       VARCHAR2 DEFAULT NULL,
no_invalidate BOOLEAN  DEFAULT to_no_invalidate_type(get_param('NO_INVALIDATE')),
force         BOOLEAN DEFAULT FALSE);

exec dbms_stats.import_table_stats(USER, 'servers', stattab=>'STAT_TAB');

예 : SCOTT의 EMP  통계를 SCOTT2의 EMP2로 이관

1. 통계정보를 임시로 저장할 Table생성.
SQL> exec dbms_stats.create_stat_table('SCOTT','STATS');

2. 원하는 table의 통계정보를 임시 table로 이관.
SQL> exec dbms_stats.export_table_stats('SCOTT','EMP',NULL,'STATS','emp stats',TRUE);

* 참고 (updated: 2010/08/03)
"emp stats"로 중간에 space가 들어가니 에러가 나는 군요.. 이래서 직접 테스트를해봐야 한다니까...

SQL> exec dbms_stats.export_table_stats('SCOTT','EMP',NULL,'STATS','EMP_STATS',TRUE,'SCOTT');

BEGIN dbms_stats.export_table_stats('SCOTT','EMP',NULL,'STATS','EMP STATS',TRUE,'SCOTT'); END;
*
ERROR at line 1:
ORA-20001: EMP STATS is an invalid identifier
ORA-06512: at "SYS.DBMS_STATS", line 8240
ORA-06512: at "SYS.DBMS_STATS", line 9188
ORA-06512: at line 1

* 주의 (updated: 2010/08/03)
STATS table의 데이터를 qery 해보면 EMP table의 원래 소유자인 SCOTT이 C5 column에 명시되어 있습니다. 만약 다른 계정으로, 즉 SCOTT2 계정으로 통계정보를 옮기려면 STATS table의 SCOTT값을 SCOTT2로 update 해주어야 합니다. 
두개의 DB의 계정이 동일하다면 변경해 줄 필요는 없겠죠~


3. 임시 table을 export, 그리고 target db로 import.
 $exp scott/tiger tables=STATS file=expstat.dmp
 $imp scott/tiger file=expstat.dmp full=y log=implog.txt
(만약 동일 DB라면 이 과정은 생략 가능합니다.)

4. 임시 Table로 부터 통계치를 원하는 table에 넣는다.
SQL> exec dbms_stats.import_table_stats('SCOTT2','EMP2',NULL,'STATS');





기본적으로 recovery는 backup된 datafile을 가져다 놓고 backup 시점 이후의 archive log를 적용하게 됩니다. 
complete recovery의 경우 모든 archive log가 적용된후 undo가 다시 적용되는데요..

아래의 시나리오는 backup된 datafile은 없으나, 손상된 datafile 생성 시점이후 모든 archive log가 있을 경우 recovery하는 시나리오입니다. 
예전 data guard에서는 primary DB에서 datafile 생성한게 standby에 생성되지 않아 수작업으로 'alter database create datafile,..'을 수행했던거 같은데, 요즘 version에서는 되는지 모르겠네요..

Re-Creating Datafiles When Backups Are Unavailable: Scenario


If a datafile is damaged and no backup of the file is available, then you can still recover the datafile if:

  • All archived log files written after the creation of the original datafile are available

  • The control file contains the name of the damaged file (that is, the control file is current, or is a backup taken after the damaged datafile was added to the database)

    Note:

    You cannot re-create any of the datafiles for the SYSTEM tablespace by using the CREATE DATAFILE clause of the ALTER DATABASE statement because the necessary redo is not available.

To re-create a datafile for recovery:

Create a new, empty datafile to replace a damaged datafile that has no corresponding backup. For example, assume that the datafile ?/oradata/trgt/users01.dbf has been damaged, and no backup is available. The following statement re-creates the original datafile (same size) on disk2:


ALTER DATABASE CREATE DATAFILE '?/oradata/trgt/users01.dbf' AS
                               '/disk2/users01.dbf';
손상된 '?/oradata/trgt/users01.dbf'를 '/disk2/users01.dbf'로 이름을 바꿔 빈 datafile을 생성합니다. 

This statement creates an empty file that is the same size as the lost file. The database looks at information in the control file and the data dictionary to obtain size information. The old datafile is renamed as the new datafile.

Perform media recovery on the empty datafile. For example, enter:


RECOVER DATAFILE '/disk2/users01.dbf';
새로 만들어진 빈 datafile에 대해 archive log를 complete로 적용합니다.

All archived logs written after the original datafile was created must be applied to the new, empty version of the lost datafile during recovery.




* 여기저기서 좋다는 책 몇권 추천합니다. 고수가 되는 그날까지.. 파이팅!!



+ INVISIBLE index 생성 
SQL> create index emp_idx01 on emp(empno) invisible;

Index created.

+ 일반 세션에서 SQL 수행 
SQL> set autot on exp
SQL> 
SQL> select empno from emp where empno = 7839;

     EMPNO
----------
      7839

Execution Plan
----------------------------------------------------------
Plan hash value: 2872589290

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |     4 |    12   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| EMP  |     1 |     4 |    12   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("EMPNO"=7839)

+ INVISIBLE INDEX 사용 정의
SQL> alter session set optimizer_use_invisible_indexes=true;

Session altered.

SQL> select empno from emp where empno = 7839;

     EMPNO
----------
      7839


Execution Plan
----------------------------------------------------------
Plan hash value: 3956893595

------------------------------------------------------------------------------
| Id  | Operation        | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |           |     1 |     4 |     1   (0)| 00:00:01 |
|*  1 |  INDEX RANGE SCAN| EMP_IDX01 |     1 |     4 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("EMPNO"=7839)

+ INVISIBLE INDEX 사용 해제
SQL> alter session set optimizer_use_invisible_indexes=false;

Session altered.

SQL> select empno from emp where empno = 7839;

     EMPNO
----------
      7839


Execution Plan
----------------------------------------------------------
Plan hash value: 2872589290

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |     4 |    12   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| EMP  |     1 |     4 |    12   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("EMPNO"=7839)

+ OPT_PARAM hint를 통한 INVISIBLE index 사용 권한 부여 -> 실패 ?
SQL> select /*+ opt_param('optimizer_use_invisible_indexes','true') */ empno from emp where empno = 7839;

     EMPNO
----------
      7839


Execution Plan
----------------------------------------------------------
Plan hash value: 2872589290

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |     4 |    12   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| EMP  |     1 |     4 |    12   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("EMPNO"=7839)

oracle 10g R2 new feature를 보다 보니까.. (나온지가 언젠데.!!)
"restore point"라는 개념이 나오더군요.. (아래..)

테스트 함 해봤습니다.

원문 : Restore Point in Flashback Database

SQL의 savepoint라는 개념을 기억하십니까? 트랜잭션에서 savepoint를 생성한 후, 데이터를 일부 수정한 다음, 다시 savepoint를 생성하는 식으로 작업합니다. 그리고 변경된 내용을 다시 되돌리고 싶다면, 해당되는 savepoint로 롤백하면 됩니다.

Oracle Database 10g의 Flashback Database는 데이터베이스를 과거의 특정 시점으로 되돌리는 기능을 제공합니다. 그렇다면 여기에도 savepoint와 유사한 개념을 도입하면 유용하지 않을까요? 시간을 기준으로 하지 않고, 명명된 시점(named point)으로 되돌릴 수 있게 하면 좋지 않을까요?

Oracle Database 10g Release 2에 추가된 “restore point”라는 기능이 바로 이러한 역할을 담당합니다. 그 작동 원리가 다음과 같습니다.


위의 내용은 OTN의
 Oracle Database 10g Release 2의 신기능에서 가져왔습니다.

1. RESTORE POINT 생성.

SQL> create restore point before_fb_test guarantee flashback database;

ERROR at line 1:
ORA-38784: Cannot create restore point 'BEFORE_FB_TEST'.
ORA-38786: Flash recovery area is not enabled.

SQL> alter system set db_recovery_file_dest='/..' scope=both;
SQL> alter system set log_archive_dest_1='' scope=spfile;
SQL> shutdown immediate;
SQL> startup

SQL> archive log list
Database log mode   Archive Mode
Automatic archival  Enabled
Archive destination USE_DB_RECOVERY_FILE_DEST
            :


SQL> create restore point before_fb_test guarantee flashback database;

ERROR at line 1:
ORA-38784: Cannot create restore point 'BEFORE_FB_TEST'.
ORA-38787: Creating the first guaranteed restore point requires mount mode when flashback database is off.

ORA-38787: Creating the first guaranteed restore point requires mount mode when flashback database is off.
Cause: While flashback database is not on, an attempt was made to create the first guaranteed restore point while the database is open.
Action: Mount the database and retry.

SQL> shutdown immediate;
SQL> STARTUP MOUNT
SQL> ALTER DATABASE FLASHBACK ON;

ERROR at line 1:
ORA-38706: Cannot turn on FLASHBACK DATABASE logging.
ORA-38708: Not enough space for first flashback database log file

SQL> alter system set db_recovery_file_dest_size=5G scope=both;
SQL> ALTER DATABASE FLASHBACK ON;
SQL> ALTER DATABASE OPEN;

( flash backup area에 flb라는 확장자로 파일이 하나 생기는 군요.)

SQL> create restore point before_fb_test guarantee flashback database;

2. 테스트

SQL> delete from scott.emp;
SQL> commit;

* flash backup area에 flb file이 추가로 1개 더 생기는 ..

SQL> SHUTDOWN IMMEDIATE
SQL> STARTUP MOUNT
SQL> FLASHBACK DATABASE TO RESTORE POINT before_update_of_emp;
SQL> ALTER DATABASE OPEN RESETLOGS;

기본 설정만 해놓으면 잘 되는 거 같네요..




CROSS JOIN/ Cartesian Products

 - Cartesian product 값을 얻을때 사용 합니다. 
 - 즉 join을 통해 생성가능한 모든 row를 return 합니다. 

SQL>SELECT ename FROM emp CROSS JOIN dept;
SQL>SELECT ename FROM emp, dept;

참고: CROSS가 생략되어 INNER join으로 수행되는 경우(join condition이 없는 경우) 나 CROSS join에서 join condition이 기술된 경우 에러발생합니다. 

SQL>SELECT e.lname, d.name FROM employee e JOIN department d;
ORA-00905: missing keyword

SQL>SELECT e.lname, d.name FROM employee e CROSS JOIN department d ON e.dept_id = d.dept_id;
ORA-00933: SQL command not properly ended


INNER JOIN 

 - 일반 조인시 Table간의 ','를 생략하고 'INNER JOIN'을 추가하고 WHERE절대신 ON절을 사용해야 합니다.
 - INNER는 생략 가능합니다.

SQL>SELECT e.empno, e.ename FROM dept d INNER JOIN emp e ON d.deptno=e.deptno;
SQL>SELECT e.empno, e.ename FROM dept d , emp e WHERE d.deptno=e.deptno;

EQUI-JOIN/ NON-EQUI-JOIN 
 - Join condition에 equal operation(=)을 사용하는 JOIN 
 - Join condition에 equal operation 외의 다른 operation을 사용하는 JOIN

SQL> SELECT s.name supplier_name, p.name part_name FROM supplier s JOIN part p ON s.supplier_id = p.supplier_id;
SQL> SELECT p.name part_name, c.inv_class inv_class FROM part p JOIN inventory_class c ON p.unit_cost BETWEEN c.low_cost AND c.high_cost;

SELF JOIN
 - 동일 TABLE에 대한 JOIN

SQL> SELECT e.lname employee, m.lname manager FROM employee e JOIN employee m ON e.manager_emp_id = m.emp_id;

OUTER JOIN 

 - Join시 특정 table의 join 조건에 일치하지 않은 row를 추가할때 사용해야 합니다.
 - { LEFT | RIGHT | FULL } [OUTER]

SQL>SELECT e.empno, e.ename FROM dept d LEFT OUTER JOIN emp e ON d.deptno=e.deptno;
SQL>SELECT e.empno, e.ename FROM dept d , emp e WHERE d.deptno=e.deptno(+);

SQL>SELECT e.empno, e.ename FROM dept d RIGHT OUTER JOIN emp e ON d.deptno=e.deptno;
SQL>SELECT e.empno, e.ename FROM dept d , emp e WHERE d.deptno(+)=e.deptno;

SQL>SELECT e.empno, e.ename FROM dept d FULL OUTER JOIN emp e ON d.deptno=e.deptno;

참고: full outer join의 비밀 [Science of DataBase]


PARTITION OUTER JOIN 

 - PARTITION OUTER JOIN을 사용하여 분석 대상이 되는 디멘션의 densification을 수행할 수 있으며, 
 - 파티션이나 테이블 내부에서 OUTER JOIN을 적용할 수 있습니다.

select hiredate,d.dname,  nvl(sum_sal,0) sum_sal
from dept d
left outer join (select deptno,to_char(hiredate,'YYYY') hiredate, sum(sal) sum_sal from emp group by deptno,to_char(hiredate,'YYYY')) e
partition by (e.hiredate)
on d.deptno = e.deptno
order by 1,2
/

HIREDATE     DNAME                                         SUM_SAL
------------ ------------------------------------------ ----------
1980         ACCOUNTING                                          0
1980         OPERATIONS                                          0
1980         RESEARCH                                          800
1980         SALES                                               0
1981         ACCOUNTING                                       7450
1981         OPERATIONS                                          0
1981         RESEARCH                                         5975
1981         SALES                                            9400
1982         ACCOUNTING                                       1300
1982         OPERATIONS                                          0
1982         RESEARCH                                         3000
1982         SALES                                               0
1983         ACCOUNTING                                          0
1983         OPERATIONS                                          0
1983         RESEARCH                                         1100
1983         SALES                                               0



NATURAL JOIN 

 - Equijoin과 동일하다고 보시면 됩니다.
 - 두 테이블의 동일한 이름을 가지는 칼럼은 모두 조인이 됩니다. (oracle forum : HR유저에서 NATURAL JOIN 결과가 다릅니다 )
 - 동일한 컬럼을 내부적으로 찾게 되므로 테이블 Alias를 주면 오류가 발생 합니다. 
    (ORA-25155: column used in NATURAL join cannot have qualifier)
 - 동일한 컬럼이 두개 이상일 경우 JOIN~USING문장으로 조인되는 컬럼을 제어 할 수 있습니다.
 
SQL>SELECT empno, ename, deptno FROM emp NATURAL JOIN dept 
SQL>SELECT e.empno, e.ename, d.deptno FROM emp e, dept d WHERE e.deptno=d.deptno


JOIN ~ USING 

 - NATURAL JOIN의 단점은 동일한 이름을 가지는 칼럼은 모두 조인이 되었는데 USING 문을 사용하면 컬럼을 선택해서 조인을 할 수가 있습니다.
 - USING절 안에 포함되는 컬럼에 Alias를 지정하면 오류가 발생 합니다.
  
SQL>SELECT e.empno, e.ename, deptno FROM emp e JOIN dept d USING(deptno)


ON 구문

 - 조인 조건을 지정 할 수 있습니다. 
 - 모든 논리 연산 및 서브쿼리를 지정할 수 있습니다.

SQL>SELECT e.empno, e.ename, e.sal
       FROM emp e JOIN dept    d  ON (e.deptno=d.deptno)
                           JOIN bonus  b ON (b.ename = e.ename)
       WHERE e.sal IS NOT NULL




* 여기저기서 좋다는 책 몇권 추천합니다. 고수가 되는 그날까지.. 파이팅!!



옵티마이저는 실행 계획의 비용을 계산하기 위한 비용 모델(Cost Model)을 갖고 있고, 이 비용 모델은 Oracle Data Dictionary에서 관리하는 다양한 통계정보를 기반으로 크게 다음과 같은 세 가지 값(measure)의 예상치를 계산한다.

Q1:
select ename, sal
from emp e, dept d
where e.deptno = d.deptno and d.loc = ‘SEOUL’

선택도

우선 선택도(selectivity)의 개념을 예로 들자. 앞에서 예로 든 질의 Q1에서 d.loc = ‘SEOUL’이라는 조건의 선택도는 dept 테이블 전체 중에서 loc의 값이 ‘SEOUL’인 레코드의 비율을 일컫는다. 옵티마이저는 선택도 계산을 통해서 해당 조건을 만족하는 레코드가 몇 건 정도가 되는지를 예측하게 된다. 옵티마이저는 만일 DBA_TABLES에 dept 테이블의 loc 칼럼의 distinct column values가 10이라면 옵티마이저는 선택도가 0.1이라고 판단하게 된다. 이때 선택도를 이와 같이 정하는 이유는 dept 테이블이 loc 칼럼들에 골고루 분포되어 있다고 가정할 때 성립한다. 그러나, 실제로 loc 칼럼의 값들이 skew되어서 분포할 수도 있다.

예를 들어, 전체 레코드의 50%가 loc 값으로‘SEOUL’을 갖는다면 잘못된 선택도 값을 얻게 된다. 이와 같이 데이타 분포가 skew되어 있는 경우, 해당 칼럼에 대한 히스토그램 정보를 DBA_HISTOGRAM 테이블에 만들어 주어야 정확한 선택도 값을 계산할 수 있다(이 경우는 0.5). 오라클 옵티마이저는 다양한 조건식의 종류에 대해 선택도를 통계정보에 기반해서 계산하는 수식을 내부적으로 갖고 있다. 그렇지만, 만일 dept 테이블이 아직 분석되지 않아서 통계정보가 없는 경우, 옵티마이저는 내부적으로 갖고 있는 디폴트 값을 선택도로 지정한다(예를 들어, 0.01).

카디널러티

앞의 dept 테이블의 전체 레코드 건수가 1000일 때, 앞에서 설명한 loc = ‘SEOUL’의 선택도가 0.1로 계산되었을 때, 조건을 만족하는 레코드 건수는 1000 x 0.1, 즉 100개로 예상할 수 있다. 이와 같이 어떤 연산을 수행한 결과로 나오는 레코드 건수를‘카디널러티(cardinality)’라 하는데, 정확한 카디널러티를 계산하는  것은 좋은 실행 계획을 만드는 데 굉장히 중요하다.

예를 들어, (T1§_T2)§_T3 순서로 테이블을 조인할 때 (T1§_T2)의 결과와 T3를 조인할 때 어떤 조인 방법을 선택하는 것이 좋을지를 결정하기 위해서는 (T1§_T2)의 크기를 정확하게 알아야 한다. 이를 위해서는 (T1§_T2) 조인의 결과 레코드가 몇 개인지를 예상할 수 있어야 한다. 이를 위해 오라클 옵티마이저는 다양한 연산의 결과 레코드의 카디널러티를 통계정보와 수식에 의해서 계산한다. T1과 T2의 조인 조건이 T1.c1 = T2.c2(이를‘P’라
표기)라 했을 때, 앞에서 설명한 선택도 계산 공식에 의해 이 조건식의 선택도 Sel(P)를 먼저 계산한 후, 이 조인의 결과 카디널러티는 Card(T1) x Card(T2) x Sel(P)가 된다.

예를 들어, T1, T2의 튜플 수가 각각 1000, 5000이고 Sel(P)가 0.01이면, 조인의 결과로 생기는 튜플 수는 1000 x 5000 x 0.01 = 5000이 된다. 그런데, Sel(P)가 조금이라도 틀리면 이후의 전체적인 비용 산정이 잘못되게 된다. 오라클 옵티마이저는 다양한 종류의 연산에 대해 내부 공식을 사용해 카디널러티를 계산한다.

비용

비용(cost)은 테이블 액세스, 조인 등을 수행하는 데 걸리는 시간을 의미하는데, 시간은 주로 디스크 I/O 수와 CPU 사용시간을 고려한다. 비용은 앞에서 계산한 통계 정보와 내부 계산식에 의해 계산된다.

예를 들어, T1§_T2를 Nested Loop 방식으로 조인할 경우 조인비용은 (T1의 데이타 블록수) + ((T1의 레코드 건수)*(T2의 액세스 비용))이 된다. 이처럼 오라클 옵티마이저는 모든 연산에 대해 소요되는 비용을 계산하는 수식을 갖고 있다. 오라클 옵티마이저는 이 세 가지 예상 값(measure)을 기반으로, 현재의 실행 계획의 예상 비용을 구한다.

위 내용은 아래 첨부된 "Oracle Technical Note : 오라클 옵티마이저의 기본 원리"의 일부를 정리한 내용입니다.



* 여기저기서 좋다는 책 몇권 추천합니다. 고수가 되는 그날까지.. 파이팅!!



가끔 문제가 있어 alert log file이나 event 설정해 놓은 걸 보면 trace file이 있을거로 예상 되어지는데, 실제 trace 파일 등이 없는 경우가 있습니다. 대부분 부족한 filesystem 때문에 oracle home 내의 trace file을 주기적으로 정리하느라 없어지는 경우가 대부분이죠. 
 
이럴 경우, unix 상에서 trace 파일을 지우면 그 trace 파일을 오픈하고 있는 process는 계속해서 write를 할 수 있기때문에, 결국은 trace file에 열심히 기록을 해 놓아도 볼 수 없게 됩니다.  결국은 내렸다 올리면 process들이 trace file을 새로 생성하게되니 문제가 해결되지만, oradebug를 사용하면 process를 내리지 않고 trace file을 다시 생성하게 할 수 있습니다. 
 
oradebug setospid <ospid>
oradebug close_trace

위의 명령을 수행하면 현재까지 process가 잡고 있던 trace file을 close 하게 되고 이 후 process가 trace file에 기록할 내용이 발생하면 trace file을 새로 생성하고 기록하게 됩니다. 물론 이미 존재하는 trace file이 있다면 마지막 부분에 file의 header를 모두 기록하고 시작하게 됩니다.



* 여기저기서 좋다는 책 몇권 추천합니다. 고수가 되는 그날까지.. 파이팅!!



10g 부터는 Oracle에서 자동으로 optimizer statistic 통계정보를 수집하게 됩니다.
11g에는 scheduler job에서 automated task로 변경된 거 같은데,
모두 마찬가지로 10% 이상의 변경을 대상으로 수집합니다. (11g 두? .. 음)

다음의 SQL은 dba_tab_modifications table에서 DML에 의해 변경된 건수와 dba_tables의 num_rows를 비교해
10% 이상 변경된 table을 조회하는 SQL 문입니다.

select TABLE_OWNER,m.TABLE_NAME,num_rows,round(num_rows/10,0) "NUM_ROWS/10",inserts+updates+deletes "tot_changed",
INSERTS,UPDATES,DELETES,TIMESTAMP,LAST_ANALYZED,round(LAST_ANALYZED-TIMESTAMP,0) GAP
from dba_tab_modifications  m, dba_tables t
where table_owner not in ('SYS')
and t.table_name = m.table_name
and t.owner = m.table_owner
and round(num_rows/10,0) - (inserts+updates+deletes) < 0
order by LAST_ANALYZED-TIMESTAMP
/

UPDATE (2010/07/05)

아래 블로그는 제가 구독하는 Oracle 관련 blog 입니다.
dba_tab_modifications에 대해 자세한 설명과 test 결과를 post 해 놓았네요. dba_tab_modifications의 데이터가 즉각적으로 보이지 않던데, 
flush를 해줘야 하는 군요.. 움~

exec DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO








Database system 운영 시 listener 관련 장애들은 크게 두가지입니다.
listener가 죽던지, 멍청하게 멈춰서 아무일도 못하든지.. 
다음은 listener가 멍청하게 멈춰서 아무일도 못하는 경우에 문제 해결을 위해 정보를 수집하는 방법입니다.


hang 문제가 발생하면 가장 손쉽게 접근할 수 있는 방법이 listener에 log를 설정해서 문제가 발생할 때 어떤 일이 있었는지를 확인하는 방법입니다.
하지만 문제가 언제쯤 발생할지 모르는 상태라면 log의 설정은 문제 발생 전까지 끊임 없는 관리가 필요합니다. 
클라이언트에서 접속하는 모든 정보까지 log에 저장하게 되므로 관리해 놓지 않으면 나중에 log file을 vi로 열기도 힘들어 집니다. 
따라서 매일 (혹은 몇일마다) listener log를 flush 하기 위해 listener restart(reload) 작업을 해주면서 log file을 관리해야 합니다. 

다음의 방법은 log 설정하지 않고 문제가 발생하는 시점에 인지를 할 수 있다면 다소 유용할 수 있는 방법입니다. 
즉, hang 상태가 발생했을때 어떤 O/S system call에서 멈춰있는지를 확인할 수 있는 두가지 방법입니다. 

process의 stack 정보를 확인하는, PSTACK 명령과 GDB 명령입니다. 

1. pstack <listener_pid> 
2. gdb $ORACLE_HOME/bin/tnslsnr <listener_pid>
   (gdb) info thread
   (gdb) thread 1
   (gdb) where
   (gdb) 반복 (thread 숫자만큼)
   (gdb) quit


위와 같이 수행하는 경우 listener가 어떤 call stack에서 멈춰있는지를 확인할 수 있습니다. 
이렇게 call stack을 수집하는 이뉴는 발생한 문제가 이미 보고된 bug과 동일한 문제가 있는지의 확인이 쉽기 때문입니다. 

만약 call stack으로 bug을 확인했을때 비슷한 문제가 없다면, 새로 분석을 진행해야 하니 
listener log의 설정을 다시 요구할 수도 있습니다..만 



* 여기저기서 좋다는 책 몇권 추천합니다. 고수가 되는 그날까지.. 파이팅!!





데이터 파일의 저장공간을 넉넉히 갖고 운영할 수 있으면 좋겠지만, 대부분의 사이트 들은 한정된 disk 공간으로 운영을 합니다.  Disk의 가격도 만만치 않거니와 향후 발생할 데이터 양을 너무 빡빡하게 계산해 놓으면 이 한정된 disk를 알뜰하게 DBA들이 사용해야 하죠. 

Disk의 공간을 줄이기 위해 compress table이나 table re-org 작업, tablespace shrink 작업들을 하게되는데 오늘은 Bulletin 10165의 내용을 정리합니다. 

tablespace shrink 작업은 extent가 할당되지 않은 공간 중 마지막 extent 부분 부터 줄일 수 있습니다. 즉, tablespace가 처음 할당 되고 얼마 되지 않은 table 들이 생성됬을 경우 꽤 짭짭할 공간을 줄일 수 있겠지만, 이미 사용한지 좀 된 tablespace의 경우에는 중간중간 extent가 할당되어 얼마 못 줄이게 됩니다. 이런 경우에는 table을 drop하고 재 생성하는 등의 re-org 작업이 선행 되거나 tablespace를 이리저리 건너 뛰면서 줄여야 됩니다. 

그럼 tablespace를 resize 할 수 있는 max size를 어떻게 구할 수 있을까요? 

dba_extents라는 view를 보면 datafile 별로 extent가 할당된 정보가 나옵니다. 이 할당된 extent의 마지막 위치를 파악해서 바로 그 이후로 size를 지정하면 됩니다.  다음의 공식은 bulletin 10165에 나와 있는 공식입니다. 

1) 줄이고자하는 file id를 확인합니다.
SQLPLUS> select file_id, file_name from dba_data_files;

2) 얼마를 사용했는지 확인합니다.
SQLPLUS> select block_id, blocks from dba_extents where file_id='FILE_ID' order by block_id;

FILE_ID 대신에 줄이려는 file id를 기술합니다.

3)현재 사용중이 db block size를 확인합니다.
SQLPLUS> show parameter db_block_size

2)에서 조회된 마지막의 block_id, blocks 값을 가지고 계산합니다. 
block_id * db_block_size + blocks * db_block_size 의 결과에 한 block더한 값만큼만 줄이는 것이 가능합니다. 

만일 마지막으로 조회된 값이 block_id가 578261, blocks가 515, db_block_size가 2048 일 경우  
578261*2048 + 515*2048 =1185333248로 최소한 1.2GB이상은 써야 합니다. 

4) 실제 datafile을 줄입니다.
SQLPLUS>alter database datafile '/oracle/dbs/toolsORA.dbf' resize 1200M;



* 여기저기서 좋다는 책 몇권 추천합니다. 고수가 되는 그날까지.. 파이팅!!


+ Recent posts