참고 자료
http://ukja.tistory.com/178#recentComments
고객 미납금액을 계산하는 아래 쿼리가 1시간쯤 걸린다고 가정하자 |
---|
{CODE:SQL} |
SELECT /* ORDERED USE_NL( B ) */ A.고객ID
, NVL(SUM(과금액), 0) 과금액
, NVL(SUM(수납액), 0) 수납액
, NVL(SUM(과금액), 0) - NVL( SUM( 수납액), 0) 미납액
FROM 과금 A, 수납 B
WHERE A.과금년월 = :과금년월
AND A.과금유형 = :과금유형
AND A.고객ID = B.고객ID(+)
AND A.과금년월 = B.수납년월(+)
GROUP BY A.고객ID
{CODE}
예제 1 해결책 (주관적 ) |
---|
{CODE:SQL} SELECT /* ORDERED USE_HASH( B ) */ A.고객ID , NVL(SUM(과금액), 0) 과금액 , NVL(SUM(수납액), 0) 수납액 , NVL(SUM(과금액), 0) - NVL( SUM( 수납액), 0) 미납액 FROM 과금 A, 수납 B WHERE A.과금년월 = :과금년월 AND A.과금유형 = :과금유형 AND A.고객ID = B.고객ID(+) AND A.과금년월 = B.수납년월(+) GROUP BY A.고객ID ORDER BY A.고객ID {CODE} |
{CODE:SQL}
1 FOR C IN (SELECT /*+ ORDERED USE_NL( B ) */ A.고객ID, A.입금액, B.수납액
2 FROM 은행입금 A, 순납 B
3 WHERE A.입금일자 = TRUNC( SYSDATE )
4 AND B.수납년월(+) = TO_CHAR( SYSDATE, 'YYYYMMDD' )
5 AND B.고객ID(+) = A.고객ID )
6 LOOP
7 IF C.수납액 IS NULL THEN
8 INSERT INTO 수납( 고객ID, 수납년월, 수납액 )
9 VALUES( C.고객ID, TO_CHAR( SYSDATE, 'YYYYMM' ), C.입금액 );
10 ELSE
11 UPDATE 수납 SET 수납액 = 수납액 + C.입금액
12 WHERE 고객ID = C.고객ID
13 AND 수납년월 = TO_CHAR( SYSDATE, 'YYYYMMM' );
14 END IF;
15 COMMIT;
16 END LOOP;
{CODE}
예제 2 해결책 (주관적 ) |
---|
{CODE:SQL} |
1 FOR C IN (SELECT /*+ ORDERED USE_NL( B ) */ A.고객ID, A.입금액, B.수납액
2 FROM 은행입금 A, 순납 B
3 WHERE A.입금일자 = TRUNC( SYSDATE )
4 AND B.수납년월(+) = TO_CHAR( SYSDATE, 'YYYYMMDD' )
5 AND B.고객ID(+) = A.고객ID )
6 LOOP
7 IF C.수납액 IS NULL THEN
8 INSERT INTO 수납( 고객ID, 수납년월, 수납액 )
9 VALUES( C.고객ID, TO_CHAR( SYSDATE, 'YYYYMM' ), C.입금액 );
10 ELSE
11 UPDATE 수납 SET 수납액 = 수납액 + C.입금액
12 WHERE 고객ID = C.고객ID
13 AND 수납년월 = TO_CHAR( SYSDATE, 'YYYYMMM' );
14 END IF;
15
16 END LOOP;
17 COMMIT;
{CODE}
SQL> create undo tablespace small_undo datafile 'C:\ORACLE\PRODUCT\10.2.0\ORADATA\ERM\S_UNDO.DBF' SI
ZE 10M AUTOEXTEND OFF;
테이블스페이스가 생성되었습니다.
SQL> alter system set undo_tablespace = 'SMALL_UNDO';
시스템이 변경되었습니다.
SQL> alter system set db_cache_size=1M scope=both ;
SQL> show parameter cache_size
NAME TYPE VALUE
------------------------------------ ----------- ---------------------
db_16k_cache_size big integer 0
db_2k_cache_size big integer 0
db_32k_cache_size big integer 0
db_4k_cache_size big integer 0
db_8k_cache_size big integer 0
db_cache_size big integer 4M
db_keep_cache_size big integer 0
db_recycle_cache_size big integer 0
SQL> show sga
Total System Global Area 612368384 bytes
Fixed Size 1250428 bytes
Variable Size 113249156 bytes
Database Buffers 490733568 bytes
Redo Buffers 7135232 bytes
SQL> --Select Query의 속도를 제어하기 위해 Sleep 함수를 만든다.
SQL> create or replace function fsleep(v1 int, vsleep int)
2 return number
3 is
4 begin
5 dbms_lock.sleep(vsleep);
6
7 return 1;
8 end;
9 /
함수가 생성되었습니다.
SQL> drop table t1 purge;
테이블이 삭제되었습니다.
SQL> create table t1(c1 int, c2 varchar2(10));
테이블이 생성되었습니다.
SQL> insert into t1
2 select level, 'AAA'
3 from dual
4 connect by level < 10000
5 ;
9999 개의 행이 만들어졌습니다.
SQL> commit;
커밋이 완료되었습니다.
SQL> create index t1_n1 on t1(c1);
인덱스가 생성되었습니다.
SQL> select rowid, dbms_rowid.rowid_relative_fno(rowid) as fno,
2 dbms_rowid.rowid_block_number(rowid)as blkno
3 from t1 where rownum = 1;
ROWID FNO BLKNO
------------------ ---------- ----------
AAAN/eAABAAAPBSAAA 1 61522
SQL> alter system dump datafile 1 block 61522;
시스템이 변경되었습니다.
--SESSION 1 번 실행중에 실행
SQL> alter system dump datafile 1 block 61522;
시스템이 변경되었습니다.
--SESSION 1 번 실행중에 실행
SQL> alter system dump datafile 1 block 61522;
시스템이 변경되었습니다.
select /*+ index(t1) */
c1, c2
from t1
where fsleep(c1,0.01) = 1
and c1 > 0;
...
---------- ----------
3356 AAA
3357 AAA
3358 AAA
3359 AAA
3360 AAA
ERROR:
ORA-01555: 너무 이전 스냅샷:롤백 세그먼트 14 수에 "_SYSSMU14$" 이름으로 된 것이
너무 작습니다
-- get rollback segment name
col rollback_seg new_value v_rollback_seg
select '_SYSSMU'||max(segment_id)||'$' as rollback_seg
from dba_rollback_segs
where segment_name <> 'SYSTEM'
;
-- do very frequent commit on t1
begin
for idx in 1 .. 1000 loop
for idx2 in 1 .. 10000 loop
set transaction use rollback segment "&v_rollback_seg";
update t1 set c2 = 'dummy'||idx
where c1 = idx2;
commit;
end loop;
end loop;
end;
/
첫번째 덤프
Block header dump: 0x0040f052
Object id on Block? Y
seg/obj: 0xdfde csc: 0x00.a750a5 itc: 2 flg: - typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0010.02e.00000020 0x01400224.000a.10 --U- 1 fsc 0x0000.00a750a8
0x02 0x000d.025.00000020 0x0140020e.0007.18 --U- 1 fsc 0x0000.00a750a6
두번째 덤프
Block header dump: 0x0040f052
Object id on Block? Y
seg/obj: 0xdfde csc: 0x00.a750a5 itc: 2 flg: - typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0010.02e.00000020 0x01400224.000a.10 --U- 1 fsc 0x0000.00a750a8
0x02 0x000d.025.00000020 0x0140020e.0007.18 --U- 1 fsc 0x0000.00a750a6
세번재 덤프
Block header dump: 0x0040f052
Object id on Block? Y
seg/obj: 0xdfde csc: 0x00.a83265 itc: 2 flg: - typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000e.02b.0000004d 0x0140003e.000d.36 C--- 0 scn 0x0000.00a83264
0x02 0x000d.023.00000066 0x0140002d.0012.21 --U- 1 fsc 0x0000.00a83266
왜 이런 일이 발생하는가? Delayed Block Cleanout 때문이다.
Table t1에 대해 Commit이 수행될 때 모든 Data Block에 대해 Commit 여부를 지정하지 않고(즉 일부에만 Commit 여부 지정) Undo Segment Header의 Transaction Table에만 Commit 여부를 지정한다. 이 정보가 완전히 덮어써져서 Rollback하지 못하면 ORA-01555 에러가 나게 된다.
Block에 Commit 여부를 저장하는 것을 Block Cleanout이라고 부른다. Delayed Block Cleanout는 Block Cleanout를 Commit 시점에 하지 않고 미루겠다(Delay)는 것을 의미한다.
그렇다면 언제 Block Cleanout이 이루어지는가? 다른 Session이 해당 Block을 Access하는 시점에 이루어진다.
즉 위의 예에서 Session#1은 Delayed Block Cleanout를 수행하기 위해 Transaction Table을 읽을려고 했는데 다른 DML에 의해 해당 정보들이 완전히 덮어써져서 Rollback이 안되기 때문에 ORA-01555 에러를 발생시키는 것이다.