검색해보면 온통 다음 방식으로 플랜을 조회하는 것으로 설명되어 있다.

EXPLAIN PLAN FOR ${SELECT STMT}
SELECT * FROM table (DBMS_XPLAN.DISPLAY);

그러나 실제로는 커서가

검색해보면 온통 다음 방식으로 플랜을 조회하는 것으로 설명되어 있다.

EXPLAIN PLAN FOR ${SELECT STMT};
SELECT * FROM table (DBMS_XPLAN.DISPLAY_CURSOR(null,null,'ALLSTATS LAST');

그러나 다음과 같은 오류가 발생하면서 플랜이 나오지 않는 경우가 발생할 수 있다.

  NOTE: cannot fetch plan for SQL_ID: ***********, CHILD_NUMBER: 0
    Please verify value of SQL_ID and CHILD_NUMBER; 
    It could also be that the plan is no longer in cursor cache (check v$sql_plan)

set serverouput=on 인 경우, 쿼리 수행 이후에 DBMS_OUTPUT 버퍼에 출력할 내용이 있는지

sqlplus가 추가적인 call을 하는 단계가 끼어들면서 이런 현상이 발생하는데,

set serveroutput=off 하면 일반적으로 이런 증상을 해결할 수 있다.

 

이런 현상을 해결하기 위해 DISPLAY_CURSOR() 대신, DISPLAY() 를 사용하라거나

v$sql_plan에서 방금 수행한 SQL을 찾아 cursor와 child_number를 일일이 찾는 방식을 추천하는 글도 많은데

 

이럴 때, 다음과 같이 loop로 묶어버리면 중간에 다른 동작이 개입될 일 없이

항상 플랜을 조회할 수 있다.

--alter session set current_schema=________;
set lines 4000
set pages 0 embedded on
-- execute dbms_session.set_sql_trace(true);
set serveroutput on size unlimited;
alter session set STATISTICS_LEVEL='ALL';
--alter session set optimizer_use_invisible_indexes=true;
--alter session set "_OPTIM_PEEK_USER_BINDS"=true;

-- var v1 varchar2(20);
-- exec :v1 := '________';

begin
for SQLQuery in (
-- /*+ GATHER_PLAN_STATISTICS */

${플랜 조회할 SQL문을 여기에, 맨 끝에 ";" separator 없이}

)
loop
null;
end loop;


-- dbms_xplan query
for SQLPlan in
(select plan_table_output pto from table(dbms_xplan.display_cursor(null, null, 'advanced allstats last +peeked_binds -projection -alias -outline +remote')))
loop
dbms_output.put_line(SQLPlan.pto);
end loop;
end;
/
Posted by in0de
,

네트워크 드라이브 경로에 있는 도움말 파일을 열었을 때, 목차는 나오지만 내용이 안나오는 경우는

KB896358 보안 패치가 설치됨에 따른 현상이며

다음 레지스트리 키를 생성하면 *.chm 파일 내용을 정상적으로 볼 수 있다.


Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\HTMLHelp\1.x]

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\HTMLHelp\1.x\HHRestrictions]
"MaxAllowedZone"=dword:00000001

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\HTMLHelp\1.x\ItssRestrictions]
"MaxAllowedZone"=dword:00000001


하지만 해당 패치는 chm을 통해 완전한 시스템 권한을 탈취할 수 있는 보안취약점을 패치하는 것으로

이렇게 해서 사용하는 것이 답은 아닌 것 같다.

Posted by in0de
,

db2에는 아직 ALTER TABLE tblName DROP PARTITION partName 구문이 없기 때문에

1. 파티션을 detach

2. 파티션테이블로부터 detach 된 single table을 drop

의 단계로 진행해야 한다.


이 때, detach 동작은 background에서 asynchronous하게 동작하므로

실제로 detach가 되어 single table로 떨어져나간 시점에 DROP TABLE을 수행하여야 하는데

partition detach가 금방 수행완료될 지 아닐 지는 알 수가 없다.


이러한 고려사항을 포함하여 간편하게 수행할 수 있는 프로시저의 소스는 아래와 같다.


-- db2 -td@ -vf dropPartition.sql

-- in dropPartition.sql

CREATE OR REPLACE PROCEDURE DBA.DROP_PARTITION (IN tblSchema VARCHAR(40), IN tblName VARCHAR(60), IN partName VARCHAR(120))

SPECIFIC DROP_PARTITION

LANGUAGE SQL

BEGIN

DECLARE tblSchema VARCHAR(40) DEFAULT '___________';

DECLARE detachSQL VARCHAR(2000);

DECLARE dropSQL VARCHAR(2000);

DECLARE isTblAbsent INT;


        DECLARE dpID INT;

        DECLARE dpState CHAR;

        DECLARE isNormalState INT DEFAULT 0;


        DECLARE curDPLock CURSOR FOR

                SELECT DATAPARTITIONID, STATUS

                FROM SYSIBM.SYSDATAPARTITIONS DP

                WHERE DP.TABSCHEMA = tblSchema

                AND DP.TABNAME = tblName

                AND (STATUS = 'L' OR STATUS = 'D' OR STATUS = 'I');


        DECLARE CONTINUE HANDLER FOR NOT FOUND

                SET isNormalState = 1;



CALL DBA.chkTblExist (tblSchema, tblName, isTblAbsent);

        IF isTblAbsent = 1

        THEN

                RETURN -1;

        END IF;


SET detachSQL = 'ALTER TABLE '  || tblSchema || '.' || tblName || ' DETACH PARTITION '|| partName || ' INTO ' || tblSchema || '.' || tblName || '_' || partName || '_DETACHED';

--CALL DBMS_OUTPUT.PUT_LINE(detachSQL);

EXECUTE IMMEDIATE detachSQL;

EXECUTE IMMEDIATE 'COMMIT WORK';


        WAIT_LOOP:

        LOOP

                OPEN curDPLock;

                FETCH curDPLock INTO dpID, dpState;

                CLOSE curDPLock;


                IF isNormalState = 1

                THEN

                        LEAVE WAIT_LOOP;

                ELSE

                        ITERATE WAIT_LOOP;

                END IF;

        END LOOP;


SET dropSQL = 'DROP TABLE ' || tblSchema || '.' || tblName || '_' || partName || '_DETACHED';

-- CALL DBMS_OUTPUT.PUT_LINE(dropSQL);

EXECUTE IMMEDIATE dropSQL;

EXECUTE IMMEDIATE 'COMMIT WORK';


RETURN 0;

END@


--

/* Sample Table DDL

DROP TABLE INODE.TMP;

CREATE TABLE INODE.TMP  (

OPRN_DT VARCHAR(8) NOT NULL ,

RIDE_TROP_ID VARCHAR(9) NOT NULL ,

RIDE_STN_NO VARCHAR(4) NOT NULL ,

ALGH_TROP_ID VARCHAR(9) NOT NULL ,

ALGH_STN_NO VARCHAR(4) NOT NULL ,

MTAR_AREA_CD VARCHAR(2) NOT NULL ,

RIDE_NCNT INTEGER NOT NULL WITH DEFAULT 0 ,

ALGH_NCNT INTEGER NOT NULL WITH DEFAULT 0 ,

RIDE_TTPS_NUM BIGINT NOT NULL WITH DEFAULT 0 ,

ALGH_TTPS_NUM BIGINT NOT NULL WITH DEFAULT 0 ,

RIDE_AMT BIGINT NOT NULL WITH DEFAULT 0 ,

ALGH_AMT BIGINT NOT NULL WITH DEFAULT 0 ,

RIDE_VLT_AMT BIGINT NOT NULL WITH DEFAULT 0 ,

DC_AMT BIGINT WITH DEFAULT 0 ,

GNPS_NUM BIGINT NOT NULL WITH DEFAULT 0 ,

TNPS_NUM BIGINT NOT NULL WITH DEFAULT 0 ,

CHPS_NUM BIGINT NOT NULL WITH DEFAULT 0 ,

SNCN_PSNG_NUM BIGINT NOT NULL WITH DEFAULT 0 ,

DSPR_PSNG_NUM BIGINT NOT NULL WITH DEFAULT 0 ,

NTMP_NUM BIGINT NOT NULL WITH DEFAULT 0 ,

FRGN_SNCN_PSNG_NUM BIGINT WITH DEFAULT 0 ,

FRAP_PSNG_NUM BIGINT WITH DEFAULT 0 ,

DW_LST_UPD_DTM VARCHAR(14) NOT NULL )

COMPRESS YES ADAPTIVE

DISTRIBUTE BY HASH(RIDE_STN_NO)

INDEX IN TR_MT16_I01 PARTITION BY RANGE(OPRN_DT)

(PART TMP_P201600 STARTING(MINVALUE) ENDING('20151299') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201601 STARTING('20160101') ENDING('20160199') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201602 STARTING('20160201') ENDING('20160299') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201603 STARTING('20160301') ENDING('20160399') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201604 STARTING('20160401') ENDING('20160499') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201605 STARTING('20160501') ENDING('20160599') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201606 STARTING('20160601') ENDING('20160699') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201607 STARTING('20160701') ENDING('20160799') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201608 STARTING('20160801') ENDING('20160899') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201609 STARTING('20160901') ENDING('20160999') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201610 STARTING('20161001') ENDING('20161099') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201611 STARTING('20161101') ENDING('20161199') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201612 STARTING('20161201') ENDING('20161299') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201701 STARTING('20170101') ENDING('20170199') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201702 STARTING('20170201') ENDING('20170299') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201703 STARTING('20170301') ENDING('20170399') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201704 STARTING('20170401') ENDING('20170499') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201705 STARTING('20170501') ENDING('20170599') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201706 STARTING('20170601') ENDING('20170699') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201707 STARTING('20170701') ENDING('20170799') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201708 STARTING('20170801') ENDING('20170899') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201709 STARTING('20170901') ENDING('20170999') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201710 STARTING('20171001') ENDING('20171099') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201711 STARTING('20171101') ENDING('20171199') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201712 STARTING('20171201') ENDING('20171299') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201801 STARTING('20180101') ENDING('20180199') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201802 STARTING('20180201') ENDING('20180299') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201803 STARTING('20180301') ENDING('20180399') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201804 STARTING('20180401') ENDING('20180499') IN TR_MT16_D01 INDEX IN TR_MT16_I01,

PART TMP_P201805 STARTING('20180501') ENDING('20180599') IN TR_MT16_D01 INDEX IN TR_MT16_I01);

CREATE UNIQUE INDEX INODE.PK_TMP ON KSCCDW.TMP (OPRN_DT ASC, RIDE_TROP_ID ASC, RIDE_STN_NO ASC, ALGH_TROP_ID ASC, ALGH_STN_NO ASC, MTAR_AREA_CD ASC) PARTITIONED;

ALTER TABLE INODE.TMP ADD CONSTRAINT PK_TMP PRIMARY KEY (OPRN_DT,RIDE_TROP_ID,RIDE_STN_NO,ALGH_TROP_ID,ALGH_STN_NO,MTAR_AREA_CD);

*/


-- Sample detach partition SQL

-- db2 "CALL DBA.DROP_PARTITION('INODE','TMP','TMP_P201601')"


Posted by in0de
,

간단하게도 /usr/local/bin/db2ls 를 실행하면 확인할 수 있다.

 

 

db2ls -q -p -b ${installed_path} 를 수행하면 에디션 정보도 확인 가능하다.

 

 

Posted by in0de
,

online 옵션을 사용해 인덱스를 생성하다가 비정상적으로 종료했을 경우

drop index, alter index rebuild 등의 작업이 다음과 같은 오류 발생하며 아무 것도 할 수 없게 되는 경우가 있다.


ORA-08104: 인덱스 객체 nnnnn 은(는) 온라인 구축 중이거나 재구축 중입니다



이 때 object id nnnnn을 인자로 다음과 같이 clean 할 수 있다.

sys 유저로 수행해야 한다.


declare

v_ret boolean;

begin

v_ret := dbms_repair.online_index_clean(nnnnn);

end;

/


만약, sys 유저로 수행하지 않은 경우는 다음과 같이 오류 발생한다.


ORA-06550: PLS-00201: 'DBMS_REPAIR.ONLINE_INDEX_CLEAN' 식별자가 정의되어야 합니다



Posted by in0de
,

https://www.advancedrenamer.com/

Advanced Renamer는 파일 이름 변경 유틸리티로서, 같은 카테고리 중 이만큼 강력한 툴은 못 본 것 같다.
용례를 보면 많은 사람들이 드라마 에피소드를 정리할 때 잘 활용하고 있다.



https://www.sno.phy.queensu.ca/~phil/exiftool/

미디어의 exif 정보를 접근하기 위해, exiftool도 설치해야 한다.




사진기를 변경하여 DSCN 번호가 0001부터 시작하게 되는 경우나,
핸드폰을 변경하거나 체인을 리셋하는 경우에
IMG_NNNN 번호가 중복되어 파일이 덮어씌워질 위기를 맞거나,
이름 순 정렬 시 정렬 순서가 보기 좋지 않다거나 하는 관리적인 문제들이 있다.

대부분의 사진기가 동작하듯이
촬영 월, 일 단위로 폴더를 만들면 해결되는 문제이지만
좀 더 큰 단위의 폴더에 두었을 때 이미지의 관리 및 접근이 편하다.

다음 스크립트는 Advanced Renamer의 강력한 기능을 활용하는
간단한 스크립트로, 파일의 exif 정보에서 DateTimeOriginal값, 없으면 File ModifyDate를 읽어
현재 파일 이름 앞에 YYMMDD를 붙여준다.



Renaming method list > Add method > Pre batch script


function zpad(str, nb_digits)
{
    if (str.length < nb_digits)
        { return (Array(nb_digits - str.length + 1).join('0') + str); }
    else
        { return str; }
}

var tobeName = null;
var tobeExt = null;
var tobeFullName = null;


Script

var strDTM = item.exifToolValue('DateTimeOriginal');
 
if ( item.exifToolValue('DateTimeOriginal') == '' ){
    var strDTM = item.exifToolValue('FileModifyDate');
}
 
var YY = strDTM.substring(2,4)
var MM = strDTM.substring(5,7)
var DD = strDTM.substring(8,10)
var HH = strDTM.substring(11,13)
var MI = strDTM.substring(14,16)
var SS = strDTM.substring(17,19)
strDTM = YY+MM+DD//+' '+HH+';'+MI+';'+SStobeFullName
        =
          strDTM + ' '+item.newBasename.replace(/^\d{6}\s+/gi,'')
        + item.newExt
 
return tobeFullName;





오른쪽 pane에 이름을 변경할 미디어 파일을 추가한 후 'Start batch' 수행하면

파일 이름을 다음과 같이 변경해준다.



Posted by in0de
,

원문 : 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
,

MPP 환경의 hash partition table에서

잘못된 hash key 선정으로 인해 skew가 발생하는 현황을 조회하려면

다음 쿼리로 조회할 수 있다.


with COL as

(
    select
        tabschema
        , tabname
        , colname
        , colno
        , colcard
        , partkeyseq
        , remarks
    from syscat.columns
    where 1=1
    and partkeyseq > 0
)
select TABINF.tabschema, TABINF.tabname
, max(COL.remarks) as remarks
, substr(xmlcast(xmlgroup(', '||COL.colname as colnm order by COL.partkeyseq) as varchar(128)), 2) as "파티션 키 컬럼"
, max(TBL.remarks) as "컬럼 설명"
--, max(COL.partkeyseq) "파티션 키 순번"
, max(TBL.card) as "테이블 건수"
, max(COL.colcard) as "파티션 키 컬럼 distinct 개수"
, stddev((data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)/1024) as "표준편차_MB"
, stddev(data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size) / sum(data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size) * 1024 as "사이즈 고려 표준편차지수"
, sum((data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)/1024) as size_mb
, max(decode(dbpartitionnum, 0, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node0
, max(decode(dbpartitionnum, 1, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node1
, max(decode(dbpartitionnum, 2, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node2
, max(decode(dbpartitionnum, 3, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node3
, max(decode(dbpartitionnum, 4, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node4
, max(decode(dbpartitionnum, 5, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node5
, max(decode(dbpartitionnum, 6, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node6
, max(decode(dbpartitionnum, 7, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node7
, max(decode(dbpartitionnum, 8, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node8
, max(decode(dbpartitionnum, 9, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node9
, max(decode(dbpartitionnum, 10, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node10
, max(decode(dbpartitionnum, 11, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node11
, max(decode(dbpartitionnum, 12, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node12
, max(decode(dbpartitionnum, 13, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node13
, max(decode(dbpartitionnum, 14, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node14
, max(decode(dbpartitionnum, 15, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node15
, max(decode(dbpartitionnum, 16, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node16
, max(decode(dbpartitionnum, 17, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node17
, max(decode(dbpartitionnum, 18, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node18
, max(decode(dbpartitionnum, 19, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node19
, max(decode(dbpartitionnum, 20, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node20
, max(decode(dbpartitionnum, 21, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node21
, max(decode(dbpartitionnum, 22, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node22
, max(decode(dbpartitionnum, 23, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node23
, max(decode(dbpartitionnum, 24, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node24
, max(decode(dbpartitionnum, 25, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node25
, max(decode(dbpartitionnum, 26, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node26
, max(decode(dbpartitionnum, 27, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node27
, max(decode(dbpartitionnum, 28, data_object_p_size+index_object_p_size+long_object_p_size+lob_object_p_size+xml_object_p_size)) as node28
, max(TBL.lastused) as "테이블 마지막 사용일"
from SYSIBMADM.ADMINTABINFO TABINF
right join COL on TABINF.tabschema=COL.tabschema and TABINF.tabname=COL.tabname
right join syscat.tables TBL on TABINF.tabschema=TBL.tabschema and TABINF.tabname=TBL.tabname
where TABINF.tabtype='T'
group by TABINF.tabschema, TABINF.tabname
order by TABINF.tabschema, TABINF.tabname
;


hash key를 여러 칼럼으로 구성한 경우에 대해서는 결과가 제대로 안나올 수 있다.

수행 결과는 다음과 같으며


노드 간 데이터 분산 표준편차는 노드별로 고르지 않은 데이터 분포를 나타내는 값으로 크면 클 수록 노드별 분산이 잘 안되어있다는 의미이고


skew로 인해 발생하는 수행 속도 이슈나 용량 이슈는 테이블이 클 수록 영향이 크므로,

테이블의 용량 가중치를 준 '사이즈 대비 표준편차' 를 기준으로 hash key 재지정 대상을 선정한 후

업무적으로 변경이 가능한 지를 판단하면 되겠다.

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
,