원문 : http://use-the-index-luke.com/sql/explain-plan/db2/getting-an-execution-plan


DB2의 실행계획을 처음 접하는 사람들은 아스키 아트 형태의 실행계획을 잘 읽지 못한다.

사실 이렇게 보기 좋게 실행계획을 아스키 아트로 만들어준 DB2 개발자들의 노력에 감사하지만
쿼리가 조금만 길어져도 한 화면에 실행계획 트리가 다 나오지 않아, 그 노력이 무색하다.


DB2에서는 db2expln, db2exfmt, SYSTOOLS.EXPLAIN* 세 종류 방식으로 실행계획을 조회해볼 수 있는데
이 중 SYSTOOLS.EXPLAIN*을 사용하여, 비교적 오라클 스타일의 실행계획을 보여주는 View가 있어서 소개한다.


1. 다음 프로시저를 실행하여 EXPLAIN 정보를 담을 SYSTOOLS.EXPLAIN_STREAM 테이블을 생성한다.


db2 "CALL SYSPROC.SYSINSTALLOBJECTS('EXPLAIN', 'C', CAST (NULL AS VARCHAR(128)), CAST (NULL AS VARCHAR(128)))"


2. 다음 프로시저를 생성한다.


db2 -tvm +p <<EOF
-- Copyright (c) 2014-2017, Markus Winand - NO WARRANTY
-- Modifications by Ember Crooks - NO WARRANTY
-- Info & license: http://use-the-index-luke.com/s/last_explained
--
--#SET TERMINATOR ;

CREATE OR REPLACE VIEW last_explained AS
WITH tree(operator_ID, level, path, explain_time, cycle)
AS
(
SELECT 1 operator_id
     , 0 level
     , CAST('001' AS VARCHAR(1000)) path
     , max(explain_time) explain_time
     , 0
  FROM SYSTOOLS.EXPLAIN_OPERATOR O
 WHERE O.EXPLAIN_REQUESTER = SESSION_USER

UNION ALL

SELECT s.source_id
     , level + 1
     , tree.path || '/' || LPAD(CAST(s.source_id AS VARCHAR(3)), 3, '0')  path
     , tree.explain_time
     , POSITION('/' || LPAD(CAST(s.source_id AS VARCHAR(3)), 3, '0')  || '/' IN path USING OCTETS)
  FROM tree
     , SYSTOOLS.EXPLAIN_STREAM S
 WHERE s.target_id    = tree.operator_id
   AND s.explain_time = tree.explain_time
   AND S.Object_Name IS NULL
   AND S.explain_requester = SESSION_USER
   AND tree.cycle = 0
   AND level < 100
)
SELECT *
  FROM (
SELECT "Explain Plan"
  FROM (
SELECT CAST(   LPAD(id,        MAX(LENGTH(id))        OVER(), ' ')
            || ' | '
            || RPAD(operation, MAX(LENGTH(operation)) OVER(), ' ')
            || ' | '
            || LPAD(rows,      MAX(LENGTH(rows))      OVER(), ' ')
            || ' | '
            -- Don't show ActualRows columns if there are no actuals available at all
            || CASE WHEN COUNT(ActualRows) OVER () > 1 -- the heading 'ActualRows' is always present, so "1" means no OTHER values
                    THEN LPAD(ActualRows, MAX(LENGTH(ActualRows)) OVER(), ' ') || ' | '
                    ELSE ''
               END
            || LPAD(cost,      MAX(LENGTH(cost))      OVER(), ' ')
         AS VARCHAR(100)) "Explain Plan"
     , path
  FROM (
SELECT 'ID' ID
     , 'Operation' Operation
     , 'Rows' Rows
     , 'ActualRows' ActualRows
     , 'Cost' Cost
     , '0' Path
  FROM SYSIBM.SYSDUMMY1
-- TODO: UNION ALL yields duplicate. where do they come from?
UNION
SELECT CAST(tree.operator_id as VARCHAR(254)) ID
     , CAST(LPAD(' ', tree.level, ' ')
       || CASE WHEN tree.cycle = 1
               THEN '(cycle) '
               ELSE ''
          END    
       || COALESCE (
             TRIM(O.Operator_Type)
          || COALESCE(' (' || argument || ')', '')
          || ' '
          || COALESCE(S.Object_Name,'')
          , ''
          )
       AS VARCHAR(254)) AS OPERATION
     , COALESCE(CAST(rows AS VARCHAR(254)), '') Rows
     , CAST(ActualRows as VARCHAR(254)) ActualRows -- note: no coalesce
     , COALESCE(CAST(CAST(O.Total_Cost AS BIGINT) AS VARCHAR(254)), '') Cost
     , path
  FROM tree
  LEFT JOIN ( SELECT i.source_id
              , i.target_id
              , CAST(CAST(ROUND(o.stream_count) AS BIGINT) AS VARCHAR(12))
                || ' of '
                || CAST (total_rows AS VARCHAR(12))
                || CASE WHEN total_rows > 0
                         AND ROUND(o.stream_count) <= total_rows THEN
                   ' ('
                   || LPAD(CAST (ROUND(ROUND(o.stream_count)/total_rows*100,2)
                          AS NUMERIC(5,2)), 6, ' ')
                   || '%)'
                   ELSE ''
                   END rows
              , CASE WHEN act.actual_value is not null then
                CAST(CAST(ROUND(act.actual_value) AS BIGINT) AS VARCHAR(12))
                || ' of '
                || CAST (total_rows AS VARCHAR(12))
                || CASE WHEN total_rows > 0 THEN
                   ' ('
                   || LPAD(CAST (ROUND(ROUND(act.actual_value)/total_rows*100,2)
                          AS NUMERIC(5,2)), 6, ' ')
                   || '%)'
                   ELSE NULL
                   END END ActualRows
              , i.object_name
              , i.explain_time
         FROM (SELECT MAX(source_id) source_id
                    , target_id
                    , MIN(CAST(ROUND(stream_count,0) AS BIGINT)) total_rows
                    , CAST(LISTAGG(object_name) AS VARCHAR(50)) object_name
                    , explain_time
                 FROM SYSTOOLS.EXPLAIN_STREAM
                WHERE explain_time = (SELECT MAX(explain_time)
                                        FROM SYSTOOLS.EXPLAIN_OPERATOR
                                       WHERE EXPLAIN_REQUESTER = SESSION_USER
                                     )
                GROUP BY target_id, explain_time
              ) I
         LEFT JOIN SYSTOOLS.EXPLAIN_STREAM O
           ON (    I.target_id=o.source_id
               AND I.explain_time = o.explain_time
               AND O.EXPLAIN_REQUESTER = SESSION_USER
              )
         LEFT JOIN SYSTOOLS.EXPLAIN_ACTUALS act
           ON (    act.operator_id  = i.target_id
               AND act.explain_time = i.explain_time
               AND act.explain_requester = SESSION_USER
               AND act.ACTUAL_TYPE  like 'CARDINALITY%'
              )
       ) s
    ON (    s.target_id    = tree.operator_id
        AND s.explain_time = tree.explain_time
       )
  LEFT JOIN SYSTOOLS.EXPLAIN_OPERATOR O
    ON (    o.operator_id  = tree.operator_id
        AND o.explain_time = tree.explain_time
        AND o.explain_requester = SESSION_USER
       )
  LEFT JOIN (SELECT LISTAGG (CASE argument_type
                             WHEN 'UNIQUE' THEN
                                  CASE WHEN argument_value = 'TRUE'
                                       THEN 'UNIQUE'
                                  ELSE NULL
                                  END
                             WHEN 'TRUNCSRT' THEN
                                  CASE WHEN argument_value = 'TRUE'
                                       THEN 'TOP-N'
                                  ELSE NULL
                                  END  
                             WHEN 'SCANDIR' THEN
                                  CASE WHEN argument_value != 'FORWARD'
                                       THEN argument_value
                                  ELSE NULL
                                  END                    
                             ELSE argument_value    
                             END
                           , ' ') argument
                  , operator_id
                  , explain_time
               FROM SYSTOOLS.EXPLAIN_ARGUMENT EA
              WHERE argument_type IN ('AGGMODE'   -- GRPBY
                                     , 'UNIQUE', 'TRUNCSRT' -- SORT
                                     , 'SCANDIR' -- IXSCAN, TBSCAN
                                     , 'OUTERJN' -- JOINs
                                     )
                AND explain_requester = SESSION_USER
              GROUP BY explain_time, operator_id

            ) A
    ON (    a.operator_id  = tree.operator_id
        AND a.explain_time = tree.explain_time
       )
     ) O
UNION ALL
VALUES ('Explain plan (c) 2014-2017 by Markus Winand - NO WARRANTY - V20171102','Z0')
    ,  ('Modifications by Ember Crooks - NO WARRANTY','Z1')
    ,  ('http://use-the-index-luke.com/s/last_explained','Z2')
    ,  ('', 'A')
    ,  ('', 'Y')
    ,  ('Predicate Information', 'AA')
UNION ALL
SELECT CAST (LPAD(CASE WHEN operator_id = LAG  (operator_id)
                                          OVER (PARTITION BY operator_id
                                                    ORDER BY pred_order
                                               )
                       THEN ''
                       ELSE operator_id || ' - '
                  END
                , MAX(LENGTH(operator_id )+4) OVER()
                , ' ')
             || how_applied
             || ' '
             || predicate_text
          AS VARCHAR(100)) "Predicate Information"
     , 'P' || LPAD(id_order, 5, '0') || pred_order path
  FROM (SELECT CAST(operator_id AS VARCHAR(254)) operator_id
             , LPAD(trim(how_applied)
                  ,  MAX (LENGTH(TRIM(how_applied)))
                    OVER (PARTITION BY operator_id)
                  , ' '
               ) how_applied
               -- next: capped to length 80 to avoid
               -- SQL0445W  Value "..." has been truncated.  SQLSTATE=01004
               -- error when long literal values may appear (space padded!)
             , CAST(substr(predicate_text, 1, 80) AS VARCHAR(80)) predicate_text
             , CASE how_applied WHEN 'START' THEN '1'
                                WHEN 'STOP'  THEN '2'
                                WHEN 'SARG'  THEN '3'
                                ELSE '9'
               END pred_order
             , operator_id id_order
          FROM systools.explain_predicate p
         WHERE explain_time = (SELECT MAX(explain_time)
                                 FROM systools.explain_operator)
       )
)
ORDER BY path
);
EOF



3. 플랜 조회할 쿼리를 explain 뜬다.


db2 "explain plan for select * from sysibm.sysdummy1"



4. 오라클 스타일 플랜을 조회한다.

db2 "select * from last_explained"



Posted by in0de
,

데이터를 export 하여 가령 다른 서버로 전송한 후 sqlloader로 import할 때

특히 네트워크 속도가 좋지 않은 경우

덤프파일을 압축하면 전체적으로 시간을 절감할 수 있다.


데이터를 export할 공간이 넉넉하지 않은 경우에도 도움이 될 수 있다.



데이터 export 수행할 쿼리 파일을 생성한다.

-- in export.sql
SET term on
SET echo off
SET UNDERLINE off
SET HEADSEP off
SET TRIMSPOOL on
SET TRIMOUT on
SET COLSEP ","
SET SQLPROMPT ''
SET PAGESIZE 0
SET LINESIZE 32767
SET LONG 999999999
SET TERMOUT OFF
SET FEEDBACK OFF

SPOOL export.pipe;
alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss';
SELECT

    col1

    ||'@@'|| col2

    ||'@@'|| col3

    ||'@@'|| col4

    ||'@@'||  col5
FROM TBL WHERE 1=1;
SPOOL OFF;
quit;
/

맨 마지막에 quit; / 부분이 없으면, 쿼리를 수행한 후 sqlplus가 종료되지 않고 멍하니 멈춰있게 되므로 유의한다.


sqplus로 데이터 export → pipe 파일 → gzip 으로 direct하여 export와 동시에 압축되도록 한다.

rm -f ./export.pipe

mkfifo ./export.pipe
chmod 770 ./export.pipe
cat ./export.pipe | gzip > ./export.csv.gz &
sqlplus -s ${DBUSER}/${password} @export.sql
rm -f ./export.pipe




반대로, 이 파일을 sqlload할 때는 다음과 같이 수행한다.

-- in import.ctl

LOAD DATA
INFILE import.pipe
BADFILE import.bad
APPEND INTO TABLE TBL
FIELDS TERMINATED BY "@@"
TRAILING NULLCOLS
(

col1

,col2

,col3

,col4 CHAR           -- load하는 타겟 테이블의 칼럼이 VARCHAR2 칼럼일 경우 굳이 명시하자면

,col5 INTEGER       -- load하는 타겟 테이블의 칼럼이 NUMBER일 경우

)


rm -f ./import.pipe

mkfifo ./import.pipe
chmod 770 ./import.pipe
gunzip < ./export.csv.gz > ./import.pipe &

sqlldr userid=${DBUSER}/${password} control=import.ctl errors=100 rows=5000

rm -f ./import.pipe

INFILE을 지정할 때 현재 디렉토리임을 명시하려고 ./import.pipe로 쓰면 오류가 발생하므로

그냥 파일이름만 써줘야 한다.

마찬가지로 sqlldr 커맨드에서 control 파일을 control=./import.ctl 로 지정하면 오류 발생한다.


컨트롤 파일을 통해 여러 옵션을 조정할 필요가 없다면

간단히 sqlldr userid=${DBUSER}/${password} data=import.pipe 로 수행할 수도 있다.

5000건 단위로 commit을 위해 rows=5000을 지정했지만, 실제 수행될 때는 5천 건 단위로 커밋을 찍지 않는데, 이런 경우에는 bindsize 옵션을 추가해볼 수 있다.

Posted by in0de
,

Undo segment를 과도하게 사용하여

예를 들어, 개발한 배치, 개발자의 임의 작업 등이 잘못 진행되고 있다가 뒤늦게 발견하여 cancel했을 경우라던가 할 때

이를 rollback을 하는 중이지만 UNDO tablespace의 unexpire 영역이 반환되지 않아,

정규 배치 등의 서비스가 실패할 가능성이 있는 시나리오에 대응하려면

다음 쿼리들을 통해 문제를 해결하도록 한다.



Rollback 진행 중인 undo segment를 조회하는 쿼리


parallel rollback 일 경우

select * from v$fast_start_transactions where state='RECOVERING';


serial rollback 일 경우 (x$KTUXE(Kernel Transaction Undo Transaction Entry))

select ktuxesiz from sys.xm$ktuxe where ktuxesta = 'ACTIVE' and KTUXECFL = 'DEAD' ;


Undo tablespace의 사용량은 다음과 같이 확인한다.

col msg format a80
WITH TB AS (
    SELECT TABLESPACE_NAME
          ,(SELECT ROUND(SUM(BLOCKS)*8/1024) FROM DBA_DATA_FILES WHERE TABLESPACE_NAME=A.TABLESPACE_NAME) TOT_MB
          ,SUM(DECODE(A.STATUS,'ACTIVE'   ,SIZE_MB,0)) ACTIVE_MB
          ,SUM(DECODE(A.STATUS,'UNEXPIRED',SIZE_MB,0)) UNEXPI_MB
          ,SUM(DECODE(A.STATUS,'EXPIRED'  ,SIZE_MB,0)) EXPIRE_MB
    FROM  (
           SELECT TABLESPACE_NAME, STATUS, ROUND(SUM(BYTES)/1024/1024) SIZE_MB
           FROM   DBA_UNDO_EXTENTS
         --WHERE  tablespace_name in ( select value from v$parameter where name='undo_tablespace')
           GROUP  BY TABLESPACE_NAME, STATUS
          ) A
    GROUP BY TABLESPACE_NAME
)
SELECT to_char(sysdate,'HH24:MI:SS')||' '||TABLESPACE_NAME||' : '||round((active_mb+unexpi_mb+expire_mb)/tot_mb*100)||
       ' % -> Act('||active_mb||') Unexp('||unexpi_mb||') Exp('||expire_mb||') TOT('||TOT_MB||')' MSG,
       round(100*(ACTIVE_MB)/TOT_MB,2),
       round(100*(ACTIVE_MB+UNEXPI_MB)/TOT_MB,2)
FROM TB;


현재 진행되고 있는 rollback의 잔여시간은 다음 쿼리로 대충 추산할 수 있다.

set serveroutput on
declare
    l_start number ;
    l_end   number ;
    r_min   number ;
begin
    select ktuxesiz into l_start from sys.xm$ktuxe where ktuxesta = 'ACTIVE' and KTUXECFL = 'DEAD' ;
    dbms_lock.sleep(60);
    select ktuxesiz into l_end   from sys.xm$ktuxe where ktuxesta = 'ACTIVE' and KTUXECFL = 'DEAD' ;
    r_min := round(l_end / (l_start - l_end)  ) ;
    dbms_output.put_line('['||to_char(sysdate,'HH24:MI:SS')||'] Remain Time : '|| r_min ||'분 '|| round(r_min/60,2)||'시간 / '||(l_start-l_end)||' blocks done / '|| l_end ||' Total blocks remains' );
end ;
/


운영DB에 임시 UNDO Tablespace를 생성한 후, 향후의 트랜잭션들은 이 UNDO tbs를 사용하도록 조치한다.

create undo tablespace undotbs2 datafile '/dev/rdsk/vsp1_000_ra000/dat_000_20G_001';

alter tablespace undotbs2 add datafile '/dev/rdsk/vsp1_000_ra000/dat_000_20G_002';

...

alter tablespace undotbs2 add datafile '/dev/rdsk/vsp1_000_ra000/dat_000_20G_NNN';


alter system set undo_tablespace=undotbs2 ;

이때 UNDO tablespace를 변경하는 것 자체는 금방 수행되므로 긴장하지 않아도 된다.

다만, alert log에 'active transactions found in undo tablespace NN - moved to pending switch-out state 라는 로그가 트랜잭션 당 1 라인씩 계속 발생할 수 있는데, 어떤 사이트의 경우에는 이 메시지가 다량 발생하기도 하였다고 하므로, alert log가 존재하는 path의 가용량을 모니터링하면서 작업 수행하자.

이러한 세션들은 세션에서 commit을 찍고 알아서 종료되길 기다리거나, 아니면 kill하여 처리하면 된다.


UNDO tablespace를 switch 한 후, 이전의 UNDO tbs를 사용하는 채로 commit 되지 않고 남아있는 세션을 찾아본다

select a.usn, a.name, b.status, c.tablespace_name, d.addr, e.sid, e.serial#, e.username, e.program, e.machine, e.osuser
from   v$rollname a, v$rollstat b, dba_rollback_segs c, v$transaction d, v$session e
where  a.usn=b.usn
and    a.name = c.segment_name
and a.usn = d.xidusn
and    d.addr = e.taddr
and b.status = 'PENDING OFFLINE';


rollback이 종료되고 상황이 정상화 되면,

기존 UNDO tablespace를 사용하도록 파라미터를 재설정해주고,

추가했었던 UNDO tablespace를 drop하면 된다.

Posted by in0de
,

1. 압축률

  • 현재 DB2 DW의 압축률 = 70% 수준 (table 단위의 block compression)
  • 현재 운영 Oracle의 압축률 = 55% 수준 (row 단위 compression)
  • DB2 BLU (columnar)의 압축률 = 95% (hybrid compression)
  • DB2 Netezza (columnar)의 압축률 = 75%
  • 오라클 HCC (columnar)의 압축률 = 85% ~ 92.5% (외부 압축 알고리즘 도입)


    ■ 압축률
    - Query Low : 85%
    - Query High :   
    - Archive Low :
    - Archive High : 92.5%

실질적으로 운영환경에서 사용할 수 있는 압축방식은 제공되는 4 가지 옵션 중
성능의 문제로 인해 Query Low 하나라고 보면 되겠고,

Archive Low/High 압축의 경우는
곧바로 읽을 수 있다는 장점이 있는 마그네틱 테이프를 생각하시면 되겠다.


■ Query Low
    CU (Compression Unit)의 크기 : 32k
    LZO 알고리즘

■ Query High
    CU 크기 : 32~64k
    ZLIB 알고리즘

■ Archive Low
    CU 크기 : 32~256k
    ZLIB 알고리즘


■ Archive High
    BZIP2 알고리즘






2. 성능
압축 시 데이터 크기 자체가 줄어들음으로 인해, 조회 성능은 개선되나,
데이터 ETL 시 불리하다.

■ Insert 성능

query low = 20초
query high = 40초
archive low = 40초
archive high = 240초


■ SQL 쿼리 성능

비압축 = IO bound :171초, CPU bound : 13초
query low = IO bound :91초, CPU bound : 16초
query high = IO bound :73초, CPU bound : 19초
archive low = IO bound :74초, CPU bound : 19초
archive high = IO bound :118초, CPU bound : 33초





3. 기타 기술적인 제약

- 특성 상 데이터의 Sort 상태를 유지하여야 압축률이 높아짐

- 조회한 데이터를 메모리 버퍼에 올리거나, aggregate 또는 sort 동작을 위해 temporary 영역에 넣으려면 압축을 해제해야 함

- Index를 읽어 데이터를 조회할 때, 하나의 CU를 여러 번 압축 해제 해야 하는 문제로 성능이 저하될 수 있음
  ∵ 정렬된 rowid 순서로 select하는 게 아니라, 컬럼 데이터 순서로 정렬된 rowid 포인터로 CU를 읽기 때문

- HCC 압축된 row에 update가 수행되면, 이 데이터는 압축이 풀리면서 기존의 낮은 압축률 공간으로 옮겨지며
  이 과정도 느릴 뿐더러, 향후 조회 시 HCC블럭, OLTP블럭 두 군데를 읽어야 하므로 성능 저하
  업데이트가 있는 테이블에는 HCC를 적용하지 않을 것을 권고

- 하나의 row를 업데이트 하더라도, 그 row가 걸쳐있는 전체 CU를 lock 걸어야 하는데, 이로 인해 빈번한 lock wait 발생 가능, 동시성 저하

- 이를 해결하기 위해서는 Oracle 12c Advanced Compression 옵션을 구매해야 하며, 적용 시 일반적인 RDBMS의 row level locking이 가능해짐

- 그러나, 이 정보를 관리하기 위한 overhead가 발생하여, 약 10% 가량의 데이터 사이즈가 증가함

- partition table의 경우, 압축된 후의 사이즈가 충분히 크지 않으면 direct path read 플랜 (스마트 스캔을 가능하게 하는) 으로 풀리지 않게 되고,
이로인해 결국 스마트 스캔을 하지 않게 되면서, 셀 노드의 CPU로 압축해제 하지 못하고, DB 서버에 CPU 부하를 주게 될 수 있음.
다시 말해, 월별 건수가 애매하게 작으면 DB서버 CPU 부하 상당량 발생 (이 기준점은 알 수 없음)



4. 환경 고려사항


- HCC 압축을 하기 위해서는 모든 insert문을 수정해야 함. INSERT /*+ APPEND */ INTO


- HCC가 적용된 DB의 백업본을 복구하기 위해서도 Exadata가 필요함 (Exadata Storage server 이외의 HW에서는 HCC 사용에 제약이 걸려있어)

- 오라클 ZFS 스토리지 어플라이언스에 복구가 가능하기는 하나, 스토리지를 백업본 크기의 10배를 준비해야 하며,
   ALTER TABLE NOCOMPRESS로 압축을 다 풀기 전에는 조회 불가

Posted by in0de
,

v$session 다이내믹 퍼포먼스 뷰를 조회하다보면,

ORA-29275 : 부분 다중바이트 문자 오류가 발생하는 경우가 있는데
이는 sys.v$session 뷰의 action varchar2(32 또는 64 byte) 컬럼이 문제이다.

 

이 action 컬럼은 DBMS_APPLICATION_INFO.SET_ACTION 프로시저로
애플리케이션이 하는 일을 사용자가 이름 붙여주는 것으로서,
어떤 SQL 로직의 실행 단계를 태그하고자 할 때 사용할 수 있는 컬럼이다.

 

최초 프로그램이 접속하면 다음과 같이 모듈명과 실행 작업을 지정해주고

BEGIN
  DBMS_APPLICATION_INFO.set_module(module_name => '1. 주문',
                                   action_name => '1.1 주문 접수');
  -- 수행할 SQL
END;
/

 

일련의 실행 단계마다, action 에 대해서

BEGIN
  DBMS_APPLICATION_INFO.set_action(action_name => '1.2 재고 조회');
 
  -- 수행할 SQL
END;
/

와 같이 이름지어주어, 모니터링 시 확인할 수 있도록 하는 컬럼이다.

 

어쨌든 이 컬럼으로 인해 Toad 등의 애플리케이션에서
세션 조회가 불가능한 경우에는 action 컬럼에 대해

TO_SINGLE_BYTE('ACTION') 과 같이 함수를 씌워서 직접 조회해볼 수는 있다.

 

SELECT /*+  RULE */ s.inst_id AS node
   , s.username
   , CASE
   WHEN SUBSTR( s.action, 1, 4 ) = 'FRM:' THEN s.module || '(Form)'
   WHEN SUBSTR( s.action, 1, 4 ) = 'Onli' THEN s.module || '(Form)'
   WHEN SUBSTR( s.action, 1, 4 ) = 'Conc' THEN s.module || '(Conc)'
   ELSE s.module
  END AS module
   , SUBSTR( CASE WHEN SIGN( LENGTHB( s.program ) - 16 ) = 1 THEN SUBSTR( s.program, 1, 16 ) || '..' ELSE s.program END, 1, 16 ) AS program
   , s.osuser||'@'||s.machine AS who
   --, s.port
   , last_call_et
   , TO_CHAR( logon_time, 'mm-dd HH24:MI:SS' ) AS logon_time
   , s.sql_id
   , 'kill -9 ' || p.spid AS OSKILLCMD
   , 'alter system kill session ' || '''' || s.sid || ',' || s.serial# || ',@' || s.inst_id || '''' || ';  --'||p.spid||'' AS ORACLEKILLCMD
   , s.terminal AS USER_INFO
   , SUBSTR( LPAD( TRIM( TO_CHAR( ROUND( p.pga_alloc_mem / 1024 / 1024 ), '999,999' ) ) || 'M', 5, ' ' ), 1, 6 ) AS pga
   --, TRIM( ( SELECT SUBSTR( sql_text, 1, 120 ) FROM v$sql sq WHERE sq.sql_id = s.sql_id AND s.sql_hash_value = sq.hash_value AND ROWNUM = 1 ) ) AS sql_text
   , CASE
   WHEN s.blocking_session IS NULL THEN ''
   ELSE SUBSTR( s.blocking_session_status, 1, 3 ) || '(' || s.blocking_instance || ')' || ( s.blocking_session - 1 )
  END AS BLOCKING_SID
   , s.seconds_in_wait
   , SUBSTR( s.event, 1, 32 ) AS wait_event
   , TO_SINGLE_BYTE( s.action ) AS action
   , ( SELECT  sql_text
    FROM   v$sql sq
    WHERE   sq.sql_id = s.sql_id AND s.sql_hash_value = sq.hash_value AND ROWNUM = 1 )
   AS sql_text
FROM gv$session s, gv$process p
WHERE s.paddr = p.addr
AND  s.inst_id = p.inst_id
AND  s.status = 'ACTIVE'
AND  s.event NOT IN ( 'queue messages'
        , 'pipe get'
        , 'jobq slave wait'
        , 'Streams AQ: waiting for messages in the queue'
        , 'class slave wait')
AND  s.username IS NOT NULL
AND  s.audsid <> USERENV( 'SESSIONID' )                     -- which is not my session
--order by s.inst_id,status1 desc,username1 desc , substr(s.action,1,3) desc, module1,last_call_et
ORDER BY s.inst_id, s.status DESC, last_call_et DESC

/

Posted by in0de
,
Oracle 10g의 압축방식은 block level에서 이뤄지는데다,
direct path insert같은 load를 할 때만 효력이 있었던 듯 하고...

Oracle에서 block level compress를 하는 과정은
load 시 중복 값을 block에서 발견하면 이를 block header에 기록한 후
실제 데이터는 block header를 pointer로 가리켜놓는 식인데
한 block에 동일한 데이터가 많이 들어갈 수록 압축 효율이 높아지게 된다.

Oracle의 block level compress로 인한 태생적 한계는
  • Table, Range partition에서의 중복값을 compress하지 못함
  • 여러 block에서 반복되는 값이라 하더라도, 각 block header에 dictionary를 만들어야 함
  • column value 단위로 찾으므로, 문자열 일부분을 압축할 수 없음
  • load시에만 작동하고, insert/update시에는 작동하지 않음
11g 들어오면서 돈 내는 옵션으로 insert/update 시에도 압축을 할 수 있는 것 같은데
특정한 값이 block에 반복하여 insert/update 하는 경우에 압축을 하는 것 같다.

DB2에서는 table 또는 partiion based로 dictionary를 만들기 때문에
block level보다 합리적으로 동작하고 압축률이 높아지는데
스토리지를 절약하는 것 이외에도 여러가지 장점
- 버퍼풀에서도 압축된 상태이므로 메모리를 아낀다던가,
 백업 시간이 단축된다던가 하는(TB급 DB를 관리할 때는 이 점이 제일 좋다) - 을 가진다.
Posted by in0de
,