행 체이닝과 마이그레이션
행 체이닝과 마이그레이션의 발생 조건, 행 조각과 CHAINED_ROWS 조회 및 MOVE 실습
#핵심 요약
행 체이닝: 행이 처음부터 블록 하나에 들어가기에 너무 커서 여러 블록에 조각으로 나누어 저장됨
LONG, LONG RAW 같은 큰 컬럼에서 주로 생기며 피할 수 없음
행 마이그레이션: 블록에 들어 있던 행이 업데이트로 커졌는데 그 블록에 여유가 없어서, 행 전체를 새 블록으로 옮기고 원래 자리에는 포인터만 남김
ROWID는 바뀌지 않음
체이닝되거나 마이그레이션된 행을 읽으려면 블록을 더 읽어야 하므로 I/O가 늘어남
행 조각 하나는 255개 컬럼까지만 담음
컬럼이 1000개인 행은 조각 4개가 됨
#개념과 동작 원리
| 구분 | 언제 생기나 | 결과 |
|---|---|---|
| 체이닝 | 처음 넣을 때부터 행이 블록 하나보다 큼 업데이트로 블록 하나보다 커져도 생김 |
행이 여러 블록의 조각으로 나뉨 |
| 마이그레이션 | 업데이트로 커진 행이 원래 블록에 안 들어가지만 새 블록 하나에는 들어감 | 행 전체가 새 블록으로 가고 원래 자리에 전달 주소가 남음 ROWID 그대로 |
| 255개 넘는 컬럼 | 행 크기와 관계없이 컬럼 수 때문 | 255개 단위로 조각이 나뉨 보통 여러 블록에 걸치지만, 작은 행이면 같은 블록에 있을 수 있음 |
공식 문서: 마이그레이션된 행은 인덱스 한 번, 원래 행 자리 한 번에 더해 옮겨 간 블록을 읽는 I/O가 한 번 더 듦
공식 문서: Segment Advisor는 공간 회수 대상뿐 아니라 체이닝된 행이 많은 객체도 알려 줌
실습 기록: INSERT나 UPDATE가 쓸 블록의 여유 공간이 충분하지만 여러 조각으로 흩어져 있으면, 데이터베이스가 자동으로 그 블록의 여유 공간을 하나로 모음
총량은 같고 연속 구간이 됨
공식 문서도 같은 조건에서만 통합한다고 적음
실습의 정리: INSERT는 체이닝만 만들고, UPDATE는 체이닝과 마이그레이션을 모두 만들 수 있음
마이그레이션은 옮겨 갈 원래 자리가 있어야 하므로 INSERT로는 생기지 않음
마이그레이션된 행은 원래 블록과 같은 익스텐트로 갈 수도, 다른 익스텐트로 갈 수도 있음
체이닝되는 것은 행이지 행 조각이 아니며, 행 조각 하나는 한 블록 안에 통째로 들어감
#필수 명령어 또는 SQL
#CHAINED_ROWS 테이블 준비
@?/rdbms/admin/utlchain.sql결과 해석:
ANALYZE ... LIST CHAINED ROWS 결과를 받을 CHAINED_ROWS 테이블을 만듦?는 SQL*Plus에서 ORACLE_HOME을 뜻함(확인 불가)
#업데이트로 생기는 마이그레이션
CREATE TABLE mig_demo (id NUMBER, txt VARCHAR2(1000)) TABLESPACE users;
INSERT INTO mig_demo SELECT LEVEL, RPAD('a', 900, 'a') FROM dual CONNECT BY LEVEL <= 5000;
COMMIT;
ALTER TABLE mig_demo ADD (memo VARCHAR2(2000));
UPDATE mig_demo SET memo = RPAD('X', 2000, 'X') WHERE MOD(id, 2) = 0;
COMMIT;DELETE FROM chained_rows;
COMMIT;
ANALYZE TABLE mig_demo LIST CHAINED ROWS INTO chained_rows;SELECT COUNT(*) FROM chained_rows WHERE table_name = 'MIG_DEMO';
SELECT id FROM mig_demo
WHERE rowid = (SELECT MIN(head_rowid) FROM chained_rows WHERE table_name = 'MIG_DEMO');결과 해석:
예상값: 행을 넣기만 했을 때는 0건, 업데이트 뒤에는 2,437건임
두 번째 조회로 옮겨 간 행을 원래 ROWID로 그대로 찾을 수 있음CHAINED_ROWS는 체이닝과 마이그레이션을 구분하지 않고 담으므로, 어느 쪽인지는 어떤 명령으로 생겼는지로 판단함
#체이닝 (12,000바이트 행)
CREATE TABLE chn_demo (id NUMBER, c1 VARCHAR2(4000), c2 VARCHAR2(4000), c3 VARCHAR2(4000));
INSERT INTO chn_demo (id, c1) VALUES (1, 'x');
UPDATE chn_demo
SET c1 = RPAD('1', 4000, '1'), c2 = RPAD('2', 4000, '2'), c3 = RPAD('3', 4000, '3')
WHERE id = 1;
INSERT INTO chn_demo VALUES (2, RPAD('1', 4000, '1'), RPAD('2', 4000, '2'), RPAD('3', 4000, '3'));
COMMIT;결과 해석:
예상값: 두 행 모두 CHAINED_ROWS에 잡힘
1번은 업데이트로, 2번은 삽입할 때부터 12,000바이트라 8KB 블록 하나에 들어가지 않아 체이닝됨
#255개 넘는 컬럼의 행 조각
SELECT s.value
FROM v$mystat s, v$statname n
WHERE s.statistic# = n.statistic#
AND n.name = 'table fetch continued row';결과 해석:
실습에서는 컬럼 300개짜리 테이블(wide300)에 짧은 행 하나를 넣고 c1과 c300을 각각 조회하며 위 통계를 비교했음
예상값: c1 조회 뒤에는 0 그대로, c300 조회 뒤에는 1이 늘었음
256번째 이후 컬럼을 읽을 때만 다음 조각을 따라간다는 뜻이고, 행이 작으므로 두 조각은 같은 블록에 있음
#축소로는 남고 MOVE로 없어지는 마이그레이션
CREATE TABLE mig_test (id NUMBER, memo VARCHAR2(2000)) PCTFREE 0;
INSERT INTO mig_test SELECT LEVEL, 'x' FROM dual CONNECT BY LEVEL <= 20000;
COMMIT;
UPDATE mig_test SET memo = RPAD('x', 1000, 'x');
COMMIT;
ANALYZE TABLE mig_test COMPUTE STATISTICS;SELECT chain_cnt FROM user_tables WHERE table_name = 'MIG_TEST';ALTER TABLE mig_test ENABLE ROW MOVEMENT;
ALTER TABLE mig_test SHRINK SPACE;
ANALYZE TABLE mig_test COMPUTE STATISTICS;
ALTER TABLE mig_test MOVE;
ANALYZE TABLE mig_test COMPUTE STATISTICS;결과 해석:
| 시점 | CHAIN_CNT |
|---|---|
| 업데이트 뒤 | 18742 |
SHRINK SPACE 뒤 |
18742 |
ALTER TABLE ... MOVE 뒤 |
0 |
PCTFREE 0으로 블록에 여유를 남기지 않고 행을 키워 마이그레이션을 일으킨 실습임ALTER TABLE ... MOVE는 세그먼트를 새로 쓰므로 인덱스가 사용 불가 상태가 될 수 있다는 점은 기재 없음
DROP TABLE mig_demo PURGE;
DROP TABLE chn_demo PURGE;
DROP TABLE mig_test PURGE;
DROP TABLE chained_rows PURGE;결과 해석: 개별 실행 출력은 기재 없음
#사용 조건과 주의사항
실습에서는 블록 덤프(ALTER SYSTEM DUMP DATAFILE ... BLOCK...)와 트레이스 파일로 전달 주소(nrid), 블록 주소, 여유 공간(avsp, tosp), 행 조각 길이(tl)를 확인했음
SYS 권한이 필요하고 트레이스 파일을 만들므로 수업 자료도 강사 시연을 권하고 운영 데이터베이스에서는 쓰지 말라고 적었음
실습은 접속 PDB를 orclpdb와 orclppdb로 섞어 적었음