데이터베이스 연구실.log

행 체이닝과 마이그레이션

행 체이닝과 마이그레이션의 발생 조건, 행 조각과 CHAINED_ROWS 조회 및 MOVE 실습

ROW CHAININGROW MIGRATIONROW PIECECHAINED_ROWSANALYZE LIST CHAINED ROWStable fetch continued rowALTER TABLE 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 테이블 준비

SQL*Plus (일반 사용자)객체 또는 설정을 변경함
@?/rdbms/admin/utlchain.sql

결과 해석:

ANALYZE ... LIST CHAINED ROWS 결과를 받을 CHAINED_ROWS 테이블을 만듦
?는 SQL*Plus에서 ORACLE_HOME을 뜻함(확인 불가)

#업데이트로 생기는 마이그레이션

SQL*Plus (일반 사용자)객체 또는 설정을 변경함
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;
SQL*Plus (일반 사용자)데이터 또는 파일을 삭제함, 되돌릴 수 없음
DELETE FROM chained_rows;
COMMIT;

ANALYZE TABLE mig_demo LIST CHAINED ROWS INTO chained_rows;
SQL*Plus (일반 사용자)조회만 함
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바이트 행)

SQL*Plus (일반 사용자)객체 또는 설정을 변경함
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개 넘는 컬럼의 행 조각

SQL*Plus (일반 사용자)조회만 함
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로 없어지는 마이그레이션

SQL*Plus (일반 사용자)객체 또는 설정을 변경함
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;
SQL*Plus (일반 사용자)조회만 함
SELECT chain_cnt FROM user_tables WHERE table_name = 'MIG_TEST';
SQL*Plus (일반 사용자)객체 또는 설정을 변경함
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는 세그먼트를 새로 쓰므로 인덱스가 사용 불가 상태가 될 수 있다는 점은 기재 없음

SQL*Plus (일반 사용자)데이터 또는 파일을 삭제함, 되돌릴 수 없음
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로 섞어 적었음

SEO_HKabsumveritas@gmail.comBuilt with Claude Code & Codex

마지막 갱신 개인 학습 기록이며 운영 환경 적용 전 공식 문서 확인 필요

두 글자 이상 입력, 위아래 화살표로 이동하고 Enter로 열기