<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko">
	<id>https://dbstudy.co.kr/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dbstudy</id>
	<title>DB스터디 - 사용자 기여 [ko]</title>
	<link rel="self" type="application/atom+xml" href="https://dbstudy.co.kr/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dbstudy"/>
	<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/%ED%8A%B9%EC%88%98:%EA%B8%B0%EC%97%AC/Dbstudy"/>
	<updated>2026-05-08T13:28:07Z</updated>
	<subtitle>사용자 기여</subtitle>
	<generator>MediaWiki 1.39.10</generator>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=SQL_%ED%8A%B8%EB%A0%88%EC%9D%B4%EC%8A%A4_%EB%B0%A9%EB%B2%95&amp;diff=1652</id>
		<title>SQL 트레이스 방법</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=SQL_%ED%8A%B8%EB%A0%88%EC%9D%B4%EC%8A%A4_%EB%B0%A9%EB%B2%95&amp;diff=1652"/>
		<updated>2025-11-20T17:07:51Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== SQL 트레이스 ==&lt;br /&gt;
&lt;br /&gt;
=== SQL 세션 트레이스 수행 ===&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 0.트레이스 식별자 지정&lt;br /&gt;
alter session set tracefile_identifier=’DBSTUDY_TUN_DUMP’; ==&amp;gt; orcl_ora_10024_&amp;quot;DBSTUDY_TUN_DUMP&amp;quot;.trc 형태로 저장됨.&lt;br /&gt;
&lt;br /&gt;
-- 1. sql trace 시작 &lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
&lt;br /&gt;
-- 2. event 를 level 12 로 설정.   이렇게 설정하면, SQL trace 정보, 실행계획, 대기 이벤트 정보를 볼 수 있다.&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET events &#039;10046 trace name context forever, level 12&#039;;&lt;br /&gt;
--        · 10046 = Query가 수행되는 사건. 즉 Query를 수행하는 &amp;quot;사건&amp;quot;을 의미.&lt;br /&gt;
--        · trace name = Trace를 수행. 사건이 발생했을 때의 행위(Action)을 의미. &lt;br /&gt;
--        · context forever, level 12 = 사건이 발생할 때마다 행위를 수행하되, 행위의 레벨을 12로 &lt;br /&gt;
&lt;br /&gt;
-- 3.sql 실행 &lt;br /&gt;
select * from tb_xxx;&lt;br /&gt;
&lt;br /&gt;
-- 4. sql trace 종료&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- 5. 10046 event 끄기 &lt;br /&gt;
-- 꺼주지 않으면, 수행하는 모든 SQL 에 대한 정보를 모은다.&lt;br /&gt;
ALTER SESSION SET events &#039;10046 trace name context off&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- 6. trace 파일 확인 (12c 이상 에서 가능 )&lt;br /&gt;
SELECT * &lt;br /&gt;
  FROM (&lt;br /&gt;
        SELECT *&lt;br /&gt;
          FROM V$DIAG_TRACE_FILE&lt;br /&gt;
         ORDER BY CHANGE_TIME DESC&lt;br /&gt;
       )&lt;br /&gt;
 WHERE ROWNUM &amp;lt;= 10&lt;br /&gt;
&lt;br /&gt;
-- 7. trace 파일의 내용 추출 (12c 이상 에서 가능 )&lt;br /&gt;
SELECT replace(replace(payload,chr(13)),chr(10)) as payload&lt;br /&gt;
  FROM V$DIAG_TRACE_FILE_CONTENTS&lt;br /&gt;
 WHERE TRACE_FILENAME = &#039;xxxx.trc&#039;&lt;br /&gt;
 ORDER BY LINE_NUMBER&lt;br /&gt;
&lt;br /&gt;
-- 8. 텍스트를 파일로 저장후 윈도우용 tkprof로 명령 수행&lt;br /&gt;
tkprof xxx.trc xxx.txt sys=no&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 트레이스 시간 표시 및 트레이스파일 크기 제한 &lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 트레이스 파일 시간 정보 포함 &lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
&lt;br /&gt;
--  트레이스 파일 크기 제한&lt;br /&gt;
-- 무제한 &lt;br /&gt;
alter session set max_dump_file_size = &#039;unlimited&#039;;&lt;br /&gt;
&lt;br /&gt;
-- 100M&lt;br /&gt;
alter session set max_dump_file_size = 100M;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== SQL로 트레이스 내용 조회 (12C 이후)  ===&lt;br /&gt;
* 12c이후 부터는 서버(터미널)에서 하지 않아도 된다.&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT NAME,VALUE&lt;br /&gt;
  FROM V$DIAG_INFO&lt;br /&gt;
 WHERE NAME = &#039;Default Trace File&#039;;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT *&lt;br /&gt;
  FROM V$DIAG_TRACE_FILE&lt;br /&gt;
-- WHERE TRACE_FILENAME = &#039;xxxx.trc&#039;&lt;br /&gt;
;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT *&lt;br /&gt;
  FROM V$DIAG_TRACE_FILE_CONTENTS&lt;br /&gt;
 WHERE ADR_HOME = &#039;트레이스 디렉토리&#039;&lt;br /&gt;
   AND TRACE_FILENAME = &#039;트레이스파일명.trc&#039;;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
* 해당 뷰가 없으면 오라클 버전이 12c 이상인지 확인&lt;br /&gt;
&lt;br /&gt;
-- PC에서 보려면 window용 tkprof.exe가 있어야 내용을 볼수 있다.&lt;br /&gt;
&lt;br /&gt;
-- (오라클 클라이언트를 관리자용으로 설치 해야 함)&lt;br /&gt;
&lt;br /&gt;
-- PAYLOAD 컬럼이 트레이스 파일내용임 =&amp;gt;  복사하여 트레이스파일.trc 형태로 저장한다&lt;br /&gt;
* 더 간단하게 조회&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT   REPLACE (REPLACE (payload, CHR (13)), CHR (10)) AS payload&lt;br /&gt;
    FROM v$diag_trace_file_contents&lt;br /&gt;
   WHERE adr_home = &#039;트레이스 파일 디렉토리&#039;&lt;br /&gt;
     AND trace_filename = &#039;트레이스파일명.trc&#039;&lt;br /&gt;
ORDER BY line_number;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== SQL 트레이스 파일 생성 위치 확인 (11g )===&lt;br /&gt;
&lt;br /&gt;
* 11G이하 버전은 서버에서 작업 해야 한다&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT NAME,VALUE&lt;br /&gt;
  FROM V$DIAG_INFO&lt;br /&gt;
 WHERE NAME = &#039;Diag Trace&#039;;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
or&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SHOW parameter user_dump_dest&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 트레이스 파일 변경 및 내용 확인 ===&lt;br /&gt;
$&amp;gt; tkprof 트레이스파일명 변경할파일명 [explain= ] [table= ] [print= ] [insert= ] [sys= ] [sort= ] ...&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
$&amp;gt; tkprof 트레이스파일명 변경할파일명 sys=no &lt;br /&gt;
-- tkprof scott/tiger sys=no trace=orcl_ora_7180.trc output = report01.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== TKPROF 결과 값 ====&lt;br /&gt;
# Parse     &lt;br /&gt;
#: : SQL문이 파싱되는 단계에 대한 통계. 새로 파싱을 했거나 Shared SQL Pool에서 찾아 온 것도 같이 포함 된다.&lt;br /&gt;
# Execute &lt;br /&gt;
#: : SQL문의 실행 단계에 대한 통계. Update, Insert, Delete 문장들은 여기에 수행한 결과만 나온다.&lt;br /&gt;
# Fetch     &lt;br /&gt;
#: : SQL문이 실행되면서 패치된 통계&lt;br /&gt;
# count    &lt;br /&gt;
#: : SQL문이 파싱/실행/패치가 수행된 횟수&lt;br /&gt;
# cpu        &lt;br /&gt;
#: : parse, execute, fetch가 실제로 사용한 CPU시간&lt;br /&gt;
# elapsed &lt;br /&gt;
#: : 작업의 시작에서 종료시까지 실제 소요된 시간&lt;br /&gt;
# disk       &lt;br /&gt;
#: : 디스크에서 읽혀진 데이터 블럭의 수&lt;br /&gt;
# query    &lt;br /&gt;
#: :&#039;읽기 일관성&#039; 모드에서 처리한 블록 수. 여기에는 블록을 &#039;롤백&#039;하기 위해 롤백(undo) 세그먼트에서 읽은 블록 수가 포함됨.&lt;br /&gt;
# current  &lt;br /&gt;
#: : &#039;current&#039; 모드에서 읽은 블록 수. current 모드 블록은 일관된 읽기 방식이 아닌 현재 있는 그대로 검색됩니다. &lt;br /&gt;
#:   일반적으로 쿼리에 대한 블록 검색은 쿼리가 시작되었을 때 존재했던 그대로 가져옵니다. 현재 모드 블록은 이전 시점이 아닌 현재 있는 그대로 가져옵니다. &lt;br /&gt;
#:   SELECT 중에 테이블의 전체 스캔을 수행하기 위해 데이터 사전을 읽어서 익스텐트 정보를 찾기 때문에 current 모드 검색이 표시될 수 있습니다(일관된 읽기가 아닌 &#039;current&#039; 정보가 필요합니다). 수정하는 동안 현재 모드에서 블록에 액세스하여 쓰기를 수행합니다.&lt;br /&gt;
#:* 물리적 IO를 수행하는 데는 버퍼링된 읽기보다 시간이 오래 걸리므로 대부분의 경우 버퍼링된 읽기를 수행해야 합니다.&lt;br /&gt;
# rows      &lt;br /&gt;
#: : SQL문을 수행한 결과에 의해 최종적으로 액세스된 로우의 수&lt;br /&gt;
&lt;br /&gt;
[[category:oracle]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=SQL_%ED%8A%B8%EB%A0%88%EC%9D%B4%EC%8A%A4_%EB%B0%A9%EB%B2%95&amp;diff=1651</id>
		<title>SQL 트레이스 방법</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=SQL_%ED%8A%B8%EB%A0%88%EC%9D%B4%EC%8A%A4_%EB%B0%A9%EB%B2%95&amp;diff=1651"/>
		<updated>2025-11-20T17:07:38Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: 새 문서: == SQL 트레이스 ==  === SQL 세션 트레이스 수행 ==== &amp;lt;source lang=sql&amp;gt; -- 0.트레이스 식별자 지정 alter session set tracefile_identifier=’DBSTUDY_TUN_DUMP’; ==&amp;gt; orcl_ora_10024_&amp;quot;DBSTUDY_TUN_DUMP&amp;quot;.trc 형태로 저장됨.  -- 1. sql trace 시작  ALTER SESSION SET SQL_TRACE = TRUE;  -- 2. event 를 level 12 로 설정.   이렇게 설정하면, SQL trace 정보, 실행계획, 대기 이벤트 정보를 볼 수 있다.  ALTER SESSION SET events &amp;#039;10046 trace n...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== SQL 트레이스 ==&lt;br /&gt;
&lt;br /&gt;
=== SQL 세션 트레이스 수행 ====&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 0.트레이스 식별자 지정&lt;br /&gt;
alter session set tracefile_identifier=’DBSTUDY_TUN_DUMP’; ==&amp;gt; orcl_ora_10024_&amp;quot;DBSTUDY_TUN_DUMP&amp;quot;.trc 형태로 저장됨.&lt;br /&gt;
&lt;br /&gt;
-- 1. sql trace 시작 &lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
&lt;br /&gt;
-- 2. event 를 level 12 로 설정.   이렇게 설정하면, SQL trace 정보, 실행계획, 대기 이벤트 정보를 볼 수 있다.&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET events &#039;10046 trace name context forever, level 12&#039;;&lt;br /&gt;
--        · 10046 = Query가 수행되는 사건. 즉 Query를 수행하는 &amp;quot;사건&amp;quot;을 의미.&lt;br /&gt;
--        · trace name = Trace를 수행. 사건이 발생했을 때의 행위(Action)을 의미. &lt;br /&gt;
--        · context forever, level 12 = 사건이 발생할 때마다 행위를 수행하되, 행위의 레벨을 12로 &lt;br /&gt;
&lt;br /&gt;
-- 3.sql 실행 &lt;br /&gt;
select * from tb_xxx;&lt;br /&gt;
&lt;br /&gt;
-- 4. sql trace 종료&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- 5. 10046 event 끄기 &lt;br /&gt;
-- 꺼주지 않으면, 수행하는 모든 SQL 에 대한 정보를 모은다.&lt;br /&gt;
ALTER SESSION SET events &#039;10046 trace name context off&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- 6. trace 파일 확인 (12c 이상 에서 가능 )&lt;br /&gt;
SELECT * &lt;br /&gt;
  FROM (&lt;br /&gt;
        SELECT *&lt;br /&gt;
          FROM V$DIAG_TRACE_FILE&lt;br /&gt;
         ORDER BY CHANGE_TIME DESC&lt;br /&gt;
       )&lt;br /&gt;
 WHERE ROWNUM &amp;lt;= 10&lt;br /&gt;
&lt;br /&gt;
-- 7. trace 파일의 내용 추출 (12c 이상 에서 가능 )&lt;br /&gt;
SELECT replace(replace(payload,chr(13)),chr(10)) as payload&lt;br /&gt;
  FROM V$DIAG_TRACE_FILE_CONTENTS&lt;br /&gt;
 WHERE TRACE_FILENAME = &#039;xxxx.trc&#039;&lt;br /&gt;
 ORDER BY LINE_NUMBER&lt;br /&gt;
&lt;br /&gt;
-- 8. 텍스트를 파일로 저장후 윈도우용 tkprof로 명령 수행&lt;br /&gt;
tkprof xxx.trc xxx.txt sys=no&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 트레이스 시간 표시 및 트레이스파일 크기 제한 &lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 트레이스 파일 시간 정보 포함 &lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
&lt;br /&gt;
--  트레이스 파일 크기 제한&lt;br /&gt;
-- 무제한 &lt;br /&gt;
alter session set max_dump_file_size = &#039;unlimited&#039;;&lt;br /&gt;
&lt;br /&gt;
-- 100M&lt;br /&gt;
alter session set max_dump_file_size = 100M;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== SQL로 트레이스 내용 조회 (12C 이후)  ===&lt;br /&gt;
* 12c이후 부터는 서버(터미널)에서 하지 않아도 된다.&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT NAME,VALUE&lt;br /&gt;
  FROM V$DIAG_INFO&lt;br /&gt;
 WHERE NAME = &#039;Default Trace File&#039;;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT *&lt;br /&gt;
  FROM V$DIAG_TRACE_FILE&lt;br /&gt;
-- WHERE TRACE_FILENAME = &#039;xxxx.trc&#039;&lt;br /&gt;
;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT *&lt;br /&gt;
  FROM V$DIAG_TRACE_FILE_CONTENTS&lt;br /&gt;
 WHERE ADR_HOME = &#039;트레이스 디렉토리&#039;&lt;br /&gt;
   AND TRACE_FILENAME = &#039;트레이스파일명.trc&#039;;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
* 해당 뷰가 없으면 오라클 버전이 12c 이상인지 확인&lt;br /&gt;
&lt;br /&gt;
-- PC에서 보려면 window용 tkprof.exe가 있어야 내용을 볼수 있다.&lt;br /&gt;
&lt;br /&gt;
-- (오라클 클라이언트를 관리자용으로 설치 해야 함)&lt;br /&gt;
&lt;br /&gt;
-- PAYLOAD 컬럼이 트레이스 파일내용임 =&amp;gt;  복사하여 트레이스파일.trc 형태로 저장한다&lt;br /&gt;
* 더 간단하게 조회&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT   REPLACE (REPLACE (payload, CHR (13)), CHR (10)) AS payload&lt;br /&gt;
    FROM v$diag_trace_file_contents&lt;br /&gt;
   WHERE adr_home = &#039;트레이스 파일 디렉토리&#039;&lt;br /&gt;
     AND trace_filename = &#039;트레이스파일명.trc&#039;&lt;br /&gt;
ORDER BY line_number;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
=== SQL 트레이스 파일 생성 위치 확인 (11g )===&lt;br /&gt;
&lt;br /&gt;
* 11G이하 버전은 서버에서 작업 해야 한다&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT NAME,VALUE&lt;br /&gt;
  FROM V$DIAG_INFO&lt;br /&gt;
 WHERE NAME = &#039;Diag Trace&#039;;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
or&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SHOW parameter user_dump_dest&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 트레이스 파일 변경 및 내용 확인 ===&lt;br /&gt;
$&amp;gt; tkprof 트레이스파일명 변경할파일명 [explain= ] [table= ] [print= ] [insert= ] [sys= ] [sort= ] ...&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
$&amp;gt; tkprof 트레이스파일명 변경할파일명 sys=no &lt;br /&gt;
-- tkprof scott/tiger sys=no trace=orcl_ora_7180.trc output = report01.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== TKPROF 결과 값 ====&lt;br /&gt;
# Parse     &lt;br /&gt;
#: : SQL문이 파싱되는 단계에 대한 통계. 새로 파싱을 했거나 Shared SQL Pool에서 찾아 온 것도 같이 포함 된다.&lt;br /&gt;
# Execute &lt;br /&gt;
#: : SQL문의 실행 단계에 대한 통계. Update, Insert, Delete 문장들은 여기에 수행한 결과만 나온다.&lt;br /&gt;
# Fetch     &lt;br /&gt;
#: : SQL문이 실행되면서 패치된 통계&lt;br /&gt;
# count    &lt;br /&gt;
#: : SQL문이 파싱/실행/패치가 수행된 횟수&lt;br /&gt;
# cpu        &lt;br /&gt;
#: : parse, execute, fetch가 실제로 사용한 CPU시간&lt;br /&gt;
# elapsed &lt;br /&gt;
#: : 작업의 시작에서 종료시까지 실제 소요된 시간&lt;br /&gt;
# disk       &lt;br /&gt;
#: : 디스크에서 읽혀진 데이터 블럭의 수&lt;br /&gt;
# query    &lt;br /&gt;
#: :&#039;읽기 일관성&#039; 모드에서 처리한 블록 수. 여기에는 블록을 &#039;롤백&#039;하기 위해 롤백(undo) 세그먼트에서 읽은 블록 수가 포함됨.&lt;br /&gt;
# current  &lt;br /&gt;
#: : &#039;current&#039; 모드에서 읽은 블록 수. current 모드 블록은 일관된 읽기 방식이 아닌 현재 있는 그대로 검색됩니다. &lt;br /&gt;
#:   일반적으로 쿼리에 대한 블록 검색은 쿼리가 시작되었을 때 존재했던 그대로 가져옵니다. 현재 모드 블록은 이전 시점이 아닌 현재 있는 그대로 가져옵니다. &lt;br /&gt;
#:   SELECT 중에 테이블의 전체 스캔을 수행하기 위해 데이터 사전을 읽어서 익스텐트 정보를 찾기 때문에 current 모드 검색이 표시될 수 있습니다(일관된 읽기가 아닌 &#039;current&#039; 정보가 필요합니다). 수정하는 동안 현재 모드에서 블록에 액세스하여 쓰기를 수행합니다.&lt;br /&gt;
#:* 물리적 IO를 수행하는 데는 버퍼링된 읽기보다 시간이 오래 걸리므로 대부분의 경우 버퍼링된 읽기를 수행해야 합니다.&lt;br /&gt;
# rows      &lt;br /&gt;
#: : SQL문을 수행한 결과에 의해 최종적으로 액세스된 로우의 수&lt;br /&gt;
&lt;br /&gt;
[[category:oracle]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1650</id>
		<title>대문</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1650"/>
		<updated>2025-11-20T17:00:00Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 튜닝 개론 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__notoc__&lt;br /&gt;
https://dbstudy.co.kr/w/resources/assets/dbstudy_iconx1.png &lt;br /&gt;
= Welcome To DB STUDY - {{CURRENTYEAR}}.{{CURRENTMONTHNAME}}.{{CURRENTDAY}}({{CURRENTDAYNAME}}) =&lt;br /&gt;
= 전문가가 되고 싶다면 기본에 충실 하라. (Return To Basics) =&lt;br /&gt;
* {{SERVER}}&lt;br /&gt;
* 게시글 총 : {{NUMBEROFPAGES}} 건 , 사용자 : {{NUMBEROFUSERS}} 명 , &lt;br /&gt;
&amp;lt;p sizes=&amp;quot;(max-width: 600px) 480px,800px&amp;quot;&amp;gt;&lt;br /&gt;
https://dbstudy.co.kr/w/images/dbstudy_logo.jpg&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
{{틀:타이틀 라운드&lt;br /&gt;
|제목=[[:Category:oracle|오라클]]: {{PAGESINCATEGORY: oracle}} 건 ,  [[:Category:postgresql|PostgreSql]] : {{PAGESINCATEGORY: postgresql}} 건 ,  [[:Category:mysql|MySQL]]: {{PAGESINCATEGORY: mysql}} 건,  [[:Category:linux|LINUX]]: {{PAGESINCATEGORY: linux}} 건&lt;br /&gt;
|아이콘=emoji_objects&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= ORACLE =&lt;br /&gt;
== [[ORACLE SQL 튜닝]] ==&lt;br /&gt;
=== [[튜닝 개론]]===&lt;br /&gt;
:::# [[카디널리티 와 셀렉티비티|카디널리티(cardinality) 와 셀렉티비티(선택도,selectivity)]]&lt;br /&gt;
:::# [[성능을 고려한 설계]]&lt;br /&gt;
:::# [[효율적인 SQL 작성법]]&lt;br /&gt;
&lt;br /&gt;
=== [[튜닝 환경 구축]] ===&lt;br /&gt;
:::# [[DBMS_XPLAN 사용법]]&lt;br /&gt;
:::# [[REAL PLAN 사용법]]&lt;br /&gt;
:::# [[SQL 트레이스 방법]]&lt;br /&gt;
&lt;br /&gt;
=== [[인덱스 설계]] ===&lt;br /&gt;
:::# [[인덱스 아키텍처]]&lt;br /&gt;
:::# [[오라클 인덱스 종류|인덱스 종류]]&lt;br /&gt;
:::# [[엑세스 패스]]&lt;br /&gt;
&lt;br /&gt;
=== [[옵티마이져]] ===&lt;br /&gt;
==== [[JPPD(Join Predicate PushDown,조인절 PUSHDOWN)]]====&lt;br /&gt;
==== [[View pushed predicate (조건절 PUSHDOWN)]]====&lt;br /&gt;
&lt;br /&gt;
=== [[튜닝 힌트]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[대용량 데이터 튜닝]] ===&lt;br /&gt;
==== [[병렬 쿼리 튜닝]] ====&lt;br /&gt;
=== [[ORACLE 튜닝 대상 조회]] ===&lt;br /&gt;
=== [[성능 문제 식별 방법론과 튜닝 접근법]]===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== [[ORACLE 아키텍쳐의 이해]] ==&lt;br /&gt;
=== [[테이블 조인 방식|오라클 조인 과 알고리즘 ]] ===&lt;br /&gt;
==== [[NL 조인]]====&lt;br /&gt;
==== [[HASH 조인]]====&lt;br /&gt;
==== [[MERGE 조인|SORT MERGE 조인]]====&lt;br /&gt;
==== [[세미조인]] ====&lt;br /&gt;
==== [[안티조인]] ====&lt;br /&gt;
=== [[파라미터 설계]] ===&lt;br /&gt;
=== [[오라클 테이블의 구조]]===&lt;br /&gt;
=== [[오라클 파티션테이블의 구조]] ===&lt;br /&gt;
=== [[데이터 블럭의 구조]] ===&lt;br /&gt;
=== [[오라클 컬럼의 구조]] ===&lt;br /&gt;
==== [[오라클 컬럼의 저장순서]] ====&lt;br /&gt;
==== [[컬럼의 순서가 변경시 ROW의 물리적 구조변화|컬럼 순서 변경시 ROW의 물리구조 변화]]====&lt;br /&gt;
==== [[오라클 컬럼저장 방식 개선 (12c 업그레이드) ]] ====&lt;br /&gt;
=== [[오라클 인덱스의 구조]] ===&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)]]=&lt;br /&gt;
== [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)#개요|개요]] ==&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1649</id>
		<title>대문</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1649"/>
		<updated>2025-11-20T16:58:39Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 튜닝 개론 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__notoc__&lt;br /&gt;
https://dbstudy.co.kr/w/resources/assets/dbstudy_iconx1.png &lt;br /&gt;
= Welcome To DB STUDY - {{CURRENTYEAR}}.{{CURRENTMONTHNAME}}.{{CURRENTDAY}}({{CURRENTDAYNAME}}) =&lt;br /&gt;
= 전문가가 되고 싶다면 기본에 충실 하라. (Return To Basics) =&lt;br /&gt;
* {{SERVER}}&lt;br /&gt;
* 게시글 총 : {{NUMBEROFPAGES}} 건 , 사용자 : {{NUMBEROFUSERS}} 명 , &lt;br /&gt;
&amp;lt;p sizes=&amp;quot;(max-width: 600px) 480px,800px&amp;quot;&amp;gt;&lt;br /&gt;
https://dbstudy.co.kr/w/images/dbstudy_logo.jpg&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
{{틀:타이틀 라운드&lt;br /&gt;
|제목=[[:Category:oracle|오라클]]: {{PAGESINCATEGORY: oracle}} 건 ,  [[:Category:postgresql|PostgreSql]] : {{PAGESINCATEGORY: postgresql}} 건 ,  [[:Category:mysql|MySQL]]: {{PAGESINCATEGORY: mysql}} 건,  [[:Category:linux|LINUX]]: {{PAGESINCATEGORY: linux}} 건&lt;br /&gt;
|아이콘=emoji_objects&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= ORACLE =&lt;br /&gt;
== [[ORACLE SQL 튜닝]] ==&lt;br /&gt;
=== [[튜닝 개론]]===&lt;br /&gt;
:::# [[카디널리티 와 셀렉티비티|카디널리티(cardinality) 와 셀렉티비티(selectivity)]]&lt;br /&gt;
:::# [[성능을 고려한 설계]]&lt;br /&gt;
:::# [[효율적인 SQL 작성법]]&lt;br /&gt;
&lt;br /&gt;
=== [[튜닝 환경 구축]] ===&lt;br /&gt;
:::# [[DBMS_XPLAN 사용법]]&lt;br /&gt;
:::# [[REAL PLAN 사용법]]&lt;br /&gt;
:::# [[SQL 트레이스 방법]]&lt;br /&gt;
&lt;br /&gt;
=== [[인덱스 설계]] ===&lt;br /&gt;
:::# [[인덱스 아키텍처]]&lt;br /&gt;
:::# [[오라클 인덱스 종류|인덱스 종류]]&lt;br /&gt;
:::# [[엑세스 패스]]&lt;br /&gt;
&lt;br /&gt;
=== [[옵티마이져]] ===&lt;br /&gt;
==== [[JPPD(Join Predicate PushDown,조인절 PUSHDOWN)]]====&lt;br /&gt;
==== [[View pushed predicate (조건절 PUSHDOWN)]]====&lt;br /&gt;
&lt;br /&gt;
=== [[튜닝 힌트]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[대용량 데이터 튜닝]] ===&lt;br /&gt;
==== [[병렬 쿼리 튜닝]] ====&lt;br /&gt;
=== [[ORACLE 튜닝 대상 조회]] ===&lt;br /&gt;
=== [[성능 문제 식별 방법론과 튜닝 접근법]]===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== [[ORACLE 아키텍쳐의 이해]] ==&lt;br /&gt;
=== [[테이블 조인 방식|오라클 조인 과 알고리즘 ]] ===&lt;br /&gt;
==== [[NL 조인]]====&lt;br /&gt;
==== [[HASH 조인]]====&lt;br /&gt;
==== [[MERGE 조인|SORT MERGE 조인]]====&lt;br /&gt;
==== [[세미조인]] ====&lt;br /&gt;
==== [[안티조인]] ====&lt;br /&gt;
=== [[파라미터 설계]] ===&lt;br /&gt;
=== [[오라클 테이블의 구조]]===&lt;br /&gt;
=== [[오라클 파티션테이블의 구조]] ===&lt;br /&gt;
=== [[데이터 블럭의 구조]] ===&lt;br /&gt;
=== [[오라클 컬럼의 구조]] ===&lt;br /&gt;
==== [[오라클 컬럼의 저장순서]] ====&lt;br /&gt;
==== [[컬럼의 순서가 변경시 ROW의 물리적 구조변화|컬럼 순서 변경시 ROW의 물리구조 변화]]====&lt;br /&gt;
==== [[오라클 컬럼저장 방식 개선 (12c 업그레이드) ]] ====&lt;br /&gt;
=== [[오라클 인덱스의 구조]] ===&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)]]=&lt;br /&gt;
== [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)#개요|개요]] ==&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1648</id>
		<title>대문</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1648"/>
		<updated>2025-11-20T16:58:00Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 튜닝 개론 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__notoc__&lt;br /&gt;
https://dbstudy.co.kr/w/resources/assets/dbstudy_iconx1.png &lt;br /&gt;
= Welcome To DB STUDY - {{CURRENTYEAR}}.{{CURRENTMONTHNAME}}.{{CURRENTDAY}}({{CURRENTDAYNAME}}) =&lt;br /&gt;
= 전문가가 되고 싶다면 기본에 충실 하라. (Return To Basics) =&lt;br /&gt;
* {{SERVER}}&lt;br /&gt;
* 게시글 총 : {{NUMBEROFPAGES}} 건 , 사용자 : {{NUMBEROFUSERS}} 명 , &lt;br /&gt;
&amp;lt;p sizes=&amp;quot;(max-width: 600px) 480px,800px&amp;quot;&amp;gt;&lt;br /&gt;
https://dbstudy.co.kr/w/images/dbstudy_logo.jpg&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
{{틀:타이틀 라운드&lt;br /&gt;
|제목=[[:Category:oracle|오라클]]: {{PAGESINCATEGORY: oracle}} 건 ,  [[:Category:postgresql|PostgreSql]] : {{PAGESINCATEGORY: postgresql}} 건 ,  [[:Category:mysql|MySQL]]: {{PAGESINCATEGORY: mysql}} 건,  [[:Category:linux|LINUX]]: {{PAGESINCATEGORY: linux}} 건&lt;br /&gt;
|아이콘=emoji_objects&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= ORACLE =&lt;br /&gt;
== [[ORACLE SQL 튜닝]] ==&lt;br /&gt;
=== [[튜닝 개론]]===&lt;br /&gt;
:::# [[카디널리티(cardinality) 와 셀렉티비티(selectivity)]]&lt;br /&gt;
:::# [[성능을 고려한 설계]]&lt;br /&gt;
:::# [[효율적인 SQL 작성법]]&lt;br /&gt;
&lt;br /&gt;
=== [[튜닝 환경 구축]] ===&lt;br /&gt;
:::# [[DBMS_XPLAN 사용법]]&lt;br /&gt;
:::# [[REAL PLAN 사용법]]&lt;br /&gt;
:::# [[SQL 트레이스 방법]]&lt;br /&gt;
&lt;br /&gt;
=== [[인덱스 설계]] ===&lt;br /&gt;
:::# [[인덱스 아키텍처]]&lt;br /&gt;
:::# [[오라클 인덱스 종류|인덱스 종류]]&lt;br /&gt;
:::# [[엑세스 패스]]&lt;br /&gt;
&lt;br /&gt;
=== [[옵티마이져]] ===&lt;br /&gt;
==== [[JPPD(Join Predicate PushDown,조인절 PUSHDOWN)]]====&lt;br /&gt;
==== [[View pushed predicate (조건절 PUSHDOWN)]]====&lt;br /&gt;
&lt;br /&gt;
=== [[튜닝 힌트]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[대용량 데이터 튜닝]] ===&lt;br /&gt;
==== [[병렬 쿼리 튜닝]] ====&lt;br /&gt;
=== [[ORACLE 튜닝 대상 조회]] ===&lt;br /&gt;
=== [[성능 문제 식별 방법론과 튜닝 접근법]]===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== [[ORACLE 아키텍쳐의 이해]] ==&lt;br /&gt;
=== [[테이블 조인 방식|오라클 조인 과 알고리즘 ]] ===&lt;br /&gt;
==== [[NL 조인]]====&lt;br /&gt;
==== [[HASH 조인]]====&lt;br /&gt;
==== [[MERGE 조인|SORT MERGE 조인]]====&lt;br /&gt;
==== [[세미조인]] ====&lt;br /&gt;
==== [[안티조인]] ====&lt;br /&gt;
=== [[파라미터 설계]] ===&lt;br /&gt;
=== [[오라클 테이블의 구조]]===&lt;br /&gt;
=== [[오라클 파티션테이블의 구조]] ===&lt;br /&gt;
=== [[데이터 블럭의 구조]] ===&lt;br /&gt;
=== [[오라클 컬럼의 구조]] ===&lt;br /&gt;
==== [[오라클 컬럼의 저장순서]] ====&lt;br /&gt;
==== [[컬럼의 순서가 변경시 ROW의 물리적 구조변화|컬럼 순서 변경시 ROW의 물리구조 변화]]====&lt;br /&gt;
==== [[오라클 컬럼저장 방식 개선 (12c 업그레이드) ]] ====&lt;br /&gt;
=== [[오라클 인덱스의 구조]] ===&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)]]=&lt;br /&gt;
== [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)#개요|개요]] ==&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EB%B6%84%EC%84%9D_%EB%8B%A8%EA%B3%84&amp;diff=1636</id>
		<title>분석 단계</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EB%B6%84%EC%84%9D_%EB%8B%A8%EA%B3%84&amp;diff=1636"/>
		<updated>2025-10-24T02:16:47Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== [[분석 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
::::# [[인터뷰 계획서]]&lt;br /&gt;
::::# [[인터뷰 결과서]]&lt;br /&gt;
::::# [[요구사항 정의서]]&lt;br /&gt;
::::# [[현행 시스템 분석서]]&lt;br /&gt;
::::## 별첨1)현행시스템분석서-DB아키텍처)-별첨1-현행_데이터파일현황.xlsx&lt;br /&gt;
::::## 별첨2)현행시스템분석서-DB아키텍처)-별첨1-현행_테이블스페이스현황.xlsx&lt;br /&gt;
::::# [[아키텍처 정의서]]&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[AS-IS 구조 분석 기술환경 파악]]&lt;br /&gt;
# [[요구사항 및 문제점 도출]]&lt;br /&gt;
# [[TO-BE 설계를 위한 기초자료 확보]]&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1635</id>
		<title>HASH 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1635"/>
		<updated>2025-10-24T02:11:55Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 해시 조인(Hash Join) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해시 조인(Hash Join) ==&lt;br /&gt;
{{틀:서브&lt;br /&gt;
|제목=&#039;&#039;&#039;&amp;lt;big&amp;gt;해시조인시 사용되는 해시(hash)함수는 입력된 데이터를 &#039;잘게 다져서&#039; 고유하고 일정한 크기의 식별자로 만들어 냄.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
:* 해시의 주요특징&lt;br /&gt;
:** 단방향성 : 해시값을 원래의 값으로 되돌릴수 없다.&lt;br /&gt;
:** 고유성 : 입력값이 바뀌면 해시값은 완전히 달라진다.&lt;br /&gt;
:** 고정된길이 : 입력값의 크기와 상관없이 항상 동일한 길이의 출력값을 가진다.&lt;br /&gt;
|내용=* hash의 영문의 뜻 : &#039;&#039;&#039;잘게 썰다, 다지다의 의미(요리에서 재료를 잘게 썰어서 섞을때 쓰이는말, 예로 맥도날도 &#039;해시브라운(감자를 다져서 튀긴요리)&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
# 해시 조인(Hash Join)은 두 개의 테이블을 조인할 때, 더 작은 테이블을 메모리에 올리고 해시 테이블을 만들어 효율적으로 조인하는 방식&lt;br /&gt;
# 대용량 테이블을 조인할 때 주로 사용되며, 조인 조건에 인덱스가 없는 경우에도 빠르게 작동&lt;br /&gt;
&lt;br /&gt;
=== 해시 조인의 원리 ===&lt;br /&gt;
* 해시 조인은 크게 두 단계로 나뉩니다.&lt;br /&gt;
# 빌드 단계 (Build Phase):&lt;br /&gt;
## 옵티마이저는 두 테이블 중 더 작은 테이블(빌드 테이블)을 선택&lt;br /&gt;
## 이 테이블의 조인 키(컬럼)를 사용하여 PGA 메모리 영역에 해시 테이블을 생성. 각 조인 키 값은 해시 함수를 통하여 해시값으로 변환되어 해시 테이블의 특정 위치에 저장.&lt;br /&gt;
## 해시테이블 저장시 변환된 해시값들은 해시버킷의 갯수 만큼 쪼개서 할당되고 &amp;quot;버킷&amp;quot;에 행을 연결리스트(체인) 형태로 저장됨.&lt;br /&gt;
## 각 버킷은 &amp;quot;해당 해시 범위에 속하는 행들의 포인터(체인 헤드)&amp;quot; 역활을 하며 , 동일 해시값 이나 충돌이 발생하면 같은 버킷내에서 체인으로 연결됨.(많이 연결될수록 성능 저하,이구간이 성능저하 구간) &lt;br /&gt;
# 프로브 단계 (Probe Phase):&lt;br /&gt;
## 더 큰 테이블(프로브 테이블)을 순차적으로 읽어옮&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 테이블에서 일치하는 행을 찾음.(대상 버킷을 찾고 해당 버킷의 체인을 스캔하여 조인키를 비교)&lt;br /&gt;
## 일치하는 행을 찾으면, 두 테이블의 행을 결합하여 결과를 반환.&lt;br /&gt;
&lt;br /&gt;
* 만약 빌드 테이블이 PGA 메모리에 다 들어가지 않을 정도로 크다면, 테이블을 여러 개의 파티션으로 나누어 디스크에 임시로 저장한 뒤(성능 저하 구간), 각 파티션별로 빌드와 프로브 단계를 반복하는 &#039;그레이스풀 해시 조인(Graceful Hash Join)&#039;이 수행(성능저하 구간)&lt;br /&gt;
* 런타임 파티셔닝을 수행해 부분(배치/파티션) 단위로 템프에 내린 후(DISK I/O발생,성능저하) 다시 합쳐서 1-pass/멀티패스 방식으로 진행함.&lt;br /&gt;
&lt;br /&gt;
=== 파이썬 예제 ===&lt;br /&gt;
* 두 개의 CSV 파일 sales.csv와 products.csv가 있다고 가정하고, 이를 파이썬 딕셔너리로 해시 조인을 구현하는 예제.&lt;br /&gt;
1. 데이터 준비 (sales.csv, products.csv 파일)&lt;br /&gt;
&lt;br /&gt;
1) 대량 테이블 - 판매(sales.csv 파일)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sale_id,product_id,amount&lt;br /&gt;
1,101,100&lt;br /&gt;
2,102,150&lt;br /&gt;
3,101,200&lt;br /&gt;
4,103,50&lt;br /&gt;
....&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2) 제품 (products.csv)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
product_id,product_name&lt;br /&gt;
101,Laptop&lt;br /&gt;
102,Mouse&lt;br /&gt;
103,Keyboard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 해시 조인 구현 코드&lt;br /&gt;
&lt;br /&gt;
소량인 products 테이블을 빌드 테이블로, sales 테이블을 프로브 테이블로 사용하여 조인을 수행.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import csv&lt;br /&gt;
&lt;br /&gt;
# 1. 빌드 테이블: products.csv를 읽어 메모리 해시 테이블(딕셔너리) 생성&lt;br /&gt;
# &#039;product_id&#039;를 키로, &#039;product_name&#039;을 값으로 저장&lt;br /&gt;
products_hash_table = {}&lt;br /&gt;
with open(&#039;products.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # 해시 테이블 생성 (딕셔너리)&lt;br /&gt;
        products_hash_table[row[&#039;product_id&#039;]] = row[&#039;product_name&#039;]&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;--- 빌드 단계 (해시 테이블) ---&amp;quot;)&lt;br /&gt;
print(products_hash_table)&lt;br /&gt;
# 출력: {&#039;101&#039;: &#039;Laptop&#039;, &#039;102&#039;: &#039;Mouse&#039;, &#039;103&#039;: &#039;Keyboard&#039;}&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;\n--- 프로브 단계 (조인 결과) ---&amp;quot;)&lt;br /&gt;
# 2. 프로브 테이블: sales.csv를 읽어 해시 테이블에서 찾기&lt;br /&gt;
with open(&#039;sales.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # sale_id 3의 &#039;product_id&#039;인 &#039;101&#039;을 해시 테이블에서 찾음&lt;br /&gt;
        product_id = row[&#039;product_id&#039;]&lt;br /&gt;
        if product_id in products_hash_table:&lt;br /&gt;
            # 해시 테이블에서 &#039;product_id&#039;를 키로 검색하여 &#039;product_name&#039;을 가져옴&lt;br /&gt;
            product_name = products_hash_table[product_id]&lt;br /&gt;
            print(f&amp;quot;Sale ID: {row[&#039;sale_id&#039;]}, Product Name: {product_name}, Amount: {row[&#039;amount&#039;]}&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 예상 출력:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Sale ID: 1, Product Name: Laptop, Amount: 100&lt;br /&gt;
# Sale ID: 2, Product Name: Mouse, Amount: 150&lt;br /&gt;
# Sale ID: 3, Product Name: Laptop, Amount: 200&lt;br /&gt;
# Sale ID: 4, Product Name: Keyboard, Amount: 50&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# products_hash_table이라는 딕셔너리가 바로 해시 테이블 역할을 합니다. &lt;br /&gt;
# sales 테이블의 각 행을 읽을 때마다 product_id를 딕셔너리(해시 테이블)에서 빠르게 찾아 product_name을 가져와 조인된 결과를 출력하는 방식&lt;br /&gt;
# 이를 통해 전체 products 테이블을 다시 스캔할 필요 없이 O(1)에 가까운 시간 복잡도로 조인 조건을 만족하는 데이터를 찾을 수 있음.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 성능 향상 원리 ===&lt;br /&gt;
==== Hash 버킷의 역할과 기능 ====&lt;br /&gt;
::::* 해시 버킷은 두 테이블을 효율적으로 조인하기 위한 핵심적인 논리적 저장 공간. &lt;br /&gt;
::::* 이는 조인 키(Join Key)를 기반으로 데이터를 빠르게 찾고 저장하는 데 사용.&lt;br /&gt;
&lt;br /&gt;
===== 역할: 데이터의 논리적 분류 및 저장소 =====&lt;br /&gt;
:::::* 해시 버킷은 조인 키 값을 해시 함수에 넣어 얻은 해시 값이 같은 데이터들을 모아두는 공간.&lt;br /&gt;
:::::* 이는 거대한 데이터를 조인 키를 기준으로 더 작은 그룹으로 나누어 관리하는 역할을 합니다.&lt;br /&gt;
:::::* 이를 통해 빌드 테이블의 모든 데이터를 일일이 탐색하지 않고도, 특정 조인 키에 해당하는 데이터가 어느 버킷에 있는지 바로 알 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
===== 기능: Build 단계와 Probe 단계에서의 활용 =====&lt;br /&gt;
# Build 단계:&lt;br /&gt;
## Oracle은 먼저 두 테이블 중 더 작은 테이블을 선택하여 빌드 테이블로 지정합니다.&lt;br /&gt;
## 빌드 테이블의 각 행을 읽어 조인 키에 해시 함수를 적용하여 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값에 해당하는 버킷에 해당 행의 데이터를 저장합니다. 만약 같은 버킷에 이미 다른 데이터가 있다면, 이를 연결 리스트(Linked List) 형태로 연결하여 **해시 충돌(Hash Collision)**을 처리합니다.&lt;br /&gt;
# Probe 단계:&lt;br /&gt;
## 이제 더 큰 테이블인 프로브 테이블을 순차적으로 읽습니다.&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 버킷 위치를 즉시 찾아갑니다.&lt;br /&gt;
## 해당 버킷 안의 연결 리스트를 탐색하여 조인 키가 일치하는 행을 찾습니다.&lt;br /&gt;
## 일치하는 행이 발견되면 두 테이블의 데이터를 결합하여 최종 결과를 생성합니다.&lt;br /&gt;
&lt;br /&gt;
* 이러한 해시 버킷의 활용 덕분에, 해시 조인은 테이블의 크기와 상관없이 조인 키가 같은 데이터들을 O(1)에 가까운 시간 복잡도로 찾아낼 수 있어 대용량 데이터 조인에 매우 효과적입니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== [[해시 버킷의 저장구조]]====&lt;br /&gt;
&lt;br /&gt;
==== 해시 버킷 개수의 결정 ====&lt;br /&gt;
* 해시 조인의 최대 해시 버킷 개수는 사전 설정된 고정값이 아니라, 조인하려는 테이블의 크기, 옵티마이저의 예측, 그리고 PGA_AGGREGATE_TARGET 파라미터에 의해 동적으로 결정&lt;br /&gt;
&lt;br /&gt;
# 빌드 테이블(Build Table)의 크기: 옵티마이저가 예측한 더 작은 테이블(빌드 테이블)의 크기에 따라 필요한 해시 버킷의 수가 결정됨&lt;br /&gt;
# 할당된 PGA 메모리: PGA_AGGREGATE_TARGET에 의해 세션에 할당된 PGA 메모리 내에서 해시 테이블이 생성됨.&lt;br /&gt;
# 메모리 부족 시: 만약 할당된 PGA 메모리만으로 해시 테이블을 모두 생성할 수 없다면, Oracle은 일부 해시 버킷을 디스크의 임시 테이블스페이스(temp tablespace)로 분할하여 저장합니다. &lt;br /&gt;
## 이를 &#039;해시 스필(Hash Spill)&#039;이라고 부르며, 이 경우 I/O가 발생하여 성능이 저하됨.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인이 느려지는 원인 ====&lt;br /&gt;
* 해시 조인 성능이 느려지는 주요 원인 중 하나는 해시 버킷 때문입니다.&lt;br /&gt;
* 정확히 말하면, 해시 충돌이 심하게 발생하거나 해시 테이블이 메모리에 맞지 않아 디스크 I/O가 발생하는 경우입니다.&lt;br /&gt;
* 해시 버킷 자체는 효율성을 위한 도구지만, 비정상적인 상황에서는 오히려 성능 저하의 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
1. 해시 충돌 (Hash Collisions)&lt;br /&gt;
* 원리: 해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다.&lt;br /&gt;
* 문제점: 해시 충돌이 발생하면, 하나의 버킷에 여러 행이 연결 리스트 형태로 쌓이게 됩니다. 이 버킷을 탐색할 때, 원하는 데이터를 찾기 위해 연결된 모든 데이터를 순차적으로 비교해야 하므로, 탐색 시간이 O(1)에서 O(N)으로 늘어납니다.&lt;br /&gt;
* 결과: 조인 키 데이터 분포가 불균형하거나(데이터 편중), 해시 함수가 비효율적이면 해시 충돌이 자주 발생하여 성능이 크게 저하됩니다.&lt;br /&gt;
* (결론) 해시조인은 조인키로 사용되는 컬럼이 중복이 많을수록  성능이 좋지 않음&lt;br /&gt;
&lt;br /&gt;
* 파이썬으로 해시 충돌 구현 예제&lt;br /&gt;
** 고의로 해시 충돌을 유발하는 데이터를 사용하여 해시 조인의 성능 차이를 보여줍니다. &lt;br /&gt;
** products_collision 리스트의 모든 product_id는 해시 함수를 거치면 같은 값을 반환하도록 설계되었습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터 (해시 충돌이 거의 없음)&lt;br /&gt;
products_normal = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 102, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 103, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 의도적으로 해시 충돌을 유발하는 데이터&lt;br /&gt;
# 101, 201, 301 모두 100으로 나눈 나머지가 1이 되도록 설계&lt;br /&gt;
products_collision = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 201, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 301, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인 대상 데이터 (각 product_id에 대한 sales 데이터)&lt;br /&gt;
sales = [{&#039;sale_id&#039;: i, &#039;product_id&#039;: 101, &#039;amount&#039;: 100} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+1000, &#039;product_id&#039;: 201, &#039;amount&#039;: 150} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+2000, &#039;product_id&#039;: 301, &#039;amount&#039;: 200} for i in range(1000)]&lt;br /&gt;
&lt;br /&gt;
# 사용자 정의 해시 함수 (단순화를 위해)&lt;br /&gt;
def custom_hash(key):&lt;br /&gt;
    return key % 100&lt;br /&gt;
&lt;br /&gt;
def hash_join(build_table, probe_table):&lt;br /&gt;
    # 빌드 단계: 해시 버킷(딕셔너리) 생성&lt;br /&gt;
    # 파이썬 딕셔너리의 키 충돌을 구현하기 위해, 딕셔너리의 값을 리스트로 저장&lt;br /&gt;
    hash_buckets = {}&lt;br /&gt;
    for item in build_table:&lt;br /&gt;
        key = item[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        if bucket_index not in hash_buckets:&lt;br /&gt;
            hash_buckets[bucket_index] = []&lt;br /&gt;
        hash_buckets[bucket_index].append(item)&lt;br /&gt;
&lt;br /&gt;
    # 프로브 단계: 조인&lt;br /&gt;
    start_time = time.time()&lt;br /&gt;
    joined_results = []&lt;br /&gt;
    for sale in probe_table:&lt;br /&gt;
        key = sale[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        &lt;br /&gt;
        # 해시 버킷에서 일치하는 데이터를 찾기 (충돌 발생 시 순차 탐색)&lt;br /&gt;
        if bucket_index in hash_buckets:&lt;br /&gt;
            for product_info in hash_buckets[bucket_index]:&lt;br /&gt;
                if product_info[&#039;product_id&#039;] == key:&lt;br /&gt;
                    joined_results.append({**sale, **product_info}) # **은 언패킹연산자 {**A,**B} 는 A,B딕셔너리를 합쳐줌.&lt;br /&gt;
                    break&lt;br /&gt;
    end_time = time.time()&lt;br /&gt;
    return end_time - start_time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터로 조인&lt;br /&gt;
normal_time = hash_join(products_normal, sales)&lt;br /&gt;
&lt;br /&gt;
# 해시 충돌 데이터로 조인&lt;br /&gt;
collision_time = hash_join(products_collision, sales)&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;일반 데이터 조인 시간: {normal_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;해시 충돌 조인 시간: {collision_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;성능 저하 비율: {(collision_time / normal_time):.2f}배 느림&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
일반 데이터 조인 시간: 0.000971 초&lt;br /&gt;
해시 충돌 조인 시간: 0.001568 초&lt;br /&gt;
성능 저하 비율: 1.61배 느림&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 메모리 부족 및 디스크 I/O&lt;br /&gt;
* 원리: 해시 조인은 더 작은 테이블(빌드 테이블)을 메모리에 올려 해시 테이블을 만드는 것이 기본 전제입니다.&lt;br /&gt;
* 문제점: 빌드 테이블이 너무 커서 **메모리(PGA)**에 다 담을 수 없게 되면, Oracle은 해시 테이블을 여러 파티션으로 나누어 일부를 **임시 테이블스페이스(Temporary Tablespace)**에 저장합니다.&lt;br /&gt;
* 결과: 조인을 수행하기 위해 디스크에 기록된 파티션 데이터를 읽고 쓰는 디스크 I/O 작업이 반복적으로 발생합니다. 이는 메모리에서만 작업할 때보다 훨씬 느리므로 성능 저하의 결정적인 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인에 대한 의문점 ====&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목=&lt;br /&gt;
* hash join에서  해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다. 또한 조인키로 사용되는 컬럼이 중복이 많을수록 성능이 좋지 않습니다.  &lt;br /&gt;
|내용= *&lt;br /&gt;
# 그러면 조인키로 사용되는 &#039;&#039;&#039;컬럼의 값&#039;&#039;&#039; 이 중복이 심하면 성능 않좋아 진다는것인가? &lt;br /&gt;
# 아니면 해시함수를 통하여 생성된 해시값이 동일한경우에 성능이 않좋아 진다는 것인가? &lt;br /&gt;
# 둘다 해당한다는것인가?&lt;br /&gt;
}}&lt;br /&gt;
* 둘 다 성능에 영향을 미치지만, 서로 다른 이유이며 실무에서는 1번이 훨씬 더 큰 문제입니다.&lt;br /&gt;
&lt;br /&gt;
===== 조인 키 값의 중복 (더 흔한 문제) =====&lt;br /&gt;
* 조인 키 컬럼 자체의 중복이 많으면 성능이 나빠집니다.&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시: 성별 컬럼으로 조인 (값이 &#039;M&#039;, &#039;F&#039; 두 개뿐)&lt;br /&gt;
SELECT /*+ USE_HASH(a b) */ *&lt;br /&gt;
FROM employees a&lt;br /&gt;
JOIN customers b ON a.gender = b.gender&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- `gender = &#039;M&#039;` 인 행들이 모두 &#039;&#039;&#039;같은 해시 버킷&#039;&#039;&#039;에 저장됨&lt;br /&gt;
:- Probe 단계에서 해당 버킷의 수많은 행들을 일일이 비교해야 함&lt;br /&gt;
:- 해시 조인의 장점(O(1) 검색)이 사라짐&lt;br /&gt;
&lt;br /&gt;
===== 해시 충돌 (드문 문제) =====&lt;br /&gt;
* 서로 다른 조인 키 값이 우연히 같은 해시 값을 생성하는 경우입니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 예시 (실제는 아니지만 개념 설명)&lt;br /&gt;
hash(&#039;emp_id_12345&#039;) = 789  # 해시값 789&lt;br /&gt;
hash(&#039;emp_id_99999&#039;) = 789  # 다른 키인데 같은 해시값!&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- 서로 다른 키인데 같은 버킷에 저장됨&lt;br /&gt;
:- 역시 Probe 시 추가 비교 필요&lt;br /&gt;
&lt;br /&gt;
===== 실제 상황 비교 =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 A: 카디널리티 낮음 (중복 많음) - 실제 큰 문제&lt;br /&gt;
employees: 1,000,000명&lt;br /&gt;
성별(gender)컬럼 값: 남자(&#039;M&#039;), 여자(&#039;F&#039;) 두 개만&lt;br /&gt;
→ 각 버킷에 약 500,000개 행 저장&lt;br /&gt;
→ 성능 매우 나쁨&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 B: 카디널리티 높음 (중복 적음) - 좋음&lt;br /&gt;
employees: 1,000,000명  &lt;br /&gt;
employee_id: 1,000,000개 유니크 값&lt;br /&gt;
→ 각 버킷에 평균 1~2개 행&lt;br /&gt;
→ 해시 충돌 있어도 영향 미미&lt;br /&gt;
→ 성능 좋음&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목=해시충돌은 언제 발생되는가? build 단계인가? probe 단계인가? 아니면 둘다 인가?&lt;br /&gt;
&lt;br /&gt;
|내용=&lt;br /&gt;
Oracle의 Hash Join에서 hash 충돌은 build 단계에서 발생 합니다.&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Hash Join의 동작 과정&lt;br /&gt;
&lt;br /&gt;
1. Build 단계 (작은 테이블)&lt;br /&gt;
:: - 작은 테이블(build input)을 읽어서 메모리(PGA)에 hash table 생성&lt;br /&gt;
:: - 각 row의 join key에 hash function을 적용하여 hash bucket에 저장&lt;br /&gt;
:: - 이때 서로 다른 key 값이 같은 hash 값을 가지면 hash 충돌 발생&lt;br /&gt;
:: - 충돌 발생 시 chaining 방식으로 같은 bucket에 여러 row를 연결&lt;br /&gt;
&lt;br /&gt;
2. Probe 단계 (큰 테이블)&lt;br /&gt;
&lt;br /&gt;
:: - 큰 테이블(probe input)을 읽으면서 각 row의 join key를 hash&lt;br /&gt;
:: - 해당 hash bucket을 찾아가서 실제 값을 비교 (equality check)&lt;br /&gt;
:: - 충돌이 발생했던 bucket의 경우 chain을 따라가며 모든 값과 비교&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
:::: Hash 충돌은 build 단계에서 hash table을 구성할 때 발생하며, probe 단계에서는 이미 발생한 충돌을 처리(chain 탐색)하는 것입니다.&lt;br /&gt;
:::: 충돌이 많아지면 probe 단계에서 chain을 따라가며 비교해야 할 row가 많아져 성능이 저하될 수 있습니다. &lt;br /&gt;
:::: Oracle은 이를 위해 적절한 hash bucket 수를 자동으로 조정합니다.​​​​​​​​​​​​​​​​&lt;br /&gt;
&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목= 성능적 build 단계의 hash 충돌이 부하가 큰지? 아니면 probe 단계에서 부하가 큰지?&lt;br /&gt;
# probe 단계에서 압도적으로 크다&lt;br /&gt;
## Probe 단계 -큰 테이블의 모든 row에 대해 반복 수행&lt;br /&gt;
## 각 row마다: hash 계산 → bucket 탐색 → chain 순회 → 값 비교&lt;br /&gt;
## Hash 충돌이 많으면 chain이 길어져서 비교 횟수가 기하급수적 증가&lt;br /&gt;
## 큰 테이블 크기에 비례하여 작업량 증가&lt;br /&gt;
}}&lt;br /&gt;
----&lt;br /&gt;
* 결론&lt;br /&gt;
&lt;br /&gt;
:- 조인 키 중복 문제 : 실무에서 흔하고 심각한 성능 저하 원인&lt;br /&gt;
:- 해시 충돌 문제 : Oracle의 해시 함수가 잘 설계되어 있어 드물게 발생&lt;br /&gt;
:- **실무 팁**: 조인 키는 **카디널리티가 높은 컬럼**(예: ID, 주문번호)을 사용해야 함&lt;br /&gt;
&lt;br /&gt;
성별, 부서코드(10개 정도) 같은 낮은 카디널리티 컬럼으로 Hash Join하면 성능이 매우 나빠집니다!&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
==== JOIN 컬럼 통계정보 ====&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 복합 키 조인 시 통계 확인&lt;br /&gt;
SELECT column_name, num_distinct&lt;br /&gt;
FROM user_tab_col_statistics&lt;br /&gt;
WHERE table_name = &#039;T1&#039;&lt;br /&gt;
  AND column_name IN (&#039;COL1&#039;, &#039;COL2&#039;);&lt;br /&gt;
&lt;br /&gt;
-- 복합 컬럼 통계 (중요!)&lt;br /&gt;
SELECT column_name, num_distinct&lt;br /&gt;
FROM user_tab_col_statistics&lt;br /&gt;
WHERE table_name = &#039;T1&#039;&lt;br /&gt;
  AND column_name = &#039;SYS_NC00003$&#039;;  -- 복합 컬럼 통계&lt;br /&gt;
&lt;br /&gt;
-- 또는 직접 확인&lt;br /&gt;
SELECT COUNT(DISTINCT col1 || &#039;,&#039; || col2) AS composite_cardinality&lt;br /&gt;
FROM t1;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
* 해시 조인이 효율적으로 작동하는 것은 해시 버킷이 잘 분산되어 있고, 해시 테이블이 메모리에서 모두 처리될 때입니다. 만약 데이터가 한쪽으로 치우쳐 해시 충돌이 심하거나, 메모리 부족으로 디스크 I/O가 발생하면 해시 조인의 장점이 사라져 성능이 느려지게 됩니다.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 장/단점 ===&lt;br /&gt;
==== 장점 ====&lt;br /&gt;
# 대용량 테이블간 &amp;quot;=&amp;quot; 조건의 조인에 적합(정렬 불필요,입력 순서 무관)&lt;br /&gt;
# 빠른 접근 가능(버킷을 이용한 빠른 처리)&lt;br /&gt;
# 병렬처리에 적합하면 효율성이 좋음&lt;br /&gt;
&lt;br /&gt;
==== 단점 ==== &lt;br /&gt;
# &amp;quot;=&amp;quot; 조건에만 적합, 범위 조건(between 이나 like) 조인은 사용할수 없음.&lt;br /&gt;
# 메모리에 의존적임(빌드테이블이 메모리내에 들어가지 못하면 템프 i/o가 발생)&lt;br /&gt;
# 조인키값이 한쪽에 치우치거나(skew),중복도가 높으면 특정 버킷에 연결된 버킷이 길어져 탐색하는 시간이 증가함(CPU사용량 급증, 충돌증가)&lt;br /&gt;
# 버킷수가 데이터 대비 부족하면 충돌 발생과 체인 길이가 늘어 탐색 비용이 증가함  &lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1634</id>
		<title>HASH 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1634"/>
		<updated>2025-10-24T02:11:40Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 해시 조인(Hash Join) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해시 조인(Hash Join) ==&lt;br /&gt;
{{틀:서브&lt;br /&gt;
|제목=&#039;&#039;&#039;&amp;lt;big&amp;gt;해시조인시 사용되는 해시(hash)함수는 입력된 데이터를 &#039;잘게 다져서&#039; 고유하고 일정한 크기의 식별자로 만들어 냄.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
::* 해시의 주요특징&lt;br /&gt;
::** 단방향성 : 해시값을 원래의 값으로 되돌릴수 없다.&lt;br /&gt;
::** 고유성 : 입력값이 바뀌면 해시값은 완전히 달라진다.&lt;br /&gt;
::** 고정된길이 : 입력값의 크기와 상관없이 항상 동일한 길이의 출력값을 가진다.&lt;br /&gt;
|내용=* hash의 영문의 뜻 : &#039;&#039;&#039;잘게 썰다, 다지다의 의미(요리에서 재료를 잘게 썰어서 섞을때 쓰이는말, 예로 맥도날도 &#039;해시브라운(감자를 다져서 튀긴요리)&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
# 해시 조인(Hash Join)은 두 개의 테이블을 조인할 때, 더 작은 테이블을 메모리에 올리고 해시 테이블을 만들어 효율적으로 조인하는 방식&lt;br /&gt;
# 대용량 테이블을 조인할 때 주로 사용되며, 조인 조건에 인덱스가 없는 경우에도 빠르게 작동&lt;br /&gt;
&lt;br /&gt;
=== 해시 조인의 원리 ===&lt;br /&gt;
* 해시 조인은 크게 두 단계로 나뉩니다.&lt;br /&gt;
# 빌드 단계 (Build Phase):&lt;br /&gt;
## 옵티마이저는 두 테이블 중 더 작은 테이블(빌드 테이블)을 선택&lt;br /&gt;
## 이 테이블의 조인 키(컬럼)를 사용하여 PGA 메모리 영역에 해시 테이블을 생성. 각 조인 키 값은 해시 함수를 통하여 해시값으로 변환되어 해시 테이블의 특정 위치에 저장.&lt;br /&gt;
## 해시테이블 저장시 변환된 해시값들은 해시버킷의 갯수 만큼 쪼개서 할당되고 &amp;quot;버킷&amp;quot;에 행을 연결리스트(체인) 형태로 저장됨.&lt;br /&gt;
## 각 버킷은 &amp;quot;해당 해시 범위에 속하는 행들의 포인터(체인 헤드)&amp;quot; 역활을 하며 , 동일 해시값 이나 충돌이 발생하면 같은 버킷내에서 체인으로 연결됨.(많이 연결될수록 성능 저하,이구간이 성능저하 구간) &lt;br /&gt;
# 프로브 단계 (Probe Phase):&lt;br /&gt;
## 더 큰 테이블(프로브 테이블)을 순차적으로 읽어옮&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 테이블에서 일치하는 행을 찾음.(대상 버킷을 찾고 해당 버킷의 체인을 스캔하여 조인키를 비교)&lt;br /&gt;
## 일치하는 행을 찾으면, 두 테이블의 행을 결합하여 결과를 반환.&lt;br /&gt;
&lt;br /&gt;
* 만약 빌드 테이블이 PGA 메모리에 다 들어가지 않을 정도로 크다면, 테이블을 여러 개의 파티션으로 나누어 디스크에 임시로 저장한 뒤(성능 저하 구간), 각 파티션별로 빌드와 프로브 단계를 반복하는 &#039;그레이스풀 해시 조인(Graceful Hash Join)&#039;이 수행(성능저하 구간)&lt;br /&gt;
* 런타임 파티셔닝을 수행해 부분(배치/파티션) 단위로 템프에 내린 후(DISK I/O발생,성능저하) 다시 합쳐서 1-pass/멀티패스 방식으로 진행함.&lt;br /&gt;
&lt;br /&gt;
=== 파이썬 예제 ===&lt;br /&gt;
* 두 개의 CSV 파일 sales.csv와 products.csv가 있다고 가정하고, 이를 파이썬 딕셔너리로 해시 조인을 구현하는 예제.&lt;br /&gt;
1. 데이터 준비 (sales.csv, products.csv 파일)&lt;br /&gt;
&lt;br /&gt;
1) 대량 테이블 - 판매(sales.csv 파일)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sale_id,product_id,amount&lt;br /&gt;
1,101,100&lt;br /&gt;
2,102,150&lt;br /&gt;
3,101,200&lt;br /&gt;
4,103,50&lt;br /&gt;
....&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2) 제품 (products.csv)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
product_id,product_name&lt;br /&gt;
101,Laptop&lt;br /&gt;
102,Mouse&lt;br /&gt;
103,Keyboard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 해시 조인 구현 코드&lt;br /&gt;
&lt;br /&gt;
소량인 products 테이블을 빌드 테이블로, sales 테이블을 프로브 테이블로 사용하여 조인을 수행.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import csv&lt;br /&gt;
&lt;br /&gt;
# 1. 빌드 테이블: products.csv를 읽어 메모리 해시 테이블(딕셔너리) 생성&lt;br /&gt;
# &#039;product_id&#039;를 키로, &#039;product_name&#039;을 값으로 저장&lt;br /&gt;
products_hash_table = {}&lt;br /&gt;
with open(&#039;products.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # 해시 테이블 생성 (딕셔너리)&lt;br /&gt;
        products_hash_table[row[&#039;product_id&#039;]] = row[&#039;product_name&#039;]&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;--- 빌드 단계 (해시 테이블) ---&amp;quot;)&lt;br /&gt;
print(products_hash_table)&lt;br /&gt;
# 출력: {&#039;101&#039;: &#039;Laptop&#039;, &#039;102&#039;: &#039;Mouse&#039;, &#039;103&#039;: &#039;Keyboard&#039;}&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;\n--- 프로브 단계 (조인 결과) ---&amp;quot;)&lt;br /&gt;
# 2. 프로브 테이블: sales.csv를 읽어 해시 테이블에서 찾기&lt;br /&gt;
with open(&#039;sales.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # sale_id 3의 &#039;product_id&#039;인 &#039;101&#039;을 해시 테이블에서 찾음&lt;br /&gt;
        product_id = row[&#039;product_id&#039;]&lt;br /&gt;
        if product_id in products_hash_table:&lt;br /&gt;
            # 해시 테이블에서 &#039;product_id&#039;를 키로 검색하여 &#039;product_name&#039;을 가져옴&lt;br /&gt;
            product_name = products_hash_table[product_id]&lt;br /&gt;
            print(f&amp;quot;Sale ID: {row[&#039;sale_id&#039;]}, Product Name: {product_name}, Amount: {row[&#039;amount&#039;]}&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 예상 출력:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Sale ID: 1, Product Name: Laptop, Amount: 100&lt;br /&gt;
# Sale ID: 2, Product Name: Mouse, Amount: 150&lt;br /&gt;
# Sale ID: 3, Product Name: Laptop, Amount: 200&lt;br /&gt;
# Sale ID: 4, Product Name: Keyboard, Amount: 50&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# products_hash_table이라는 딕셔너리가 바로 해시 테이블 역할을 합니다. &lt;br /&gt;
# sales 테이블의 각 행을 읽을 때마다 product_id를 딕셔너리(해시 테이블)에서 빠르게 찾아 product_name을 가져와 조인된 결과를 출력하는 방식&lt;br /&gt;
# 이를 통해 전체 products 테이블을 다시 스캔할 필요 없이 O(1)에 가까운 시간 복잡도로 조인 조건을 만족하는 데이터를 찾을 수 있음.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 성능 향상 원리 ===&lt;br /&gt;
==== Hash 버킷의 역할과 기능 ====&lt;br /&gt;
::::* 해시 버킷은 두 테이블을 효율적으로 조인하기 위한 핵심적인 논리적 저장 공간. &lt;br /&gt;
::::* 이는 조인 키(Join Key)를 기반으로 데이터를 빠르게 찾고 저장하는 데 사용.&lt;br /&gt;
&lt;br /&gt;
===== 역할: 데이터의 논리적 분류 및 저장소 =====&lt;br /&gt;
:::::* 해시 버킷은 조인 키 값을 해시 함수에 넣어 얻은 해시 값이 같은 데이터들을 모아두는 공간.&lt;br /&gt;
:::::* 이는 거대한 데이터를 조인 키를 기준으로 더 작은 그룹으로 나누어 관리하는 역할을 합니다.&lt;br /&gt;
:::::* 이를 통해 빌드 테이블의 모든 데이터를 일일이 탐색하지 않고도, 특정 조인 키에 해당하는 데이터가 어느 버킷에 있는지 바로 알 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
===== 기능: Build 단계와 Probe 단계에서의 활용 =====&lt;br /&gt;
# Build 단계:&lt;br /&gt;
## Oracle은 먼저 두 테이블 중 더 작은 테이블을 선택하여 빌드 테이블로 지정합니다.&lt;br /&gt;
## 빌드 테이블의 각 행을 읽어 조인 키에 해시 함수를 적용하여 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값에 해당하는 버킷에 해당 행의 데이터를 저장합니다. 만약 같은 버킷에 이미 다른 데이터가 있다면, 이를 연결 리스트(Linked List) 형태로 연결하여 **해시 충돌(Hash Collision)**을 처리합니다.&lt;br /&gt;
# Probe 단계:&lt;br /&gt;
## 이제 더 큰 테이블인 프로브 테이블을 순차적으로 읽습니다.&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 버킷 위치를 즉시 찾아갑니다.&lt;br /&gt;
## 해당 버킷 안의 연결 리스트를 탐색하여 조인 키가 일치하는 행을 찾습니다.&lt;br /&gt;
## 일치하는 행이 발견되면 두 테이블의 데이터를 결합하여 최종 결과를 생성합니다.&lt;br /&gt;
&lt;br /&gt;
* 이러한 해시 버킷의 활용 덕분에, 해시 조인은 테이블의 크기와 상관없이 조인 키가 같은 데이터들을 O(1)에 가까운 시간 복잡도로 찾아낼 수 있어 대용량 데이터 조인에 매우 효과적입니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== [[해시 버킷의 저장구조]]====&lt;br /&gt;
&lt;br /&gt;
==== 해시 버킷 개수의 결정 ====&lt;br /&gt;
* 해시 조인의 최대 해시 버킷 개수는 사전 설정된 고정값이 아니라, 조인하려는 테이블의 크기, 옵티마이저의 예측, 그리고 PGA_AGGREGATE_TARGET 파라미터에 의해 동적으로 결정&lt;br /&gt;
&lt;br /&gt;
# 빌드 테이블(Build Table)의 크기: 옵티마이저가 예측한 더 작은 테이블(빌드 테이블)의 크기에 따라 필요한 해시 버킷의 수가 결정됨&lt;br /&gt;
# 할당된 PGA 메모리: PGA_AGGREGATE_TARGET에 의해 세션에 할당된 PGA 메모리 내에서 해시 테이블이 생성됨.&lt;br /&gt;
# 메모리 부족 시: 만약 할당된 PGA 메모리만으로 해시 테이블을 모두 생성할 수 없다면, Oracle은 일부 해시 버킷을 디스크의 임시 테이블스페이스(temp tablespace)로 분할하여 저장합니다. &lt;br /&gt;
## 이를 &#039;해시 스필(Hash Spill)&#039;이라고 부르며, 이 경우 I/O가 발생하여 성능이 저하됨.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인이 느려지는 원인 ====&lt;br /&gt;
* 해시 조인 성능이 느려지는 주요 원인 중 하나는 해시 버킷 때문입니다.&lt;br /&gt;
* 정확히 말하면, 해시 충돌이 심하게 발생하거나 해시 테이블이 메모리에 맞지 않아 디스크 I/O가 발생하는 경우입니다.&lt;br /&gt;
* 해시 버킷 자체는 효율성을 위한 도구지만, 비정상적인 상황에서는 오히려 성능 저하의 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
1. 해시 충돌 (Hash Collisions)&lt;br /&gt;
* 원리: 해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다.&lt;br /&gt;
* 문제점: 해시 충돌이 발생하면, 하나의 버킷에 여러 행이 연결 리스트 형태로 쌓이게 됩니다. 이 버킷을 탐색할 때, 원하는 데이터를 찾기 위해 연결된 모든 데이터를 순차적으로 비교해야 하므로, 탐색 시간이 O(1)에서 O(N)으로 늘어납니다.&lt;br /&gt;
* 결과: 조인 키 데이터 분포가 불균형하거나(데이터 편중), 해시 함수가 비효율적이면 해시 충돌이 자주 발생하여 성능이 크게 저하됩니다.&lt;br /&gt;
* (결론) 해시조인은 조인키로 사용되는 컬럼이 중복이 많을수록  성능이 좋지 않음&lt;br /&gt;
&lt;br /&gt;
* 파이썬으로 해시 충돌 구현 예제&lt;br /&gt;
** 고의로 해시 충돌을 유발하는 데이터를 사용하여 해시 조인의 성능 차이를 보여줍니다. &lt;br /&gt;
** products_collision 리스트의 모든 product_id는 해시 함수를 거치면 같은 값을 반환하도록 설계되었습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터 (해시 충돌이 거의 없음)&lt;br /&gt;
products_normal = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 102, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 103, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 의도적으로 해시 충돌을 유발하는 데이터&lt;br /&gt;
# 101, 201, 301 모두 100으로 나눈 나머지가 1이 되도록 설계&lt;br /&gt;
products_collision = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 201, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 301, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인 대상 데이터 (각 product_id에 대한 sales 데이터)&lt;br /&gt;
sales = [{&#039;sale_id&#039;: i, &#039;product_id&#039;: 101, &#039;amount&#039;: 100} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+1000, &#039;product_id&#039;: 201, &#039;amount&#039;: 150} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+2000, &#039;product_id&#039;: 301, &#039;amount&#039;: 200} for i in range(1000)]&lt;br /&gt;
&lt;br /&gt;
# 사용자 정의 해시 함수 (단순화를 위해)&lt;br /&gt;
def custom_hash(key):&lt;br /&gt;
    return key % 100&lt;br /&gt;
&lt;br /&gt;
def hash_join(build_table, probe_table):&lt;br /&gt;
    # 빌드 단계: 해시 버킷(딕셔너리) 생성&lt;br /&gt;
    # 파이썬 딕셔너리의 키 충돌을 구현하기 위해, 딕셔너리의 값을 리스트로 저장&lt;br /&gt;
    hash_buckets = {}&lt;br /&gt;
    for item in build_table:&lt;br /&gt;
        key = item[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        if bucket_index not in hash_buckets:&lt;br /&gt;
            hash_buckets[bucket_index] = []&lt;br /&gt;
        hash_buckets[bucket_index].append(item)&lt;br /&gt;
&lt;br /&gt;
    # 프로브 단계: 조인&lt;br /&gt;
    start_time = time.time()&lt;br /&gt;
    joined_results = []&lt;br /&gt;
    for sale in probe_table:&lt;br /&gt;
        key = sale[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        &lt;br /&gt;
        # 해시 버킷에서 일치하는 데이터를 찾기 (충돌 발생 시 순차 탐색)&lt;br /&gt;
        if bucket_index in hash_buckets:&lt;br /&gt;
            for product_info in hash_buckets[bucket_index]:&lt;br /&gt;
                if product_info[&#039;product_id&#039;] == key:&lt;br /&gt;
                    joined_results.append({**sale, **product_info}) # **은 언패킹연산자 {**A,**B} 는 A,B딕셔너리를 합쳐줌.&lt;br /&gt;
                    break&lt;br /&gt;
    end_time = time.time()&lt;br /&gt;
    return end_time - start_time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터로 조인&lt;br /&gt;
normal_time = hash_join(products_normal, sales)&lt;br /&gt;
&lt;br /&gt;
# 해시 충돌 데이터로 조인&lt;br /&gt;
collision_time = hash_join(products_collision, sales)&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;일반 데이터 조인 시간: {normal_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;해시 충돌 조인 시간: {collision_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;성능 저하 비율: {(collision_time / normal_time):.2f}배 느림&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
일반 데이터 조인 시간: 0.000971 초&lt;br /&gt;
해시 충돌 조인 시간: 0.001568 초&lt;br /&gt;
성능 저하 비율: 1.61배 느림&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 메모리 부족 및 디스크 I/O&lt;br /&gt;
* 원리: 해시 조인은 더 작은 테이블(빌드 테이블)을 메모리에 올려 해시 테이블을 만드는 것이 기본 전제입니다.&lt;br /&gt;
* 문제점: 빌드 테이블이 너무 커서 **메모리(PGA)**에 다 담을 수 없게 되면, Oracle은 해시 테이블을 여러 파티션으로 나누어 일부를 **임시 테이블스페이스(Temporary Tablespace)**에 저장합니다.&lt;br /&gt;
* 결과: 조인을 수행하기 위해 디스크에 기록된 파티션 데이터를 읽고 쓰는 디스크 I/O 작업이 반복적으로 발생합니다. 이는 메모리에서만 작업할 때보다 훨씬 느리므로 성능 저하의 결정적인 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인에 대한 의문점 ====&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목=&lt;br /&gt;
* hash join에서  해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다. 또한 조인키로 사용되는 컬럼이 중복이 많을수록 성능이 좋지 않습니다.  &lt;br /&gt;
|내용= *&lt;br /&gt;
# 그러면 조인키로 사용되는 &#039;&#039;&#039;컬럼의 값&#039;&#039;&#039; 이 중복이 심하면 성능 않좋아 진다는것인가? &lt;br /&gt;
# 아니면 해시함수를 통하여 생성된 해시값이 동일한경우에 성능이 않좋아 진다는 것인가? &lt;br /&gt;
# 둘다 해당한다는것인가?&lt;br /&gt;
}}&lt;br /&gt;
* 둘 다 성능에 영향을 미치지만, 서로 다른 이유이며 실무에서는 1번이 훨씬 더 큰 문제입니다.&lt;br /&gt;
&lt;br /&gt;
===== 조인 키 값의 중복 (더 흔한 문제) =====&lt;br /&gt;
* 조인 키 컬럼 자체의 중복이 많으면 성능이 나빠집니다.&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시: 성별 컬럼으로 조인 (값이 &#039;M&#039;, &#039;F&#039; 두 개뿐)&lt;br /&gt;
SELECT /*+ USE_HASH(a b) */ *&lt;br /&gt;
FROM employees a&lt;br /&gt;
JOIN customers b ON a.gender = b.gender&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- `gender = &#039;M&#039;` 인 행들이 모두 &#039;&#039;&#039;같은 해시 버킷&#039;&#039;&#039;에 저장됨&lt;br /&gt;
:- Probe 단계에서 해당 버킷의 수많은 행들을 일일이 비교해야 함&lt;br /&gt;
:- 해시 조인의 장점(O(1) 검색)이 사라짐&lt;br /&gt;
&lt;br /&gt;
===== 해시 충돌 (드문 문제) =====&lt;br /&gt;
* 서로 다른 조인 키 값이 우연히 같은 해시 값을 생성하는 경우입니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 예시 (실제는 아니지만 개념 설명)&lt;br /&gt;
hash(&#039;emp_id_12345&#039;) = 789  # 해시값 789&lt;br /&gt;
hash(&#039;emp_id_99999&#039;) = 789  # 다른 키인데 같은 해시값!&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- 서로 다른 키인데 같은 버킷에 저장됨&lt;br /&gt;
:- 역시 Probe 시 추가 비교 필요&lt;br /&gt;
&lt;br /&gt;
===== 실제 상황 비교 =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 A: 카디널리티 낮음 (중복 많음) - 실제 큰 문제&lt;br /&gt;
employees: 1,000,000명&lt;br /&gt;
성별(gender)컬럼 값: 남자(&#039;M&#039;), 여자(&#039;F&#039;) 두 개만&lt;br /&gt;
→ 각 버킷에 약 500,000개 행 저장&lt;br /&gt;
→ 성능 매우 나쁨&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 B: 카디널리티 높음 (중복 적음) - 좋음&lt;br /&gt;
employees: 1,000,000명  &lt;br /&gt;
employee_id: 1,000,000개 유니크 값&lt;br /&gt;
→ 각 버킷에 평균 1~2개 행&lt;br /&gt;
→ 해시 충돌 있어도 영향 미미&lt;br /&gt;
→ 성능 좋음&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목=해시충돌은 언제 발생되는가? build 단계인가? probe 단계인가? 아니면 둘다 인가?&lt;br /&gt;
&lt;br /&gt;
|내용=&lt;br /&gt;
Oracle의 Hash Join에서 hash 충돌은 build 단계에서 발생 합니다.&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Hash Join의 동작 과정&lt;br /&gt;
&lt;br /&gt;
1. Build 단계 (작은 테이블)&lt;br /&gt;
:: - 작은 테이블(build input)을 읽어서 메모리(PGA)에 hash table 생성&lt;br /&gt;
:: - 각 row의 join key에 hash function을 적용하여 hash bucket에 저장&lt;br /&gt;
:: - 이때 서로 다른 key 값이 같은 hash 값을 가지면 hash 충돌 발생&lt;br /&gt;
:: - 충돌 발생 시 chaining 방식으로 같은 bucket에 여러 row를 연결&lt;br /&gt;
&lt;br /&gt;
2. Probe 단계 (큰 테이블)&lt;br /&gt;
&lt;br /&gt;
:: - 큰 테이블(probe input)을 읽으면서 각 row의 join key를 hash&lt;br /&gt;
:: - 해당 hash bucket을 찾아가서 실제 값을 비교 (equality check)&lt;br /&gt;
:: - 충돌이 발생했던 bucket의 경우 chain을 따라가며 모든 값과 비교&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
:::: Hash 충돌은 build 단계에서 hash table을 구성할 때 발생하며, probe 단계에서는 이미 발생한 충돌을 처리(chain 탐색)하는 것입니다.&lt;br /&gt;
:::: 충돌이 많아지면 probe 단계에서 chain을 따라가며 비교해야 할 row가 많아져 성능이 저하될 수 있습니다. &lt;br /&gt;
:::: Oracle은 이를 위해 적절한 hash bucket 수를 자동으로 조정합니다.​​​​​​​​​​​​​​​​&lt;br /&gt;
&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목= 성능적 build 단계의 hash 충돌이 부하가 큰지? 아니면 probe 단계에서 부하가 큰지?&lt;br /&gt;
# probe 단계에서 압도적으로 크다&lt;br /&gt;
## Probe 단계 -큰 테이블의 모든 row에 대해 반복 수행&lt;br /&gt;
## 각 row마다: hash 계산 → bucket 탐색 → chain 순회 → 값 비교&lt;br /&gt;
## Hash 충돌이 많으면 chain이 길어져서 비교 횟수가 기하급수적 증가&lt;br /&gt;
## 큰 테이블 크기에 비례하여 작업량 증가&lt;br /&gt;
}}&lt;br /&gt;
----&lt;br /&gt;
* 결론&lt;br /&gt;
&lt;br /&gt;
:- 조인 키 중복 문제 : 실무에서 흔하고 심각한 성능 저하 원인&lt;br /&gt;
:- 해시 충돌 문제 : Oracle의 해시 함수가 잘 설계되어 있어 드물게 발생&lt;br /&gt;
:- **실무 팁**: 조인 키는 **카디널리티가 높은 컬럼**(예: ID, 주문번호)을 사용해야 함&lt;br /&gt;
&lt;br /&gt;
성별, 부서코드(10개 정도) 같은 낮은 카디널리티 컬럼으로 Hash Join하면 성능이 매우 나빠집니다!&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
==== JOIN 컬럼 통계정보 ====&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 복합 키 조인 시 통계 확인&lt;br /&gt;
SELECT column_name, num_distinct&lt;br /&gt;
FROM user_tab_col_statistics&lt;br /&gt;
WHERE table_name = &#039;T1&#039;&lt;br /&gt;
  AND column_name IN (&#039;COL1&#039;, &#039;COL2&#039;);&lt;br /&gt;
&lt;br /&gt;
-- 복합 컬럼 통계 (중요!)&lt;br /&gt;
SELECT column_name, num_distinct&lt;br /&gt;
FROM user_tab_col_statistics&lt;br /&gt;
WHERE table_name = &#039;T1&#039;&lt;br /&gt;
  AND column_name = &#039;SYS_NC00003$&#039;;  -- 복합 컬럼 통계&lt;br /&gt;
&lt;br /&gt;
-- 또는 직접 확인&lt;br /&gt;
SELECT COUNT(DISTINCT col1 || &#039;,&#039; || col2) AS composite_cardinality&lt;br /&gt;
FROM t1;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
* 해시 조인이 효율적으로 작동하는 것은 해시 버킷이 잘 분산되어 있고, 해시 테이블이 메모리에서 모두 처리될 때입니다. 만약 데이터가 한쪽으로 치우쳐 해시 충돌이 심하거나, 메모리 부족으로 디스크 I/O가 발생하면 해시 조인의 장점이 사라져 성능이 느려지게 됩니다.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 장/단점 ===&lt;br /&gt;
==== 장점 ====&lt;br /&gt;
# 대용량 테이블간 &amp;quot;=&amp;quot; 조건의 조인에 적합(정렬 불필요,입력 순서 무관)&lt;br /&gt;
# 빠른 접근 가능(버킷을 이용한 빠른 처리)&lt;br /&gt;
# 병렬처리에 적합하면 효율성이 좋음&lt;br /&gt;
&lt;br /&gt;
==== 단점 ==== &lt;br /&gt;
# &amp;quot;=&amp;quot; 조건에만 적합, 범위 조건(between 이나 like) 조인은 사용할수 없음.&lt;br /&gt;
# 메모리에 의존적임(빌드테이블이 메모리내에 들어가지 못하면 템프 i/o가 발생)&lt;br /&gt;
# 조인키값이 한쪽에 치우치거나(skew),중복도가 높으면 특정 버킷에 연결된 버킷이 길어져 탐색하는 시간이 증가함(CPU사용량 급증, 충돌증가)&lt;br /&gt;
# 버킷수가 데이터 대비 부족하면 충돌 발생과 체인 길이가 늘어 탐색 비용이 증가함  &lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1633</id>
		<title>HASH 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1633"/>
		<updated>2025-10-24T02:11:28Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 해시 조인(Hash Join) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해시 조인(Hash Join) ==&lt;br /&gt;
{{틀:서브&lt;br /&gt;
|제목=&#039;&#039;&#039;&amp;lt;big&amp;gt;해시조인시 사용되는 해시(hash)함수는 입력된 데이터를 &#039;잘게 다져서&#039; 고유하고 일정한 크기의 식별자로 만들어 냄.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
:::* 해시의 주요특징&lt;br /&gt;
:::** 단방향성 : 해시값을 원래의 값으로 되돌릴수 없다.&lt;br /&gt;
:::** 고유성 : 입력값이 바뀌면 해시값은 완전히 달라진다.&lt;br /&gt;
:::** 고정된길이 : 입력값의 크기와 상관없이 항상 동일한 길이의 출력값을 가진다.&lt;br /&gt;
|내용=* hash의 영문의 뜻 : &#039;&#039;&#039;잘게 썰다, 다지다의 의미(요리에서 재료를 잘게 썰어서 섞을때 쓰이는말, 예로 맥도날도 &#039;해시브라운(감자를 다져서 튀긴요리)&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
# 해시 조인(Hash Join)은 두 개의 테이블을 조인할 때, 더 작은 테이블을 메모리에 올리고 해시 테이블을 만들어 효율적으로 조인하는 방식&lt;br /&gt;
# 대용량 테이블을 조인할 때 주로 사용되며, 조인 조건에 인덱스가 없는 경우에도 빠르게 작동&lt;br /&gt;
&lt;br /&gt;
=== 해시 조인의 원리 ===&lt;br /&gt;
* 해시 조인은 크게 두 단계로 나뉩니다.&lt;br /&gt;
# 빌드 단계 (Build Phase):&lt;br /&gt;
## 옵티마이저는 두 테이블 중 더 작은 테이블(빌드 테이블)을 선택&lt;br /&gt;
## 이 테이블의 조인 키(컬럼)를 사용하여 PGA 메모리 영역에 해시 테이블을 생성. 각 조인 키 값은 해시 함수를 통하여 해시값으로 변환되어 해시 테이블의 특정 위치에 저장.&lt;br /&gt;
## 해시테이블 저장시 변환된 해시값들은 해시버킷의 갯수 만큼 쪼개서 할당되고 &amp;quot;버킷&amp;quot;에 행을 연결리스트(체인) 형태로 저장됨.&lt;br /&gt;
## 각 버킷은 &amp;quot;해당 해시 범위에 속하는 행들의 포인터(체인 헤드)&amp;quot; 역활을 하며 , 동일 해시값 이나 충돌이 발생하면 같은 버킷내에서 체인으로 연결됨.(많이 연결될수록 성능 저하,이구간이 성능저하 구간) &lt;br /&gt;
# 프로브 단계 (Probe Phase):&lt;br /&gt;
## 더 큰 테이블(프로브 테이블)을 순차적으로 읽어옮&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 테이블에서 일치하는 행을 찾음.(대상 버킷을 찾고 해당 버킷의 체인을 스캔하여 조인키를 비교)&lt;br /&gt;
## 일치하는 행을 찾으면, 두 테이블의 행을 결합하여 결과를 반환.&lt;br /&gt;
&lt;br /&gt;
* 만약 빌드 테이블이 PGA 메모리에 다 들어가지 않을 정도로 크다면, 테이블을 여러 개의 파티션으로 나누어 디스크에 임시로 저장한 뒤(성능 저하 구간), 각 파티션별로 빌드와 프로브 단계를 반복하는 &#039;그레이스풀 해시 조인(Graceful Hash Join)&#039;이 수행(성능저하 구간)&lt;br /&gt;
* 런타임 파티셔닝을 수행해 부분(배치/파티션) 단위로 템프에 내린 후(DISK I/O발생,성능저하) 다시 합쳐서 1-pass/멀티패스 방식으로 진행함.&lt;br /&gt;
&lt;br /&gt;
=== 파이썬 예제 ===&lt;br /&gt;
* 두 개의 CSV 파일 sales.csv와 products.csv가 있다고 가정하고, 이를 파이썬 딕셔너리로 해시 조인을 구현하는 예제.&lt;br /&gt;
1. 데이터 준비 (sales.csv, products.csv 파일)&lt;br /&gt;
&lt;br /&gt;
1) 대량 테이블 - 판매(sales.csv 파일)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sale_id,product_id,amount&lt;br /&gt;
1,101,100&lt;br /&gt;
2,102,150&lt;br /&gt;
3,101,200&lt;br /&gt;
4,103,50&lt;br /&gt;
....&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2) 제품 (products.csv)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
product_id,product_name&lt;br /&gt;
101,Laptop&lt;br /&gt;
102,Mouse&lt;br /&gt;
103,Keyboard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 해시 조인 구현 코드&lt;br /&gt;
&lt;br /&gt;
소량인 products 테이블을 빌드 테이블로, sales 테이블을 프로브 테이블로 사용하여 조인을 수행.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import csv&lt;br /&gt;
&lt;br /&gt;
# 1. 빌드 테이블: products.csv를 읽어 메모리 해시 테이블(딕셔너리) 생성&lt;br /&gt;
# &#039;product_id&#039;를 키로, &#039;product_name&#039;을 값으로 저장&lt;br /&gt;
products_hash_table = {}&lt;br /&gt;
with open(&#039;products.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # 해시 테이블 생성 (딕셔너리)&lt;br /&gt;
        products_hash_table[row[&#039;product_id&#039;]] = row[&#039;product_name&#039;]&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;--- 빌드 단계 (해시 테이블) ---&amp;quot;)&lt;br /&gt;
print(products_hash_table)&lt;br /&gt;
# 출력: {&#039;101&#039;: &#039;Laptop&#039;, &#039;102&#039;: &#039;Mouse&#039;, &#039;103&#039;: &#039;Keyboard&#039;}&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;\n--- 프로브 단계 (조인 결과) ---&amp;quot;)&lt;br /&gt;
# 2. 프로브 테이블: sales.csv를 읽어 해시 테이블에서 찾기&lt;br /&gt;
with open(&#039;sales.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # sale_id 3의 &#039;product_id&#039;인 &#039;101&#039;을 해시 테이블에서 찾음&lt;br /&gt;
        product_id = row[&#039;product_id&#039;]&lt;br /&gt;
        if product_id in products_hash_table:&lt;br /&gt;
            # 해시 테이블에서 &#039;product_id&#039;를 키로 검색하여 &#039;product_name&#039;을 가져옴&lt;br /&gt;
            product_name = products_hash_table[product_id]&lt;br /&gt;
            print(f&amp;quot;Sale ID: {row[&#039;sale_id&#039;]}, Product Name: {product_name}, Amount: {row[&#039;amount&#039;]}&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 예상 출력:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Sale ID: 1, Product Name: Laptop, Amount: 100&lt;br /&gt;
# Sale ID: 2, Product Name: Mouse, Amount: 150&lt;br /&gt;
# Sale ID: 3, Product Name: Laptop, Amount: 200&lt;br /&gt;
# Sale ID: 4, Product Name: Keyboard, Amount: 50&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# products_hash_table이라는 딕셔너리가 바로 해시 테이블 역할을 합니다. &lt;br /&gt;
# sales 테이블의 각 행을 읽을 때마다 product_id를 딕셔너리(해시 테이블)에서 빠르게 찾아 product_name을 가져와 조인된 결과를 출력하는 방식&lt;br /&gt;
# 이를 통해 전체 products 테이블을 다시 스캔할 필요 없이 O(1)에 가까운 시간 복잡도로 조인 조건을 만족하는 데이터를 찾을 수 있음.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 성능 향상 원리 ===&lt;br /&gt;
==== Hash 버킷의 역할과 기능 ====&lt;br /&gt;
::::* 해시 버킷은 두 테이블을 효율적으로 조인하기 위한 핵심적인 논리적 저장 공간. &lt;br /&gt;
::::* 이는 조인 키(Join Key)를 기반으로 데이터를 빠르게 찾고 저장하는 데 사용.&lt;br /&gt;
&lt;br /&gt;
===== 역할: 데이터의 논리적 분류 및 저장소 =====&lt;br /&gt;
:::::* 해시 버킷은 조인 키 값을 해시 함수에 넣어 얻은 해시 값이 같은 데이터들을 모아두는 공간.&lt;br /&gt;
:::::* 이는 거대한 데이터를 조인 키를 기준으로 더 작은 그룹으로 나누어 관리하는 역할을 합니다.&lt;br /&gt;
:::::* 이를 통해 빌드 테이블의 모든 데이터를 일일이 탐색하지 않고도, 특정 조인 키에 해당하는 데이터가 어느 버킷에 있는지 바로 알 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
===== 기능: Build 단계와 Probe 단계에서의 활용 =====&lt;br /&gt;
# Build 단계:&lt;br /&gt;
## Oracle은 먼저 두 테이블 중 더 작은 테이블을 선택하여 빌드 테이블로 지정합니다.&lt;br /&gt;
## 빌드 테이블의 각 행을 읽어 조인 키에 해시 함수를 적용하여 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값에 해당하는 버킷에 해당 행의 데이터를 저장합니다. 만약 같은 버킷에 이미 다른 데이터가 있다면, 이를 연결 리스트(Linked List) 형태로 연결하여 **해시 충돌(Hash Collision)**을 처리합니다.&lt;br /&gt;
# Probe 단계:&lt;br /&gt;
## 이제 더 큰 테이블인 프로브 테이블을 순차적으로 읽습니다.&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 버킷 위치를 즉시 찾아갑니다.&lt;br /&gt;
## 해당 버킷 안의 연결 리스트를 탐색하여 조인 키가 일치하는 행을 찾습니다.&lt;br /&gt;
## 일치하는 행이 발견되면 두 테이블의 데이터를 결합하여 최종 결과를 생성합니다.&lt;br /&gt;
&lt;br /&gt;
* 이러한 해시 버킷의 활용 덕분에, 해시 조인은 테이블의 크기와 상관없이 조인 키가 같은 데이터들을 O(1)에 가까운 시간 복잡도로 찾아낼 수 있어 대용량 데이터 조인에 매우 효과적입니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== [[해시 버킷의 저장구조]]====&lt;br /&gt;
&lt;br /&gt;
==== 해시 버킷 개수의 결정 ====&lt;br /&gt;
* 해시 조인의 최대 해시 버킷 개수는 사전 설정된 고정값이 아니라, 조인하려는 테이블의 크기, 옵티마이저의 예측, 그리고 PGA_AGGREGATE_TARGET 파라미터에 의해 동적으로 결정&lt;br /&gt;
&lt;br /&gt;
# 빌드 테이블(Build Table)의 크기: 옵티마이저가 예측한 더 작은 테이블(빌드 테이블)의 크기에 따라 필요한 해시 버킷의 수가 결정됨&lt;br /&gt;
# 할당된 PGA 메모리: PGA_AGGREGATE_TARGET에 의해 세션에 할당된 PGA 메모리 내에서 해시 테이블이 생성됨.&lt;br /&gt;
# 메모리 부족 시: 만약 할당된 PGA 메모리만으로 해시 테이블을 모두 생성할 수 없다면, Oracle은 일부 해시 버킷을 디스크의 임시 테이블스페이스(temp tablespace)로 분할하여 저장합니다. &lt;br /&gt;
## 이를 &#039;해시 스필(Hash Spill)&#039;이라고 부르며, 이 경우 I/O가 발생하여 성능이 저하됨.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인이 느려지는 원인 ====&lt;br /&gt;
* 해시 조인 성능이 느려지는 주요 원인 중 하나는 해시 버킷 때문입니다.&lt;br /&gt;
* 정확히 말하면, 해시 충돌이 심하게 발생하거나 해시 테이블이 메모리에 맞지 않아 디스크 I/O가 발생하는 경우입니다.&lt;br /&gt;
* 해시 버킷 자체는 효율성을 위한 도구지만, 비정상적인 상황에서는 오히려 성능 저하의 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
1. 해시 충돌 (Hash Collisions)&lt;br /&gt;
* 원리: 해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다.&lt;br /&gt;
* 문제점: 해시 충돌이 발생하면, 하나의 버킷에 여러 행이 연결 리스트 형태로 쌓이게 됩니다. 이 버킷을 탐색할 때, 원하는 데이터를 찾기 위해 연결된 모든 데이터를 순차적으로 비교해야 하므로, 탐색 시간이 O(1)에서 O(N)으로 늘어납니다.&lt;br /&gt;
* 결과: 조인 키 데이터 분포가 불균형하거나(데이터 편중), 해시 함수가 비효율적이면 해시 충돌이 자주 발생하여 성능이 크게 저하됩니다.&lt;br /&gt;
* (결론) 해시조인은 조인키로 사용되는 컬럼이 중복이 많을수록  성능이 좋지 않음&lt;br /&gt;
&lt;br /&gt;
* 파이썬으로 해시 충돌 구현 예제&lt;br /&gt;
** 고의로 해시 충돌을 유발하는 데이터를 사용하여 해시 조인의 성능 차이를 보여줍니다. &lt;br /&gt;
** products_collision 리스트의 모든 product_id는 해시 함수를 거치면 같은 값을 반환하도록 설계되었습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터 (해시 충돌이 거의 없음)&lt;br /&gt;
products_normal = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 102, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 103, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 의도적으로 해시 충돌을 유발하는 데이터&lt;br /&gt;
# 101, 201, 301 모두 100으로 나눈 나머지가 1이 되도록 설계&lt;br /&gt;
products_collision = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 201, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 301, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인 대상 데이터 (각 product_id에 대한 sales 데이터)&lt;br /&gt;
sales = [{&#039;sale_id&#039;: i, &#039;product_id&#039;: 101, &#039;amount&#039;: 100} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+1000, &#039;product_id&#039;: 201, &#039;amount&#039;: 150} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+2000, &#039;product_id&#039;: 301, &#039;amount&#039;: 200} for i in range(1000)]&lt;br /&gt;
&lt;br /&gt;
# 사용자 정의 해시 함수 (단순화를 위해)&lt;br /&gt;
def custom_hash(key):&lt;br /&gt;
    return key % 100&lt;br /&gt;
&lt;br /&gt;
def hash_join(build_table, probe_table):&lt;br /&gt;
    # 빌드 단계: 해시 버킷(딕셔너리) 생성&lt;br /&gt;
    # 파이썬 딕셔너리의 키 충돌을 구현하기 위해, 딕셔너리의 값을 리스트로 저장&lt;br /&gt;
    hash_buckets = {}&lt;br /&gt;
    for item in build_table:&lt;br /&gt;
        key = item[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        if bucket_index not in hash_buckets:&lt;br /&gt;
            hash_buckets[bucket_index] = []&lt;br /&gt;
        hash_buckets[bucket_index].append(item)&lt;br /&gt;
&lt;br /&gt;
    # 프로브 단계: 조인&lt;br /&gt;
    start_time = time.time()&lt;br /&gt;
    joined_results = []&lt;br /&gt;
    for sale in probe_table:&lt;br /&gt;
        key = sale[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        &lt;br /&gt;
        # 해시 버킷에서 일치하는 데이터를 찾기 (충돌 발생 시 순차 탐색)&lt;br /&gt;
        if bucket_index in hash_buckets:&lt;br /&gt;
            for product_info in hash_buckets[bucket_index]:&lt;br /&gt;
                if product_info[&#039;product_id&#039;] == key:&lt;br /&gt;
                    joined_results.append({**sale, **product_info}) # **은 언패킹연산자 {**A,**B} 는 A,B딕셔너리를 합쳐줌.&lt;br /&gt;
                    break&lt;br /&gt;
    end_time = time.time()&lt;br /&gt;
    return end_time - start_time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터로 조인&lt;br /&gt;
normal_time = hash_join(products_normal, sales)&lt;br /&gt;
&lt;br /&gt;
# 해시 충돌 데이터로 조인&lt;br /&gt;
collision_time = hash_join(products_collision, sales)&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;일반 데이터 조인 시간: {normal_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;해시 충돌 조인 시간: {collision_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;성능 저하 비율: {(collision_time / normal_time):.2f}배 느림&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
일반 데이터 조인 시간: 0.000971 초&lt;br /&gt;
해시 충돌 조인 시간: 0.001568 초&lt;br /&gt;
성능 저하 비율: 1.61배 느림&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 메모리 부족 및 디스크 I/O&lt;br /&gt;
* 원리: 해시 조인은 더 작은 테이블(빌드 테이블)을 메모리에 올려 해시 테이블을 만드는 것이 기본 전제입니다.&lt;br /&gt;
* 문제점: 빌드 테이블이 너무 커서 **메모리(PGA)**에 다 담을 수 없게 되면, Oracle은 해시 테이블을 여러 파티션으로 나누어 일부를 **임시 테이블스페이스(Temporary Tablespace)**에 저장합니다.&lt;br /&gt;
* 결과: 조인을 수행하기 위해 디스크에 기록된 파티션 데이터를 읽고 쓰는 디스크 I/O 작업이 반복적으로 발생합니다. 이는 메모리에서만 작업할 때보다 훨씬 느리므로 성능 저하의 결정적인 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인에 대한 의문점 ====&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목=&lt;br /&gt;
* hash join에서  해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다. 또한 조인키로 사용되는 컬럼이 중복이 많을수록 성능이 좋지 않습니다.  &lt;br /&gt;
|내용= *&lt;br /&gt;
# 그러면 조인키로 사용되는 &#039;&#039;&#039;컬럼의 값&#039;&#039;&#039; 이 중복이 심하면 성능 않좋아 진다는것인가? &lt;br /&gt;
# 아니면 해시함수를 통하여 생성된 해시값이 동일한경우에 성능이 않좋아 진다는 것인가? &lt;br /&gt;
# 둘다 해당한다는것인가?&lt;br /&gt;
}}&lt;br /&gt;
* 둘 다 성능에 영향을 미치지만, 서로 다른 이유이며 실무에서는 1번이 훨씬 더 큰 문제입니다.&lt;br /&gt;
&lt;br /&gt;
===== 조인 키 값의 중복 (더 흔한 문제) =====&lt;br /&gt;
* 조인 키 컬럼 자체의 중복이 많으면 성능이 나빠집니다.&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시: 성별 컬럼으로 조인 (값이 &#039;M&#039;, &#039;F&#039; 두 개뿐)&lt;br /&gt;
SELECT /*+ USE_HASH(a b) */ *&lt;br /&gt;
FROM employees a&lt;br /&gt;
JOIN customers b ON a.gender = b.gender&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- `gender = &#039;M&#039;` 인 행들이 모두 &#039;&#039;&#039;같은 해시 버킷&#039;&#039;&#039;에 저장됨&lt;br /&gt;
:- Probe 단계에서 해당 버킷의 수많은 행들을 일일이 비교해야 함&lt;br /&gt;
:- 해시 조인의 장점(O(1) 검색)이 사라짐&lt;br /&gt;
&lt;br /&gt;
===== 해시 충돌 (드문 문제) =====&lt;br /&gt;
* 서로 다른 조인 키 값이 우연히 같은 해시 값을 생성하는 경우입니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 예시 (실제는 아니지만 개념 설명)&lt;br /&gt;
hash(&#039;emp_id_12345&#039;) = 789  # 해시값 789&lt;br /&gt;
hash(&#039;emp_id_99999&#039;) = 789  # 다른 키인데 같은 해시값!&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- 서로 다른 키인데 같은 버킷에 저장됨&lt;br /&gt;
:- 역시 Probe 시 추가 비교 필요&lt;br /&gt;
&lt;br /&gt;
===== 실제 상황 비교 =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 A: 카디널리티 낮음 (중복 많음) - 실제 큰 문제&lt;br /&gt;
employees: 1,000,000명&lt;br /&gt;
성별(gender)컬럼 값: 남자(&#039;M&#039;), 여자(&#039;F&#039;) 두 개만&lt;br /&gt;
→ 각 버킷에 약 500,000개 행 저장&lt;br /&gt;
→ 성능 매우 나쁨&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 B: 카디널리티 높음 (중복 적음) - 좋음&lt;br /&gt;
employees: 1,000,000명  &lt;br /&gt;
employee_id: 1,000,000개 유니크 값&lt;br /&gt;
→ 각 버킷에 평균 1~2개 행&lt;br /&gt;
→ 해시 충돌 있어도 영향 미미&lt;br /&gt;
→ 성능 좋음&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목=해시충돌은 언제 발생되는가? build 단계인가? probe 단계인가? 아니면 둘다 인가?&lt;br /&gt;
&lt;br /&gt;
|내용=&lt;br /&gt;
Oracle의 Hash Join에서 hash 충돌은 build 단계에서 발생 합니다.&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Hash Join의 동작 과정&lt;br /&gt;
&lt;br /&gt;
1. Build 단계 (작은 테이블)&lt;br /&gt;
:: - 작은 테이블(build input)을 읽어서 메모리(PGA)에 hash table 생성&lt;br /&gt;
:: - 각 row의 join key에 hash function을 적용하여 hash bucket에 저장&lt;br /&gt;
:: - 이때 서로 다른 key 값이 같은 hash 값을 가지면 hash 충돌 발생&lt;br /&gt;
:: - 충돌 발생 시 chaining 방식으로 같은 bucket에 여러 row를 연결&lt;br /&gt;
&lt;br /&gt;
2. Probe 단계 (큰 테이블)&lt;br /&gt;
&lt;br /&gt;
:: - 큰 테이블(probe input)을 읽으면서 각 row의 join key를 hash&lt;br /&gt;
:: - 해당 hash bucket을 찾아가서 실제 값을 비교 (equality check)&lt;br /&gt;
:: - 충돌이 발생했던 bucket의 경우 chain을 따라가며 모든 값과 비교&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
:::: Hash 충돌은 build 단계에서 hash table을 구성할 때 발생하며, probe 단계에서는 이미 발생한 충돌을 처리(chain 탐색)하는 것입니다.&lt;br /&gt;
:::: 충돌이 많아지면 probe 단계에서 chain을 따라가며 비교해야 할 row가 많아져 성능이 저하될 수 있습니다. &lt;br /&gt;
:::: Oracle은 이를 위해 적절한 hash bucket 수를 자동으로 조정합니다.​​​​​​​​​​​​​​​​&lt;br /&gt;
&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목= 성능적 build 단계의 hash 충돌이 부하가 큰지? 아니면 probe 단계에서 부하가 큰지?&lt;br /&gt;
# probe 단계에서 압도적으로 크다&lt;br /&gt;
## Probe 단계 -큰 테이블의 모든 row에 대해 반복 수행&lt;br /&gt;
## 각 row마다: hash 계산 → bucket 탐색 → chain 순회 → 값 비교&lt;br /&gt;
## Hash 충돌이 많으면 chain이 길어져서 비교 횟수가 기하급수적 증가&lt;br /&gt;
## 큰 테이블 크기에 비례하여 작업량 증가&lt;br /&gt;
}}&lt;br /&gt;
----&lt;br /&gt;
* 결론&lt;br /&gt;
&lt;br /&gt;
:- 조인 키 중복 문제 : 실무에서 흔하고 심각한 성능 저하 원인&lt;br /&gt;
:- 해시 충돌 문제 : Oracle의 해시 함수가 잘 설계되어 있어 드물게 발생&lt;br /&gt;
:- **실무 팁**: 조인 키는 **카디널리티가 높은 컬럼**(예: ID, 주문번호)을 사용해야 함&lt;br /&gt;
&lt;br /&gt;
성별, 부서코드(10개 정도) 같은 낮은 카디널리티 컬럼으로 Hash Join하면 성능이 매우 나빠집니다!&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
==== JOIN 컬럼 통계정보 ====&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 복합 키 조인 시 통계 확인&lt;br /&gt;
SELECT column_name, num_distinct&lt;br /&gt;
FROM user_tab_col_statistics&lt;br /&gt;
WHERE table_name = &#039;T1&#039;&lt;br /&gt;
  AND column_name IN (&#039;COL1&#039;, &#039;COL2&#039;);&lt;br /&gt;
&lt;br /&gt;
-- 복합 컬럼 통계 (중요!)&lt;br /&gt;
SELECT column_name, num_distinct&lt;br /&gt;
FROM user_tab_col_statistics&lt;br /&gt;
WHERE table_name = &#039;T1&#039;&lt;br /&gt;
  AND column_name = &#039;SYS_NC00003$&#039;;  -- 복합 컬럼 통계&lt;br /&gt;
&lt;br /&gt;
-- 또는 직접 확인&lt;br /&gt;
SELECT COUNT(DISTINCT col1 || &#039;,&#039; || col2) AS composite_cardinality&lt;br /&gt;
FROM t1;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
* 해시 조인이 효율적으로 작동하는 것은 해시 버킷이 잘 분산되어 있고, 해시 테이블이 메모리에서 모두 처리될 때입니다. 만약 데이터가 한쪽으로 치우쳐 해시 충돌이 심하거나, 메모리 부족으로 디스크 I/O가 발생하면 해시 조인의 장점이 사라져 성능이 느려지게 됩니다.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 장/단점 ===&lt;br /&gt;
==== 장점 ====&lt;br /&gt;
# 대용량 테이블간 &amp;quot;=&amp;quot; 조건의 조인에 적합(정렬 불필요,입력 순서 무관)&lt;br /&gt;
# 빠른 접근 가능(버킷을 이용한 빠른 처리)&lt;br /&gt;
# 병렬처리에 적합하면 효율성이 좋음&lt;br /&gt;
&lt;br /&gt;
==== 단점 ==== &lt;br /&gt;
# &amp;quot;=&amp;quot; 조건에만 적합, 범위 조건(between 이나 like) 조인은 사용할수 없음.&lt;br /&gt;
# 메모리에 의존적임(빌드테이블이 메모리내에 들어가지 못하면 템프 i/o가 발생)&lt;br /&gt;
# 조인키값이 한쪽에 치우치거나(skew),중복도가 높으면 특정 버킷에 연결된 버킷이 길어져 탐색하는 시간이 증가함(CPU사용량 급증, 충돌증가)&lt;br /&gt;
# 버킷수가 데이터 대비 부족하면 충돌 발생과 체인 길이가 늘어 탐색 비용이 증가함  &lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=NL_%EC%A1%B0%EC%9D%B8&amp;diff=1632</id>
		<title>NL 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=NL_%EC%A1%B0%EC%9D%B8&amp;diff=1632"/>
		<updated>2025-10-24T02:10:25Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== NL(Nested Loop) 조인 ===&lt;br /&gt;
==== Nested Loop Join 의 개념 ====&lt;br /&gt;
::::{{틀:핵심&lt;br /&gt;
|제목=&#039;&#039;&#039; &amp;lt;big&amp;gt;NL 조인의 영문의 뜻 : 중첩된(Nested) + 반복(Loop) + 연결(Join)&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
|내용= &#039;&#039;&#039; &amp;lt;big&amp;gt;프로그래밍 언어에서 For 문장 처럼 반복(Loop) 처리&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
:::# 두개의 테이블에서, 하나의 집합(테이블)을 기준으로 순차적으로 상대방 테이블의 row 를 결합하여 원하는 결과를 추출하는 테이블 연결 방식&lt;br /&gt;
:::# 조회기준이 되는 테이블 : 선행테이블=드라이빙(driving)테이블=outer테이블=바깥테이블&lt;br /&gt;
:::# 결합되어지는 테이블   : 후행테이블=드리븐(driven)테이블=inner테이블)=안쪽 테이블&lt;br /&gt;
:::# NL 조인에서는 드라이빙 테이블의 각 row 에 대하여 loop 방식으로 조인이 되는데 드라이빙 테이블의 집합을 줄여주는 조건이 (where에서 사용된 컬럼에 인덱스가 있는가?) NL 조인의 성능을 결정함.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
::::* 예시) USE_NL(각각 테이블에 어떤 컬럼에 인덱스를 이용 할것 인가? )&lt;br /&gt;
::::&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT 고객.* ,주문.*&lt;br /&gt;
  FROM 고객 -- 1) [고객]테이블은 어떤 컬럼에 인덱스가 있으면 좋은가?&lt;br /&gt;
  JOIN 주문 -- 2) [주문]테이블은 어떤 컬럼에 인덱스가 있으면 좋은가?&lt;br /&gt;
    ON 주문.고객번호 = 고객.고객번호&lt;br /&gt;
 WHERE 고객.고객명=&#039;홍길동&#039;&lt;br /&gt;
   AND 주문.주문일자=&#039;201909&#039;; &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::::* 고객(outer,드라이빙테이블,선행 테이블)은 WHERE절의 &#039;=&#039; 조건(고객명) 인덱스 여부, &lt;br /&gt;
::::* 주문(inner,드리븐테이블,후행 테이블)은  &#039;고객번호&#039; 컬럼(조인되는컬럼)의 인덱스 여부가  N/L조인의 성능을 결정.&lt;br /&gt;
&lt;br /&gt;
==== NL조인 에서 중요한건 인덱스 ====&lt;br /&gt;
::::{{틀:핵심&lt;br /&gt;
|제목= NL조인 에서&lt;br /&gt;
|내용= 인덱스의 중요성&lt;br /&gt;
:# outer(선행,driving ) 테이블이 한 row 씩 반복해 가면서 inner(후행,driven ) 테이블로  조인이 이루어짐&lt;br /&gt;
:# inner(후행) 테이블의 컬럼은 outer(선행) 테이블의 컬럼을 받아서 데이터를 빨리 찾기하기 위해서는 &#039;&#039;&#039;인덱스&#039;&#039;&#039;가 반드시 있어야함.(성능 향상 포인트)&lt;br /&gt;
:# inner 테이블의 크기가 적다면 테이블 전체를 메모리에 읽어서 반복적으로 검색하는 것이 빠름&lt;br /&gt;
:# 조인되는 값들의 카디널리티(cardinality) 가 높을 수록, 한 번 스캔되어 조인된 자료가 다음 row 에서 조인에 사용될 확률이 낮아지기 때문에 스캔에 의한 조인 효율은 저하 &lt;br /&gt;
* 원하는 값이 존재하는 지 빠르게 확인하기 위한 목적과 그 값에 대한 데이터를 빠르게 읽어 내기 위해서 인덱스 오브젝트는 N/L 조인에서 (특히 inner 테이블의 액세스 시) 반드시 필요&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== NL조인에서 선행 과 후행 테이블의 인덱스의 위치 ====&lt;br /&gt;
::::{{틀:핵심&lt;br /&gt;
|제목=&#039;&#039;&#039;NL조인에서 인덱스의 위치의 중요성&#039;&#039;&#039; &lt;br /&gt;
:::# 선행(드라이빙,outer) 테이블은 where 절에 사용된 컬럼의 인덱스 여부가 중요&lt;br /&gt;
:::#: [why] 데이터 범위를 줄여주는 조건에 인덱스가 있어야 빨리 찾기 때문&lt;br /&gt;
:::# 후행(드리븐,inner) 테이블은 조인조건 컬럼의 인덱스가 중요&lt;br /&gt;
:::#: [why] 조인되는 조건에 인덱스가 있어야 빨리 찾기 때문&lt;br /&gt;
|내용= &#039;&#039;&#039;&amp;lt;big&amp;gt; outer 테이블 조회 후 1건씩 순차적(sequential)으로 inner 테이블에 접근 &amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
:::* INDEX 구성 &lt;br /&gt;
:::* EMP.IX_EMP_01 ( DEPTNO)&lt;br /&gt;
:::* DEPT.IX_DEPT_01 ( DEPTNO))&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT /*+ USE_NL(B) LEADING(A) */ ENAME,JOB,B.DNAME   &lt;br /&gt;
  FROM EMP A   &lt;br /&gt;
  JOIN DEPT B     &lt;br /&gt;
    ON A.DEPTNO = B.DEPTNO&lt;br /&gt;
 &lt;br /&gt;
-------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
| Id  | Operation                    | Name       | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |&lt;br /&gt;
-------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
|   0 | SELECT STATEMENT             |            |      1 |        |       |    17 (100)|          |     14 |00:00:00.01 |      11 |&lt;br /&gt;
|   1 |  NESTED LOOPS                |            |      1 |     14 |   672 |    17   (0)| 00:00:01 |     14 |00:00:00.01 |      11 |&lt;br /&gt;
|   2 |   NESTED LOOPS               |            |      1 |     14 |   672 |    17   (0)| 00:00:01 |     14 |00:00:00.01 |      10 |&lt;br /&gt;
|   3 |    TABLE ACCESS FULL         | EMP        |      1 |     14 |   364 |     3   (0)| 00:00:01 |     14 |00:00:00.01 |       7 |&lt;br /&gt;
|*  4 |    INDEX RANGE SCAN          | IX_DEPT_01 |     14 |      1 |       |     0   (0)|          |     14 |00:00:00.01 |       3 |&lt;br /&gt;
|   5 |   TABLE ACCESS BY INDEX ROWID| DEPT       |     14 |      1 |    22 |     1   (0)| 00:00:01 |     14 |00:00:00.01 |       1 |&lt;br /&gt;
-------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== LEADING 이나 ORDERED 힌트와 같이 사용 추천 ====&lt;br /&gt;
* INDEX 구성 &lt;br /&gt;
* EMP.IX_EMP_01 ( DEPTNO)&lt;br /&gt;
* DEPT.IX_DEPT_01 ( DEPTNO))&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT /*+ USE_NL(A,B) LEADING(A) */ ENAME,JOB,B.DNAME   &lt;br /&gt;
  FROM EMP A   JOIN DEPT B     ON A.DEPTNO = B.DEPTNO&lt;br /&gt;
 &lt;br /&gt;
-------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
| Id  | Operation                    | Name       | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |&lt;br /&gt;
-------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
|   0 | SELECT STATEMENT             |            |      1 |        |       |    17 (100)|          |     14 |00:00:00.01 |      11 |&lt;br /&gt;
|   1 |  NESTED LOOPS                |            |      1 |     14 |   672 |    17   (0)| 00:00:01 |     14 |00:00:00.01 |      11 |&lt;br /&gt;
|   2 |   NESTED LOOPS               |            |      1 |     14 |   672 |    17   (0)| 00:00:01 |     14 |00:00:00.01 |      10 |&lt;br /&gt;
|   3 |    TABLE ACCESS FULL         | EMP        |      1 |     14 |   364 |     3   (0)| 00:00:01 |     14 |00:00:00.01 |       7 |&lt;br /&gt;
|*  4 |    INDEX RANGE SCAN          | IX_DEPT_01 |     14 |      1 |       |     0   (0)|          |     14 |00:00:00.01 |       3 |&lt;br /&gt;
|   5 |   TABLE ACCESS BY INDEX ROWID| DEPT       |     14 |      1 |    22 |     1   (0)| 00:00:01 |     14 |00:00:00.01 |       1 |&lt;br /&gt;
-------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== USE_NL 괄호 안의 테이블은 NL조인 적용 대상 테이블  ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT /*+ USE_NL(A,B) LEADING(B) */ ENAME,JOB,B.DNAME   &lt;br /&gt;
  FROM EMP A   &lt;br /&gt;
  JOIN DEPT B     ON A.DEPTNO = B.DEPTNO&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
| Id  | Operation                    | Name      | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
|   0 | SELECT STATEMENT             |           |      1 |        |       |     6 (100)|          |     14 |00:00:00.01 |      10 |&lt;br /&gt;
|   1 |  NESTED LOOPS                |           |      1 |     14 |   672 |     6   (0)| 00:00:01 |     14 |00:00:00.01 |      10 |&lt;br /&gt;
|   2 |   NESTED LOOPS               |           |      1 |     20 |   672 |     6   (0)| 00:00:01 |     14 |00:00:00.01 |       9 |&lt;br /&gt;
|   3 |    TABLE ACCESS FULL         | DEPT      |      1 |      4 |    88 |     3   (0)| 00:00:01 |      4 |00:00:00.01 |       7 |&lt;br /&gt;
|*  4 |    INDEX RANGE SCAN          | IX_EMP_01 |      4 |      5 |       |     0   (0)|          |     14 |00:00:00.01 |       2 |&lt;br /&gt;
|   5 |   TABLE ACCESS BY INDEX ROWID| EMP       |     14 |      4 |   104 |     1   (0)| 00:00:01 |     14 |00:00:00.01 |       1 |&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== NL조인 - 파이썬으로 구현한 예제 ===&lt;br /&gt;
# NL 조인은 두 개의 테이블을 조인할 때, 한 테이블(외부 테이블)을 먼저 순차적으로 스캔하고, 스캔한 각 행마다 다른 테이블(내부 테이블)을 반복적으로 탐색하여 조인하는 방식&lt;br /&gt;
# 이중 for 루프와 같아서 이름이 &#039;중첩 루프(Nested Loop)&#039; 조인&lt;br /&gt;
# 외부 테이블(Outer Table): 바깥쪽 루프를 도는 테이블. 주로 크기가 작거나 인덱스가 있는 테이블이 선택됨.&lt;br /&gt;
# 내부 테이블(Inner Table): 안쪽 루프를 도는 테이블. 외부 테이블의 행 수만큼 반복적으로 스캔. 내부 테이블에 조인되는 컬럼에 인덱스가 있으면 성능이 매우 향상됨&lt;br /&gt;
&lt;br /&gt;
* 아래 코드는 employees 테이블과 departments 테이블을 NL 조인하는 상황을 가정 &lt;br /&gt;
* 이 예제에서는 departments를 외부 테이블로, employees를 내부 테이블로 사용.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# &#039;departments&#039; 테이블 (외부 테이블)&lt;br /&gt;
departments = [&lt;br /&gt;
    {&#039;dept_id&#039;: 10, &#039;dept_name&#039;: &#039;인사팀&#039;},&lt;br /&gt;
    {&#039;dept_id&#039;: 20, &#039;dept_name&#039;: &#039;개발팀&#039;},&lt;br /&gt;
    {&#039;dept_id&#039;: 30, &#039;dept_name&#039;: &#039;마케팅팀&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# &#039;employees&#039; 테이블 (내부 테이블)&lt;br /&gt;
employees = [&lt;br /&gt;
    {&#039;emp_id&#039;: 1, &#039;name&#039;: &#039;김철수&#039;, &#039;dept_id&#039;: 20},&lt;br /&gt;
    {&#039;emp_id&#039;: 2, &#039;name&#039;: &#039;박영희&#039;, &#039;dept_id&#039;: 10},&lt;br /&gt;
    {&#039;emp_id&#039;: 3, &#039;name&#039;: &#039;이민호&#039;, &#039;dept_id&#039;: 20},&lt;br /&gt;
    {&#039;emp_id&#039;: 4, &#039;name&#039;: &#039;최민수&#039;, &#039;dept_id&#039;: 30},&lt;br /&gt;
    {&#039;emp_id&#039;: 5, &#039;name&#039;: &#039;정수미&#039;, &#039;dept_id&#039;: 40},  # 조인되지 않는 데이터&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인된 결과 &lt;br /&gt;
joined_results = []&lt;br /&gt;
&lt;br /&gt;
# 외부 루프: departments 테이블을 순회&lt;br /&gt;
for dept in departments:&lt;br /&gt;
    # 내부 루프: employees 테이블을 순회&lt;br /&gt;
    for emp in employees:&lt;br /&gt;
        # 조인 조건: 두 테이블의 dept_id가 같은지 비교&lt;br /&gt;
        if dept[&#039;dept_id&#039;] == emp[&#039;dept_id&#039;]:&lt;br /&gt;
            # 조건이 일치하면 조인 결과를 추가&lt;br /&gt;
            joined_row = {&lt;br /&gt;
                &#039;emp_id&#039;: emp[&#039;emp_id&#039;],&lt;br /&gt;
                &#039;name&#039;: emp[&#039;name&#039;],&lt;br /&gt;
                &#039;dept_name&#039;: dept[&#039;dept_name&#039;]&lt;br /&gt;
            }&lt;br /&gt;
            joined_results.append(joined_row)&lt;br /&gt;
&lt;br /&gt;
# 조인 결과 출력&lt;br /&gt;
for result in joined_results:&lt;br /&gt;
    print(result)&lt;br /&gt;
&lt;br /&gt;
# 예상 출력:&lt;br /&gt;
# {&#039;emp_id&#039;: 2, &#039;name&#039;: &#039;박영희&#039;, &#039;dept_name&#039;: &#039;인사팀&#039;}&lt;br /&gt;
# {&#039;emp_id&#039;: 1, &#039;name&#039;: &#039;김철수&#039;, &#039;dept_name&#039;: &#039;개발팀&#039;}&lt;br /&gt;
# {&#039;emp_id&#039;: 3, &#039;name&#039;: &#039;이민호&#039;, &#039;dept_name&#039;: &#039;개발팀&#039;}&lt;br /&gt;
# {&#039;emp_id&#039;: 4, &#039;name&#039;: &#039;최민수&#039;, &#039;dept_name&#039;: &#039;마케팅팀&#039;}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== NL 조인의 특징 ===&lt;br /&gt;
* 성능: 외부 테이블의 크기(건수) 와 내부 테이블의 조인되는 컬럼의 인덱스 존재 여부에 따라 성능이 크게 차이남.&lt;br /&gt;
* 외부 테이블의 크기: 외부 테이블의 행 수가 적을수록 내부 루프를 도는 횟수가 줄어들어 효율적임.&lt;br /&gt;
* 내부 테이블의 인덱스: 내부 테이블의 조인되는 컬럼에 인덱스가 있으면, 내부 루프가 전체 테이블을 스캔하는 대신 인덱스 스캔을 통해 필요한 행만 빠르게 찾으므로 성능이 매우 좋아짐.&lt;br /&gt;
* 활용: 주로 외부 테이블이 작고, 내부 테이블의 조인 키에 인덱스가 잘 구성되어 있을 때 효율적입니다. &lt;br /&gt;
* 대용량 테이블을 조인할 때는 대부분 비효율적입니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:oracle]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=NL_%EC%A1%B0%EC%9D%B8&amp;diff=1631</id>
		<title>NL 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=NL_%EC%A1%B0%EC%9D%B8&amp;diff=1631"/>
		<updated>2025-10-24T02:09:29Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* Nested Loop Join 의 개념 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== NL(Nested Loop) 조인 ===&lt;br /&gt;
==== Nested Loop Join 의 개념 ====&lt;br /&gt;
{{틀:핵심&lt;br /&gt;
|제목=&#039;&#039;&#039; &amp;lt;big&amp;gt;NL 조인의 영문의 뜻 : 중첩된(Nested) + 반복(Loop) + 연결(Join)&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
|내용= &#039;&#039;&#039; &amp;lt;big&amp;gt;프로그래밍 언어에서 For 문장 처럼 반복(Loop) 처리&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
:::# 두개의 테이블에서, 하나의 집합(테이블)을 기준으로 순차적으로 상대방 테이블의 row 를 결합하여 원하는 결과를 추출하는 테이블 연결 방식&lt;br /&gt;
:::# 조회기준이 되는 테이블 : 선행테이블=드라이빙(driving)테이블=outer테이블=바깥테이블&lt;br /&gt;
:::# 결합되어지는 테이블   : 후행테이블=드리븐(driven)테이블=inner테이블)=안쪽 테이블&lt;br /&gt;
:::# NL 조인에서는 드라이빙 테이블의 각 row 에 대하여 loop 방식으로 조인이 되는데 드라이빙 테이블의 집합을 줄여주는 조건이 (where에서 사용된 컬럼에 인덱스가 있는가?) NL 조인의 성능을 결정함.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
* 예시) USE_NL(각각 테이블에 어떤 컬럼에 인덱스를 이용 할것 인가? )&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT 고객.* ,주문.*&lt;br /&gt;
  FROM 고객 -- 1) [고객]테이블은 어떤 컬럼에 인덱스가 있으면 좋은가?&lt;br /&gt;
  JOIN 주문 -- 2) [주문]테이블은 어떤 컬럼에 인덱스가 있으면 좋은가?&lt;br /&gt;
    ON 주문.고객번호 = 고객.고객번호&lt;br /&gt;
 WHERE 고객.고객명=&#039;홍길동&#039;&lt;br /&gt;
   AND 주문.주문일자=&#039;201909&#039;; &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 고객(outer,드라이빙테이블,선행 테이블)은 WHERE절의 &#039;=&#039; 조건(고객명) 인덱스 여부, &lt;br /&gt;
* 주문(inner,드리븐테이블,후행 테이블)은  &#039;고객번호&#039; 컬럼(조인되는컬럼)의 인덱스 여부가  N/L조인의 성능을 결정.&lt;br /&gt;
&lt;br /&gt;
==== NL조인 에서 중요한건 인덱스 ====&lt;br /&gt;
{{틀:핵심&lt;br /&gt;
|제목= NL조인 에서&lt;br /&gt;
|내용= 인덱스의 중요성&lt;br /&gt;
:# outer(선행,driving ) 테이블이 한 row 씩 반복해 가면서 inner(후행,driven ) 테이블로  조인이 이루어짐&lt;br /&gt;
:# inner(후행) 테이블의 컬럼은 outer(선행) 테이블의 컬럼을 받아서 데이터를 빨리 찾기하기 위해서는 &#039;&#039;&#039;인덱스&#039;&#039;&#039;가 반드시 있어야함.(성능 향상 포인트)&lt;br /&gt;
:# inner 테이블의 크기가 적다면 테이블 전체를 메모리에 읽어서 반복적으로 검색하는 것이 빠름&lt;br /&gt;
:# 조인되는 값들의 카디널리티(cardinality) 가 높을 수록, 한 번 스캔되어 조인된 자료가 다음 row 에서 조인에 사용될 확률이 낮아지기 때문에 스캔에 의한 조인 효율은 저하 &lt;br /&gt;
* 원하는 값이 존재하는 지 빠르게 확인하기 위한 목적과 그 값에 대한 데이터를 빠르게 읽어 내기 위해서 인덱스 오브젝트는 N/L 조인에서 (특히 inner 테이블의 액세스 시) 반드시 필요&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== NL조인에서 선행 과 후행 테이블의 인덱스의 위치 ====&lt;br /&gt;
{{틀:핵심&lt;br /&gt;
|제목=&#039;&#039;&#039;NL조인에서 인덱스의 위치의 중요성&#039;&#039;&#039; &lt;br /&gt;
:::# 선행(드라이빙,outer) 테이블은 where 절에 사용된 컬럼의 인덱스 여부가 중요&lt;br /&gt;
:::#: [why] 데이터 범위를 줄여주는 조건에 인덱스가 있어야 빨리 찾기 때문&lt;br /&gt;
:::# 후행(드리븐,inner) 테이블은 조인조건 컬럼의 인덱스가 중요&lt;br /&gt;
:::#: [why] 조인되는 조건에 인덱스가 있어야 빨리 찾기 때문&lt;br /&gt;
|내용= &#039;&#039;&#039;&amp;lt;big&amp;gt; outer 테이블 조회 후 1건씩 순차적(sequential)으로 inner 테이블에 접근 &amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
:::* INDEX 구성 &lt;br /&gt;
:::* EMP.IX_EMP_01 ( DEPTNO)&lt;br /&gt;
:::* DEPT.IX_DEPT_01 ( DEPTNO))&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT /*+ USE_NL(B) LEADING(A) */ ENAME,JOB,B.DNAME   &lt;br /&gt;
  FROM EMP A   &lt;br /&gt;
  JOIN DEPT B     &lt;br /&gt;
    ON A.DEPTNO = B.DEPTNO&lt;br /&gt;
 &lt;br /&gt;
-------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
| Id  | Operation                    | Name       | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |&lt;br /&gt;
-------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
|   0 | SELECT STATEMENT             |            |      1 |        |       |    17 (100)|          |     14 |00:00:00.01 |      11 |&lt;br /&gt;
|   1 |  NESTED LOOPS                |            |      1 |     14 |   672 |    17   (0)| 00:00:01 |     14 |00:00:00.01 |      11 |&lt;br /&gt;
|   2 |   NESTED LOOPS               |            |      1 |     14 |   672 |    17   (0)| 00:00:01 |     14 |00:00:00.01 |      10 |&lt;br /&gt;
|   3 |    TABLE ACCESS FULL         | EMP        |      1 |     14 |   364 |     3   (0)| 00:00:01 |     14 |00:00:00.01 |       7 |&lt;br /&gt;
|*  4 |    INDEX RANGE SCAN          | IX_DEPT_01 |     14 |      1 |       |     0   (0)|          |     14 |00:00:00.01 |       3 |&lt;br /&gt;
|   5 |   TABLE ACCESS BY INDEX ROWID| DEPT       |     14 |      1 |    22 |     1   (0)| 00:00:01 |     14 |00:00:00.01 |       1 |&lt;br /&gt;
-------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== LEADING 이나 ORDERED 힌트와 같이 사용 추천 ====&lt;br /&gt;
* INDEX 구성 &lt;br /&gt;
* EMP.IX_EMP_01 ( DEPTNO)&lt;br /&gt;
* DEPT.IX_DEPT_01 ( DEPTNO))&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT /*+ USE_NL(A,B) LEADING(A) */ ENAME,JOB,B.DNAME   &lt;br /&gt;
  FROM EMP A   JOIN DEPT B     ON A.DEPTNO = B.DEPTNO&lt;br /&gt;
 &lt;br /&gt;
-------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
| Id  | Operation                    | Name       | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |&lt;br /&gt;
-------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
|   0 | SELECT STATEMENT             |            |      1 |        |       |    17 (100)|          |     14 |00:00:00.01 |      11 |&lt;br /&gt;
|   1 |  NESTED LOOPS                |            |      1 |     14 |   672 |    17   (0)| 00:00:01 |     14 |00:00:00.01 |      11 |&lt;br /&gt;
|   2 |   NESTED LOOPS               |            |      1 |     14 |   672 |    17   (0)| 00:00:01 |     14 |00:00:00.01 |      10 |&lt;br /&gt;
|   3 |    TABLE ACCESS FULL         | EMP        |      1 |     14 |   364 |     3   (0)| 00:00:01 |     14 |00:00:00.01 |       7 |&lt;br /&gt;
|*  4 |    INDEX RANGE SCAN          | IX_DEPT_01 |     14 |      1 |       |     0   (0)|          |     14 |00:00:00.01 |       3 |&lt;br /&gt;
|   5 |   TABLE ACCESS BY INDEX ROWID| DEPT       |     14 |      1 |    22 |     1   (0)| 00:00:01 |     14 |00:00:00.01 |       1 |&lt;br /&gt;
-------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== USE_NL 괄호 안의 테이블은 NL조인 적용 대상 테이블  ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT /*+ USE_NL(A,B) LEADING(B) */ ENAME,JOB,B.DNAME   &lt;br /&gt;
  FROM EMP A   &lt;br /&gt;
  JOIN DEPT B     ON A.DEPTNO = B.DEPTNO&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
| Id  | Operation                    | Name      | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
|   0 | SELECT STATEMENT             |           |      1 |        |       |     6 (100)|          |     14 |00:00:00.01 |      10 |&lt;br /&gt;
|   1 |  NESTED LOOPS                |           |      1 |     14 |   672 |     6   (0)| 00:00:01 |     14 |00:00:00.01 |      10 |&lt;br /&gt;
|   2 |   NESTED LOOPS               |           |      1 |     20 |   672 |     6   (0)| 00:00:01 |     14 |00:00:00.01 |       9 |&lt;br /&gt;
|   3 |    TABLE ACCESS FULL         | DEPT      |      1 |      4 |    88 |     3   (0)| 00:00:01 |      4 |00:00:00.01 |       7 |&lt;br /&gt;
|*  4 |    INDEX RANGE SCAN          | IX_EMP_01 |      4 |      5 |       |     0   (0)|          |     14 |00:00:00.01 |       2 |&lt;br /&gt;
|   5 |   TABLE ACCESS BY INDEX ROWID| EMP       |     14 |      4 |   104 |     1   (0)| 00:00:01 |     14 |00:00:00.01 |       1 |&lt;br /&gt;
------------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== NL조인 - 파이썬으로 구현한 예제 ===&lt;br /&gt;
# NL 조인은 두 개의 테이블을 조인할 때, 한 테이블(외부 테이블)을 먼저 순차적으로 스캔하고, 스캔한 각 행마다 다른 테이블(내부 테이블)을 반복적으로 탐색하여 조인하는 방식&lt;br /&gt;
# 이중 for 루프와 같아서 이름이 &#039;중첩 루프(Nested Loop)&#039; 조인&lt;br /&gt;
# 외부 테이블(Outer Table): 바깥쪽 루프를 도는 테이블. 주로 크기가 작거나 인덱스가 있는 테이블이 선택됨.&lt;br /&gt;
# 내부 테이블(Inner Table): 안쪽 루프를 도는 테이블. 외부 테이블의 행 수만큼 반복적으로 스캔. 내부 테이블에 조인되는 컬럼에 인덱스가 있으면 성능이 매우 향상됨&lt;br /&gt;
&lt;br /&gt;
* 아래 코드는 employees 테이블과 departments 테이블을 NL 조인하는 상황을 가정 &lt;br /&gt;
* 이 예제에서는 departments를 외부 테이블로, employees를 내부 테이블로 사용.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# &#039;departments&#039; 테이블 (외부 테이블)&lt;br /&gt;
departments = [&lt;br /&gt;
    {&#039;dept_id&#039;: 10, &#039;dept_name&#039;: &#039;인사팀&#039;},&lt;br /&gt;
    {&#039;dept_id&#039;: 20, &#039;dept_name&#039;: &#039;개발팀&#039;},&lt;br /&gt;
    {&#039;dept_id&#039;: 30, &#039;dept_name&#039;: &#039;마케팅팀&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# &#039;employees&#039; 테이블 (내부 테이블)&lt;br /&gt;
employees = [&lt;br /&gt;
    {&#039;emp_id&#039;: 1, &#039;name&#039;: &#039;김철수&#039;, &#039;dept_id&#039;: 20},&lt;br /&gt;
    {&#039;emp_id&#039;: 2, &#039;name&#039;: &#039;박영희&#039;, &#039;dept_id&#039;: 10},&lt;br /&gt;
    {&#039;emp_id&#039;: 3, &#039;name&#039;: &#039;이민호&#039;, &#039;dept_id&#039;: 20},&lt;br /&gt;
    {&#039;emp_id&#039;: 4, &#039;name&#039;: &#039;최민수&#039;, &#039;dept_id&#039;: 30},&lt;br /&gt;
    {&#039;emp_id&#039;: 5, &#039;name&#039;: &#039;정수미&#039;, &#039;dept_id&#039;: 40},  # 조인되지 않는 데이터&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인된 결과 &lt;br /&gt;
joined_results = []&lt;br /&gt;
&lt;br /&gt;
# 외부 루프: departments 테이블을 순회&lt;br /&gt;
for dept in departments:&lt;br /&gt;
    # 내부 루프: employees 테이블을 순회&lt;br /&gt;
    for emp in employees:&lt;br /&gt;
        # 조인 조건: 두 테이블의 dept_id가 같은지 비교&lt;br /&gt;
        if dept[&#039;dept_id&#039;] == emp[&#039;dept_id&#039;]:&lt;br /&gt;
            # 조건이 일치하면 조인 결과를 추가&lt;br /&gt;
            joined_row = {&lt;br /&gt;
                &#039;emp_id&#039;: emp[&#039;emp_id&#039;],&lt;br /&gt;
                &#039;name&#039;: emp[&#039;name&#039;],&lt;br /&gt;
                &#039;dept_name&#039;: dept[&#039;dept_name&#039;]&lt;br /&gt;
            }&lt;br /&gt;
            joined_results.append(joined_row)&lt;br /&gt;
&lt;br /&gt;
# 조인 결과 출력&lt;br /&gt;
for result in joined_results:&lt;br /&gt;
    print(result)&lt;br /&gt;
&lt;br /&gt;
# 예상 출력:&lt;br /&gt;
# {&#039;emp_id&#039;: 2, &#039;name&#039;: &#039;박영희&#039;, &#039;dept_name&#039;: &#039;인사팀&#039;}&lt;br /&gt;
# {&#039;emp_id&#039;: 1, &#039;name&#039;: &#039;김철수&#039;, &#039;dept_name&#039;: &#039;개발팀&#039;}&lt;br /&gt;
# {&#039;emp_id&#039;: 3, &#039;name&#039;: &#039;이민호&#039;, &#039;dept_name&#039;: &#039;개발팀&#039;}&lt;br /&gt;
# {&#039;emp_id&#039;: 4, &#039;name&#039;: &#039;최민수&#039;, &#039;dept_name&#039;: &#039;마케팅팀&#039;}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== NL 조인의 특징 ===&lt;br /&gt;
* 성능: 외부 테이블의 크기(건수) 와 내부 테이블의 조인되는 컬럼의 인덱스 존재 여부에 따라 성능이 크게 차이남.&lt;br /&gt;
* 외부 테이블의 크기: 외부 테이블의 행 수가 적을수록 내부 루프를 도는 횟수가 줄어들어 효율적임.&lt;br /&gt;
* 내부 테이블의 인덱스: 내부 테이블의 조인되는 컬럼에 인덱스가 있으면, 내부 루프가 전체 테이블을 스캔하는 대신 인덱스 스캔을 통해 필요한 행만 빠르게 찾으므로 성능이 매우 좋아짐.&lt;br /&gt;
* 활용: 주로 외부 테이블이 작고, 내부 테이블의 조인 키에 인덱스가 잘 구성되어 있을 때 효율적입니다. &lt;br /&gt;
* 대용량 테이블을 조인할 때는 대부분 비효율적입니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:oracle]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1630</id>
		<title>대문</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1630"/>
		<updated>2025-10-24T02:05:49Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* ORACLE */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__notoc__&lt;br /&gt;
https://dbstudy.co.kr/w/resources/assets/dbstudy_iconx1.png &lt;br /&gt;
= Welcome To DB STUDY - {{CURRENTYEAR}}.{{CURRENTMONTHNAME}}.{{CURRENTDAY}}({{CURRENTDAYNAME}}) =&lt;br /&gt;
= 전문가가 되고 싶다면 기본에 충실 하라. (Return To Basics) =&lt;br /&gt;
* {{SERVER}}&lt;br /&gt;
* 게시글 총 : {{NUMBEROFPAGES}} 건 , 사용자 : {{NUMBEROFUSERS}} 명 , &lt;br /&gt;
&amp;lt;p sizes=&amp;quot;(max-width: 600px) 480px,800px&amp;quot;&amp;gt;&lt;br /&gt;
https://dbstudy.co.kr/w/images/dbstudy_logo.jpg&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
{{틀:타이틀 라운드&lt;br /&gt;
|제목=[[:Category:oracle|오라클]]: {{PAGESINCATEGORY: oracle}} 건 ,  [[:Category:postgresql|PostgreSql]] : {{PAGESINCATEGORY: postgresql}} 건 ,  [[:Category:mysql|MySQL]]: {{PAGESINCATEGORY: mysql}} 건,  [[:Category:linux|LINUX]]: {{PAGESINCATEGORY: linux}} 건&lt;br /&gt;
|아이콘=emoji_objects&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= ORACLE =&lt;br /&gt;
== [[ORACLE SQL 튜닝]] ==&lt;br /&gt;
=== [[성능을 고려한 설계]] ===&lt;br /&gt;
=== [[효율적인 SQL 작성법]] ===&lt;br /&gt;
=== [[튜닝 환경 구축]] ===&lt;br /&gt;
:::# [[DBMS_XPLAN 사용법]]&lt;br /&gt;
:::# [[REAL PLAN 사용법]]&lt;br /&gt;
:::# [[SQL 트레이스 방법]]&lt;br /&gt;
&lt;br /&gt;
=== [[인덱스 설계]] ===&lt;br /&gt;
:::# [[인덱스 아키텍처]]&lt;br /&gt;
:::# [[오라클 인덱스 종류|인덱스 종류]]&lt;br /&gt;
:::# [[엑세스 패스]]&lt;br /&gt;
&lt;br /&gt;
=== [[옵티마이져]] ===&lt;br /&gt;
==== [[JPPD(Join Predicate PushDown,조인절 PUSHDOWN)]]====&lt;br /&gt;
==== [[View pushed predicate (조건절 PUSHDOWN)]]====&lt;br /&gt;
&lt;br /&gt;
=== [[튜닝 힌트]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[대용량 데이터 튜닝]] ===&lt;br /&gt;
==== [[병렬 쿼리 튜닝]] ====&lt;br /&gt;
=== [[ORACLE 튜닝 대상 조회]] ===&lt;br /&gt;
=== [[성능 문제 식별 방법론과 튜닝 접근법]]===&lt;br /&gt;
----&lt;br /&gt;
== [[ORACLE 아키텍쳐의 이해]] ==&lt;br /&gt;
=== [[테이블 조인 방식|오라클 조인 과 알고리즘 ]] ===&lt;br /&gt;
==== [[NL 조인]]====&lt;br /&gt;
==== [[HASH 조인]]====&lt;br /&gt;
==== [[MERGE 조인|SORT MERGE 조인]]====&lt;br /&gt;
==== [[세미조인]] ====&lt;br /&gt;
==== [[안티조인]] ====&lt;br /&gt;
=== [[파라미터 설계]] ===&lt;br /&gt;
=== [[오라클 테이블의 구조]]===&lt;br /&gt;
=== [[오라클 파티션테이블의 구조]] ===&lt;br /&gt;
=== [[데이터 블럭의 구조]] ===&lt;br /&gt;
=== [[오라클 컬럼의 구조]] ===&lt;br /&gt;
==== [[오라클 컬럼의 저장순서]] ====&lt;br /&gt;
==== [[컬럼의 순서가 변경시 ROW의 물리적 구조변화|컬럼 순서 변경시 ROW의 물리구조 변화]]====&lt;br /&gt;
==== [[오라클 컬럼저장 방식 개선 (12c 업그레이드) ]] ====&lt;br /&gt;
=== [[오라클 인덱스의 구조]] ===&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)]]=&lt;br /&gt;
== [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)#개요|개요]] ==&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1629</id>
		<title>대문</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1629"/>
		<updated>2025-10-24T02:00:03Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* ORACLE */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__notoc__&lt;br /&gt;
https://dbstudy.co.kr/w/resources/assets/dbstudy_iconx1.png &lt;br /&gt;
= Welcome To DB STUDY - {{CURRENTYEAR}}.{{CURRENTMONTHNAME}}.{{CURRENTDAY}}({{CURRENTDAYNAME}}) =&lt;br /&gt;
= 전문가가 되고 싶다면 기본에 충실 하라. (Return To Basics) =&lt;br /&gt;
* {{SERVER}}&lt;br /&gt;
* 게시글 총 : {{NUMBEROFPAGES}} 건 , 사용자 : {{NUMBEROFUSERS}} 명 , &lt;br /&gt;
&amp;lt;p sizes=&amp;quot;(max-width: 600px) 480px,800px&amp;quot;&amp;gt;&lt;br /&gt;
https://dbstudy.co.kr/w/images/dbstudy_logo.jpg&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
{{틀:타이틀 라운드&lt;br /&gt;
|제목=[[:Category:oracle|오라클]]: {{PAGESINCATEGORY: oracle}} 건 ,  [[:Category:postgresql|PostgreSql]] : {{PAGESINCATEGORY: postgresql}} 건 ,  [[:Category:mysql|MySQL]]: {{PAGESINCATEGORY: mysql}} 건,  [[:Category:linux|LINUX]]: {{PAGESINCATEGORY: linux}} 건&lt;br /&gt;
|아이콘=emoji_objects&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= ORACLE =&lt;br /&gt;
== [[ORACLE SQL 튜닝]] ==&lt;br /&gt;
=== [[성능을 고려한 설계]] ===&lt;br /&gt;
=== [[효율적인 SQL 작성법]] ===&lt;br /&gt;
=== [[튜닝 환경 구축]] ===&lt;br /&gt;
:::# [[DBMS_XPLAN 사용법]]&lt;br /&gt;
:::# [[REAL PLAN 사용법]]&lt;br /&gt;
:::# [[SQL 트레이스 방법]]&lt;br /&gt;
&lt;br /&gt;
=== [[인덱스 설계]] ===&lt;br /&gt;
:::# [[인덱스 아키텍처]]&lt;br /&gt;
:::# [[오라클 인덱스 종류|인덱스 종류]]&lt;br /&gt;
:::# [[엑세스 패스]]&lt;br /&gt;
&lt;br /&gt;
=== [[테이블 조인 방식|오라클 조인 과 알고리즘 ]] ===&lt;br /&gt;
==== [[NL 조인]]====&lt;br /&gt;
==== [[HASH 조인]]====&lt;br /&gt;
==== [[MERGE 조인|SORT MERGE 조인]]====&lt;br /&gt;
&lt;br /&gt;
==== [[세미조인]] ====&lt;br /&gt;
==== [[안티조인]] ====&lt;br /&gt;
&lt;br /&gt;
=== [[옵티마이져]] ===&lt;br /&gt;
==== [[JPPD(Join Predicate PushDown,조인절 PUSHDOWN)]]====&lt;br /&gt;
&lt;br /&gt;
==== [[View pushed predicate (조건절 PUSHDOWN)]]====&lt;br /&gt;
&lt;br /&gt;
=== [[튜닝 힌트]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[대용량 데이터 튜닝]] ===&lt;br /&gt;
==== [[병렬 쿼리 튜닝]] ====&lt;br /&gt;
&lt;br /&gt;
=== [[ORACLE 튜닝 대상 조회]] ===&lt;br /&gt;
=== [[파라미터 설계]] ===&lt;br /&gt;
=== [[ORACLE 아키텍쳐의 이해]] ===&lt;br /&gt;
==== [[데이터 블럭의 구조]] ====&lt;br /&gt;
==== [[오라클 컬럼의 구조]] ====&lt;br /&gt;
===== [[오라클 컬럼의 저장순서]] =====&lt;br /&gt;
===== [[컬럼의 순서가 변경시 ROW의 물리적 구조변화|컬럼 순서 변경시 ROW의 물리구조 변화]]=====&lt;br /&gt;
&lt;br /&gt;
===== [[오라클 컬럼저장 방식 개선 (12c 업그레이드) ]] =====&lt;br /&gt;
&lt;br /&gt;
==== [[오라클 테이블의 구조]]====&lt;br /&gt;
==== [[오라클 파티션테이블의 구조]] ====&lt;br /&gt;
&lt;br /&gt;
==== [[오라클 인덱스의 구조]] ====&lt;br /&gt;
=== [[성능 문제 식별 방법론과 튜닝 접근법]]===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)]]=&lt;br /&gt;
== [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)#개요|개요]] ==&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1628</id>
		<title>대문</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1628"/>
		<updated>2025-10-24T01:58:50Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 오라클 테이블의 구조 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__notoc__&lt;br /&gt;
https://dbstudy.co.kr/w/resources/assets/dbstudy_iconx1.png &lt;br /&gt;
= Welcome To DB STUDY - {{CURRENTYEAR}}.{{CURRENTMONTHNAME}}.{{CURRENTDAY}}({{CURRENTDAYNAME}}) =&lt;br /&gt;
= 전문가가 되고 싶다면 기본에 충실 하라. (Return To Basics) =&lt;br /&gt;
* {{SERVER}}&lt;br /&gt;
* 게시글 총 : {{NUMBEROFPAGES}} 건 , 사용자 : {{NUMBEROFUSERS}} 명 , &lt;br /&gt;
&amp;lt;p sizes=&amp;quot;(max-width: 600px) 480px,800px&amp;quot;&amp;gt;&lt;br /&gt;
https://dbstudy.co.kr/w/images/dbstudy_logo.jpg&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
{{틀:타이틀 라운드&lt;br /&gt;
|제목=[[:Category:oracle|오라클]]: {{PAGESINCATEGORY: oracle}} 건 ,  [[:Category:postgresql|PostgreSql]] : {{PAGESINCATEGORY: postgresql}} 건 ,  [[:Category:mysql|MySQL]]: {{PAGESINCATEGORY: mysql}} 건,  [[:Category:linux|LINUX]]: {{PAGESINCATEGORY: linux}} 건&lt;br /&gt;
|아이콘=emoji_objects&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= ORACLE =&lt;br /&gt;
== [[ORACLE SQL 튜닝]] ==&lt;br /&gt;
=== [[성능을 고려한 설계]] ===&lt;br /&gt;
=== [[효율적인 SQL 작성법]] ===&lt;br /&gt;
=== [[튜닝 환경 구축]] ===&lt;br /&gt;
# [[DBMS_XPLAN 사용법]]&lt;br /&gt;
# [[REAL PLAN 사용법]]&lt;br /&gt;
# [[SQL 트레이스 방법]]&lt;br /&gt;
&lt;br /&gt;
=== [[인덱스 설계]] ===&lt;br /&gt;
# [[인덱스 아키텍처]]&lt;br /&gt;
# [[오라클 인덱스 종류|인덱스 종류]]&lt;br /&gt;
# [[엑세스 패스]]&lt;br /&gt;
&lt;br /&gt;
=== [[테이블 조인 방식|오라클 조인 과 알고리즘 ]] ===&lt;br /&gt;
==== [[NL 조인]]====&lt;br /&gt;
==== [[HASH 조인]]====&lt;br /&gt;
==== [[MERGE 조인|SORT MERGE 조인]]====&lt;br /&gt;
&lt;br /&gt;
==== [[세미조인]] ====&lt;br /&gt;
==== [[안티조인]] ====&lt;br /&gt;
&lt;br /&gt;
=== [[옵티마이져]] ===&lt;br /&gt;
==== [[JPPD(Join Predicate PushDown,조인절 PUSHDOWN)]]====&lt;br /&gt;
&lt;br /&gt;
==== [[View pushed predicate (조건절 PUSHDOWN)]]====&lt;br /&gt;
&lt;br /&gt;
=== [[튜닝 힌트]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[대용량 데이터 튜닝]] ===&lt;br /&gt;
==== [[병렬 쿼리 튜닝]] ====&lt;br /&gt;
&lt;br /&gt;
=== [[ORACLE 튜닝 대상 조회]] ===&lt;br /&gt;
=== [[파라미터 설계]] ===&lt;br /&gt;
=== [[ORACLE 아키텍쳐의 이해]] ===&lt;br /&gt;
==== [[데이터 블럭의 구조]] ====&lt;br /&gt;
==== [[오라클 컬럼의 구조]] ====&lt;br /&gt;
===== [[오라클 컬럼의 저장순서]] =====&lt;br /&gt;
===== [[컬럼의 순서가 변경시 ROW의 물리적 구조변화|컬럼 순서 변경시 ROW의 물리구조 변화]]=====&lt;br /&gt;
&lt;br /&gt;
===== [[오라클 컬럼저장 방식 개선 (12c 업그레이드) ]] =====&lt;br /&gt;
&lt;br /&gt;
==== [[오라클 테이블의 구조]]====&lt;br /&gt;
==== [[오라클 파티션테이블의 구조]] ====&lt;br /&gt;
&lt;br /&gt;
==== [[오라클 인덱스의 구조]] ====&lt;br /&gt;
=== [[성능 문제 식별 방법론과 튜닝 접근법]]===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)]]=&lt;br /&gt;
== [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)#개요|개요]] ==&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1626</id>
		<title>해시 버킷의 저장구조</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1626"/>
		<updated>2025-10-23T04:55:18Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Oracle Hash Join의 해시테이블 내부 구조==&lt;br /&gt;
=== 전체 구조 개요 ===&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
Hash Table (PGA Memory)&lt;br /&gt;
├── Bucket Array (해시 테이블의 배열)&lt;br /&gt;
│   ├── Bucket #0 → Hash Entry Chain&lt;br /&gt;
│   ├── Bucket #1 → Hash Entry Chain&lt;br /&gt;
│   ├── Bucket #2 → Hash Entry Chain&lt;br /&gt;
│   ├── ...&lt;br /&gt;
│   └── Bucket #N → Hash Entry Chain&lt;br /&gt;
│&lt;br /&gt;
└── Hash Entry Pool (실제 데이터 저장 영역)&lt;br /&gt;
    ├── Entry1 [hash, key, data, next*]&lt;br /&gt;
    ├── Entry2 [hash, key, data, next*]&lt;br /&gt;
    └── ...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:::* 시각적 구조&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
┌─────────────────────────────────────────────────────────┐&lt;br /&gt;
│  Hash Table (PGA Memory)                                │&lt;br /&gt;
├─────────────────────────────────────────────────────────┤&lt;br /&gt;
│                                                         │&lt;br /&gt;
│  [Bucket Array - 포인터만 저장]                            │&lt;br /&gt;
│  ┌──────┬──────────┐                                    │&lt;br /&gt;
│  │ #0   │ NULL     │                                    │&lt;br /&gt;
│  ├──────┼──────────┤                                    │&lt;br /&gt;
│  │ #1   │ 0x1000 ──┼──┐                                 │&lt;br /&gt;
│  ├──────┼──────────┤  │                                 │&lt;br /&gt;
│  │ #2   │ NULL     │  │                                 │&lt;br /&gt;
│  ├──────┼──────────┤  │                                 │&lt;br /&gt;
│  │ #3   │ 0x2000 ──┼──┼──┐                              │&lt;br /&gt;
│  └──────┴──────────┘  │  │                              │&lt;br /&gt;
│                       │  │                              │&lt;br /&gt;
│  [Hash Entry Pool - 실제 데이터 저장]                       │&lt;br /&gt;
│                       │  │                              │&lt;br /&gt;
│  ┌──────────────────┐ │  │                              │&lt;br /&gt;
│  │ 0x1000 주소       │←┘  │                              │&lt;br /&gt;
│  ├──────────────────┤    │                              │&lt;br /&gt;
│  │ Hash: 1234       │    │                              │&lt;br /&gt;
│  │ Next: 0x1100 ────┼──┐ │                              │&lt;br /&gt;
│  │ Key: 10          │  │ │                              │&lt;br /&gt;
│  │ Data: &#039;Sales&#039;    │  │ │                              │&lt;br /&gt;
│  └──────────────────┘  │ │                              │&lt;br /&gt;
│                        │ │                              │&lt;br /&gt;
│  ┌──────────────────┐  │ │                              │&lt;br /&gt;
│  │ 0x1100 주소       │←─┘ │                              │&lt;br /&gt;
│  ├──────────────────┤    │                              │&lt;br /&gt;
│  │ Hash: 5678       │    │                              │&lt;br /&gt;
│  │ Next: NULL       │    │                              │&lt;br /&gt;
│  │ Key: 30          │    │                              │&lt;br /&gt;
│  │ Data: &#039;IT&#039;       │    │                              │&lt;br /&gt;
│  └──────────────────┘    │                              │&lt;br /&gt;
│                          │                              │&lt;br /&gt;
│  ┌──────────────────┐    │                              │&lt;br /&gt;
│  │ 0x2000 주소       │←───┘                              │&lt;br /&gt;
│  ├──────────────────┤                                   │&lt;br /&gt;
│  │ Hash: 9012       │                                   │&lt;br /&gt;
│  │ Next: NULL       │                                   │&lt;br /&gt;
│  │ Key: 20          │                                   │&lt;br /&gt;
│  │ Data: &#039;HR&#039;       │                                   │&lt;br /&gt;
│  └──────────────────┘                                   │&lt;br /&gt;
│                                                         │&lt;br /&gt;
└─────────────────────────────────────────────────────────┘&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 해시 버킷 (Hash Bucket) ===&lt;br /&gt;
:::* Bucket이란?&lt;br /&gt;
:::- Hash Table을 구성하는 **논리적 슬롯(Slot)**&lt;br /&gt;
:::- 포인터 배열로 구현됨&lt;br /&gt;
:::- 각 bucket은 hash entry chain의 **시작점(head pointer)**를 가리킴&lt;br /&gt;
&lt;br /&gt;
=== Bucket 개수 결정 ===&lt;br /&gt;
:::&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
Bucket 수 = 2의 거듭제곱 (예: 256, 512, 1024, 2048...)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:::* Oracle이 자동 계산:&lt;br /&gt;
:::- Build input의 cardinality&lt;br /&gt;
:::- 사용 가능한 메모리&lt;br /&gt;
:::- 통계 정보 (NUM_ROWS, NUM_DISTINCT)&lt;br /&gt;
&lt;br /&gt;
=== Bucket 선택 방식 ===&lt;br /&gt;
:::&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 의사 코드&lt;br /&gt;
hash_value = hash_function(join_key)&lt;br /&gt;
bucket_number = hash_value % bucket_count  # 또는 비트 마스킹&lt;br /&gt;
bucket[bucket_number]  # 해당 bucket 접근&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 해시 버킷의 저장 구조&lt;br /&gt;
&lt;br /&gt;
=== 해시 버킷에 저장되는 내용 ===&lt;br /&gt;
:::*Hash Bucket (Pointer Array)&lt;br /&gt;
:::* Hash Bucket은 Hash Entry의 주소(포인터)만 가지고 있습니다.&lt;br /&gt;
* 단순한 포인터 배열&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Bucket[0] → 0x00000000 (NULL, 비어있음)&lt;br /&gt;
Bucket[1] → 0x7F8A3C00 (Entry 시작 주소)&lt;br /&gt;
Bucket[2] → 0x00000000 (NULL, 비어있음)&lt;br /&gt;
Bucket[3] → 0x7F8A4D20 (Entry 시작 주소)&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
각 bucket = 8 bytes (포인터 크기)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Hash Entry ===&lt;br /&gt;
&lt;br /&gt;
==== Entry 구성 요소 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
+---------------------------+&lt;br /&gt;
| Hash Entry Structure      |&lt;br /&gt;
+---------------------------+&lt;br /&gt;
| 1. Hash Value (4-8 bytes) | ← 해시함수 결과값&lt;br /&gt;
| 2. Next Pointer (8 bytes) | ← 다음 entry 주소 (chaining)&lt;br /&gt;
| 3. Join Key Value(s)      | ← 실제 join 컬럼 원본값&lt;br /&gt;
| 4. Row Data               | ← SELECT에 필요한 컬럼들&lt;br /&gt;
+---------------------------+&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
:::* Hash Entry (Actual Data)&lt;br /&gt;
:::* 실제 데이터를 담는 구조체&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
메모리 주소: 0x7F8A3C00&lt;br /&gt;
+---------------------------+&lt;br /&gt;
| Hash Value: 1234          | 4-8 bytes&lt;br /&gt;
| Next Pointer: 0x7F8A3C40  | 8 bytes (다음 entry)&lt;br /&gt;
| Join Key: dept_id = 10    | 가변 크기&lt;br /&gt;
| Row Data: &#039;Sales&#039;, ...    | 가변 크기&lt;br /&gt;
+---------------------------+&lt;br /&gt;
&lt;br /&gt;
메모리 주소: 0x7F8A3C40&lt;br /&gt;
+---------------------------+&lt;br /&gt;
| Hash Value: 5678          |&lt;br /&gt;
| Next Pointer: NULL        |&lt;br /&gt;
| Join Key: dept_id = 20    |&lt;br /&gt;
| Row Data: &#039;Marketing&#039;,... |&lt;br /&gt;
+---------------------------+&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Entry 저장 정보 예시 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 쿼리 예시&lt;br /&gt;
SELECT e.emp_id, e.name, d.dept_name&lt;br /&gt;
  FROM employees e&lt;br /&gt;
     , departments d&lt;br /&gt;
 WHERE e.dept_id = d.dept_id;&lt;br /&gt;
&lt;br /&gt;
-- departments (작은 테이블) → Build Input&lt;br /&gt;
-- employees (큰 테이블) → Probe Input&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::::* Hash Entry 내용:&lt;br /&gt;
::::&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Entry for dept_id = 10:&lt;br /&gt;
- Hash Value: 2348756 (hash 함수 결과)&lt;br /&gt;
- Next Pointer: 0x7F8A3C (다음 entry 주소 또는 NULL)&lt;br /&gt;
- Join Key: dept_id = 10 (원본값)&lt;br /&gt;
- Row Data: dept_name = &#039;Sales&#039;, location = &#039;Seoul&#039;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### **Phase 1: Build Phase**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
1. 작은 테이블(Build Input) 읽기&lt;br /&gt;
   ↓&lt;br /&gt;
2. 각 row에 대해:&lt;br /&gt;
   hash_value = hash(join_key)&lt;br /&gt;
   bucket_num = hash_value % bucket_count&lt;br /&gt;
   ↓&lt;br /&gt;
3. Hash Entry 생성:&lt;br /&gt;
   - Hash value 저장&lt;br /&gt;
   - Join key 원본값 저장&lt;br /&gt;
   - 필요한 컬럼 데이터 저장&lt;br /&gt;
   ↓&lt;br /&gt;
4. 해당 bucket의 chain에 추가 (linked list)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**메모리 구조:**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
Bucket Array          Hash Entry Pool&lt;br /&gt;
[0] → NULL           &lt;br /&gt;
[1] → Entry_A ─────→ [Hash:1234, Key:10, Data:..., Next→Entry_B]&lt;br /&gt;
[2] → Entry_C                                              ↓&lt;br /&gt;
[3] → NULL                    [Hash:5678, Key:20, Data:..., Next:NULL]&lt;br /&gt;
...&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **Phase 2: Probe Phase**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
1. 큰 테이블(Probe Input) 읽기&lt;br /&gt;
   ↓&lt;br /&gt;
2. 각 row에 대해:&lt;br /&gt;
   hash_value = hash(join_key)&lt;br /&gt;
   bucket_num = hash_value % bucket_count&lt;br /&gt;
   ↓&lt;br /&gt;
3. 해당 bucket의 chain을 순회:&lt;br /&gt;
   entry = bucket[bucket_num]&lt;br /&gt;
   while entry != NULL:&lt;br /&gt;
       if entry.hash_value == hash_value:  # 1차 비교 (빠름)&lt;br /&gt;
           if entry.join_key == probe_key:  # 2차 비교 (정확)&lt;br /&gt;
               → JOIN 성공, 결과 반환&lt;br /&gt;
       entry = entry.next&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 5. Hash Collision 처리&lt;br /&gt;
&lt;br /&gt;
### **충돌 발생 시나리오**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
hash(dept_id=10) = 1234 → 1234 % 256 = 50 → Bucket #50&lt;br /&gt;
hash(dept_id=30) = 5678 → 5678 % 256 = 50 → Bucket #50  ← 충돌!&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **Chaining으로 해결**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
Bucket #50&lt;br /&gt;
   ↓&lt;br /&gt;
[Entry1: hash=1234, key=10, next→]&lt;br /&gt;
   ↓&lt;br /&gt;
[Entry2: hash=5678, key=30, next→]&lt;br /&gt;
   ↓&lt;br /&gt;
[Entry3: hash=9012, key=70, next=NULL]&lt;br /&gt;
&lt;br /&gt;
Probe 시: Chain을 순차 탐색하며 실제 key 값 비교&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 6. 메모리 관리&lt;br /&gt;
&lt;br /&gt;
### **저장 위치: PGA**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- 메모리 할당 우선순위&lt;br /&gt;
PGA_AGGREGATE_TARGET (자동 관리)&lt;br /&gt;
  └── Work Area&lt;br /&gt;
      └── Hash Area&lt;br /&gt;
          ├── Bucket Array (포인터 배열)&lt;br /&gt;
          └── Hash Entry Pool (실제 데이터)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **메모리 부족 시: Temp Spill**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
Optimal: 전체 hash table이 메모리에 fit&lt;br /&gt;
   ↓&lt;br /&gt;
OnePass: 일부 partition이 디스크로 (1회 읽기)&lt;br /&gt;
   ↓&lt;br /&gt;
MultiPass: 여러 partition이 디스크로 (여러 번 읽기) ← 성능 저하!&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**확인 방법:**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
SELECT sql_id, operation_type, &lt;br /&gt;
       estimated_optimal_size/1024/1024 as optimal_mb,&lt;br /&gt;
       estimated_onepass_size/1024/1024 as onepass_mb,&lt;br /&gt;
       last_memory_used/1024/1024 as used_mb,&lt;br /&gt;
       last_execution,&lt;br /&gt;
       CASE &lt;br /&gt;
         WHEN last_memory_used &amp;lt; estimated_onepass_size THEN &#039;MultiPass&#039;&lt;br /&gt;
         WHEN last_memory_used &amp;lt; estimated_optimal_size THEN &#039;OnePass&#039;&lt;br /&gt;
         ELSE &#039;Optimal&#039;&lt;br /&gt;
       END as execution_type&lt;br /&gt;
FROM v$sql_workarea&lt;br /&gt;
WHERE operation_type = &#039;HASH-JOIN&#039;&lt;br /&gt;
ORDER BY last_execution DESC;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 7. 성능 영향 요소&lt;br /&gt;
&lt;br /&gt;
### **좋은 경우 (O(1) lookup)**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
✓ 충분한 메모리 (Optimal execution)&lt;br /&gt;
✓ 적절한 bucket 수 (충돌 최소화)&lt;br /&gt;
✓ 균등한 hash 분포&lt;br /&gt;
✓ 작은 build input&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **나쁜 경우 (O(n) lookup)**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
✗ 메모리 부족 (MultiPass → 디스크 I/O)&lt;br /&gt;
✗ Bucket 수 부족 (긴 chain)&lt;br /&gt;
✗ 심한 데이터 skew (특정 bucket에 집중)&lt;br /&gt;
✗ 큰 build input&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 8. 실무 활용 예시&lt;br /&gt;
&lt;br /&gt;
### **Hash Join 모니터링**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- 현재 실행 중인 hash join 확인&lt;br /&gt;
SELECT s.sid, s.serial#, s.username,&lt;br /&gt;
       w.operation_type, w.policy,&lt;br /&gt;
       w.work_area_size/1024/1024 as workarea_mb,&lt;br /&gt;
       w.expected_size/1024/1024 as expected_mb&lt;br /&gt;
FROM v$sql_workarea_active w, v$session s&lt;br /&gt;
WHERE w.sid = s.sid&lt;br /&gt;
  AND w.operation_type = &#039;HASH-JOIN&#039;;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **통계 수집 (중요!)**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- Hash join 성능은 통계 정확도에 크게 의존&lt;br /&gt;
BEGIN&lt;br /&gt;
  DBMS_STATS.GATHER_TABLE_STATS(&lt;br /&gt;
    ownname =&amp;gt; &#039;SCOTT&#039;,&lt;br /&gt;
    tabname =&amp;gt; &#039;EMPLOYEES&#039;,&lt;br /&gt;
    estimate_percent =&amp;gt; DBMS_STATS.AUTO_SAMPLE_SIZE,&lt;br /&gt;
    method_opt =&amp;gt; &#039;FOR ALL COLUMNS SIZE AUTO&#039;&lt;br /&gt;
  );&lt;br /&gt;
END;&lt;br /&gt;
/&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **힌트 활용**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- Hash join 강제 + build input 지정&lt;br /&gt;
SELECT /*+ USE_HASH(e d) SWAP_JOIN_INPUTS(d) */ &lt;br /&gt;
  e.emp_id, d.dept_name&lt;br /&gt;
FROM employees e, departments d&lt;br /&gt;
WHERE e.dept_id = d.dept_id;&lt;br /&gt;
&lt;br /&gt;
-- SWAP_JOIN_INPUTS: departments를 build input으로 사용&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 9. 핵심 정리&lt;br /&gt;
&lt;br /&gt;
|구성요소          |역할    |저장 내용                           |&lt;br /&gt;
|--------------|------|--------------------------------|&lt;br /&gt;
|**Hash Table**|전체 구조 |PGA 메모리에 생성                     |&lt;br /&gt;
|**Bucket**    |논리적 슬롯|Entry chain의 head pointer       |&lt;br /&gt;
|**Hash Entry**|실제 데이터|Hash value + Join key + Row data|&lt;br /&gt;
|**Chain**     |충돌 해결 |Linked list로 같은 bucket의 entry 연결|&lt;br /&gt;
&lt;br /&gt;
**핵심 원리:**&lt;br /&gt;
&lt;br /&gt;
- Hash value로 **빠르게 bucket 찾기** (O(1))&lt;br /&gt;
- 원본 key 값으로 **정확한 매칭** (충돌 해결)&lt;br /&gt;
- Chaining으로 **여러 entry 관리**&lt;br /&gt;
- PGA 메모리 활용으로 **빠른 접근**&lt;br /&gt;
&lt;br /&gt;
DBA 업무 시 hash join 성능 문제가 있다면 통계 정보, PGA 메모리 설정, 그리고 build input 크기를 먼저 확인하시는 것이 좋습니다!​​​​​​​​​​​​​​​​&lt;br /&gt;
&lt;br /&gt;
=== 실제 메모리 구조 예시 ===&lt;br /&gt;
==== Build 단계 ====&lt;br /&gt;
* Build 단계: employees 테이블 (작은 테이블)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시 쿼리&lt;br /&gt;
SELECT e.emp_id, e.name, e.salary, d.dept_name&lt;br /&gt;
  FROM employees e  -- Build 테이블&lt;br /&gt;
  JOIN departments d  -- Probe 테이블&lt;br /&gt;
    ON e.dept_id = d.dept_id&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 단계에서 생성되는 해시 테이블:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
메모리 영역 (PGA - Work Area)&lt;br /&gt;
해시 테이블 버킷:&lt;br /&gt;
&lt;br /&gt;
Bucket[0] → NULL&lt;br /&gt;
&lt;br /&gt;
Bucket[1] → [Entry 1] → NULL&lt;br /&gt;
            ┌──────────────────────────────┐&lt;br /&gt;
            │ emp_id: 101     (조인 키)      │&lt;br /&gt;
            │ name: &#039;김철수&#039;   (일반 컬럼)     │&lt;br /&gt;
            │ salary: 5000000 (일반 컬럼)    │&lt;br /&gt;
            │ next: NULL      (체인 포인터)   │&lt;br /&gt;
            └──────────────────────────────┘&lt;br /&gt;
&lt;br /&gt;
Bucket[2] → [Entry 2] → [Entry 3] → NULL&lt;br /&gt;
            ┌──────────────────┐   ┌──────────────────┐&lt;br /&gt;
            │ emp_id: 202      │   │ emp_id: 302      │&lt;br /&gt;
            │ name: &#039;이영희&#039;     │    │ name: &#039;박민수&#039;    │&lt;br /&gt;
            │ salary: 6000000  │   │ salary: 5500000  │&lt;br /&gt;
            │ next: 0x3000     │   │ next: NULL       │&lt;br /&gt;
            └──────────────────┘   └──────────────────┘&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 실제 메모리 레이아웃&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
메모리 주소   | 내용&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x1000       | emp_id = 101&lt;br /&gt;
0x1004       | name = &amp;quot;김철수&amp;quot; (포인터 또는 인라인)&lt;br /&gt;
0x1008       | salary = 5000000&lt;br /&gt;
0x100C       | next = NULL&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x2000       | emp_id = 202&lt;br /&gt;
0x2004       | name = &amp;quot;이영희&amp;quot;&lt;br /&gt;
0x2008       | salary = 6000000&lt;br /&gt;
0x200C       | next = 0x3000  (다음 엔트리 주소)&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x3000       | emp_id = 302&lt;br /&gt;
0x3004       | name = &amp;quot;박민수&amp;quot;&lt;br /&gt;
0x3008       | salary = 5500000&lt;br /&gt;
0x300C       | next = NULL&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 과정 상세&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 의사 코드로 표현한 Build 단계&lt;br /&gt;
&lt;br /&gt;
hash_table = Array[BUCKET_SIZE]  # 버킷 배열 초기화&lt;br /&gt;
&lt;br /&gt;
for row in small_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 엔트리 생성 (실제 데이터 복사)&lt;br /&gt;
    entry = {&lt;br /&gt;
        &#039;join_key&#039;: row.join_key,&lt;br /&gt;
        &#039;column1&#039;: row.column1,&lt;br /&gt;
        &#039;column2&#039;: row.column2,&lt;br /&gt;
        &#039;next&#039;: NULL  # 체인 포인터&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    # 3. 버킷에 삽입 (체이닝 방식)&lt;br /&gt;
    if hash_table[bucket_index] is NULL:&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
    else:&lt;br /&gt;
        # 충돌 발생 - 체인의 맨 앞에 추가&lt;br /&gt;
        entry.next = hash_table[bucket_index]&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Probe 단계 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Probe 단계&lt;br /&gt;
&lt;br /&gt;
for probe_row in large_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(probe_row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 해당 버킷의 체인을 순회&lt;br /&gt;
    entry = hash_table[bucket_index]&lt;br /&gt;
    while entry is not NULL:&lt;br /&gt;
        # 3. 실제 조인 키 비교 (중요!)&lt;br /&gt;
        if entry.join_key == probe_row.join_key:&lt;br /&gt;
            # 조인 성공 - 결과 생성&lt;br /&gt;
            output(entry.data + probe_row.data)&lt;br /&gt;
        &lt;br /&gt;
        entry = entry.next  # 체인의 다음 엔트리로&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== [[hash join Probe 단계 처리과정|Probe 단계]] 에서도 해시버킷이 사용되나? =====&lt;br /&gt;
답변 : 아니요&lt;br /&gt;
&lt;br /&gt;
=== 왜 이렇게 설계 되었을까? ===&lt;br /&gt;
# 디스크 기반 접근 (주소만 저장한다면):&lt;br /&gt;
## 조인 매칭마다 디스크 I/O 발생&lt;br /&gt;
## 성능: 매우 느림&lt;br /&gt;
# 메모리 기반 접근 (실제 데이터 저장):&lt;br /&gt;
## 모든 매칭이 메모리에서 처리&lt;br /&gt;
## 성능: 매우 빠름 (Hash Join의 장점)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 결론 : 해시 버킷 구조의 핵심 포인트 ===&lt;br /&gt;
# 데이터 블럭의 주소만 저장 하나요? &lt;br /&gt;
#: NO → 실제 행 데이터를 메모리에 복사하여 저장&lt;br /&gt;
# 왜 데이터를 저장하나요? &lt;br /&gt;
#:Probe 단계에서 디스크 I/O 없이 빠르게 접근하기 위함&lt;br /&gt;
# DB의 어디 영역에 저장하나요? &lt;br /&gt;
#: 저장 위치: PGA의 Work Area (메모리)&lt;br /&gt;
# 해시충돌시 처리방법은?&lt;br /&gt;
#: 체이닝(Chaining) 방식 - 링크드 리스트&lt;br /&gt;
# 메모리 부족 시 에는?&lt;br /&gt;
#: 디스크 임시 테이블스페이스 사용 (성능 저하)&lt;br /&gt;
&lt;br /&gt;
------&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
## 구조 비교&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
## C 언어 스타일 구조체로 설명&lt;br /&gt;
&lt;br /&gt;
```c&lt;br /&gt;
// Hash Bucket (포인터 배열)&lt;br /&gt;
typedef struct {&lt;br /&gt;
    HashEntry* head;  // 8 bytes (포인터만!)&lt;br /&gt;
} HashBucket;&lt;br /&gt;
&lt;br /&gt;
// Hash Entry (실제 데이터)&lt;br /&gt;
typedef struct HashEntry {&lt;br /&gt;
    uint32_t hash_value;      // 4 bytes&lt;br /&gt;
    struct HashEntry* next;   // 8 bytes (다음 entry 포인터)&lt;br /&gt;
    void* join_key;           // 가변 크기 (실제 join key 값)&lt;br /&gt;
    void* row_data;           // 가변 크기 (실제 row 데이터)&lt;br /&gt;
} HashEntry;&lt;br /&gt;
&lt;br /&gt;
// Hash Table&lt;br /&gt;
typedef struct {&lt;br /&gt;
    uint32_t bucket_count;    // bucket 개수&lt;br /&gt;
    HashBucket* buckets;      // bucket 배열 (포인터만!)&lt;br /&gt;
    HashEntry* entry_pool;    // entry 저장 공간 (실제 데이터!)&lt;br /&gt;
} HashTable;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 메모리 크기 비교&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
예시: 1024개 bucket, 10000개 entry&lt;br /&gt;
&lt;br /&gt;
Bucket Array:&lt;br /&gt;
= 1024 buckets × 8 bytes (포인터)&lt;br /&gt;
= 8 KB (매우 작음!)&lt;br /&gt;
&lt;br /&gt;
Hash Entry Pool:&lt;br /&gt;
= 10000 entries × 평균 100 bytes (데이터 크기)&lt;br /&gt;
= 1 MB (실제 데이터 공간이 큼!)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 동작 과정 상세&lt;br /&gt;
&lt;br /&gt;
### **1. Build Phase - Entry 추가**&lt;br /&gt;
&lt;br /&gt;
```python&lt;br /&gt;
# 의사 코드&lt;br /&gt;
def insert_entry(join_key, row_data):&lt;br /&gt;
    # 1. Hash 계산&lt;br /&gt;
    hash_value = hash_function(join_key)&lt;br /&gt;
    bucket_index = hash_value % bucket_count&lt;br /&gt;
    &lt;br /&gt;
    # 2. 새 Entry 생성 (실제 데이터 저장)&lt;br /&gt;
    new_entry = HashEntry()&lt;br /&gt;
    new_entry.hash_value = hash_value&lt;br /&gt;
    new_entry.join_key = join_key      # 실제 값&lt;br /&gt;
    new_entry.row_data = row_data      # 실제 데이터&lt;br /&gt;
    new_entry.next = NULL&lt;br /&gt;
    &lt;br /&gt;
    # 3. Bucket의 chain 맨 앞에 추가&lt;br /&gt;
    new_entry.next = buckets[bucket_index].head  # 기존 head&lt;br /&gt;
    buckets[bucket_index].head = new_entry       # 새 entry를 head로&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **2. Probe Phase - Entry 검색**&lt;br /&gt;
&lt;br /&gt;
```python&lt;br /&gt;
def probe_entry(probe_key):&lt;br /&gt;
    # 1. Hash 계산으로 bucket 찾기&lt;br /&gt;
    hash_value = hash_function(probe_key)&lt;br /&gt;
    bucket_index = hash_value % bucket_count&lt;br /&gt;
    &lt;br /&gt;
    # 2. Bucket에서 시작 포인터 가져오기&lt;br /&gt;
    current_entry = buckets[bucket_index].head  # 포인터!&lt;br /&gt;
    &lt;br /&gt;
    # 3. Chain 순회 (실제 데이터 비교)&lt;br /&gt;
    while current_entry != NULL:&lt;br /&gt;
        if current_entry.hash_value == hash_value:  # 1차 필터&lt;br /&gt;
            if current_entry.join_key == probe_key:  # 2차 비교 (실제 값!)&lt;br /&gt;
                return current_entry.row_data  # 매칭 성공&lt;br /&gt;
        current_entry = current_entry.next  # 다음 entry로&lt;br /&gt;
    &lt;br /&gt;
    return NULL  # 매칭 실패&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 실제 예시&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- 테이블&lt;br /&gt;
departments: dept_id(10, 20, 30)&lt;br /&gt;
&lt;br /&gt;
-- Hash Table 생성 (bucket_count = 4)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**Build 후 상태:**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
Bucket Array (포인터만):&lt;br /&gt;
[0] → NULL&lt;br /&gt;
[1] → 0xA000 (dept_id=10의 entry 주소)&lt;br /&gt;
[2] → 0xB000 (dept_id=20의 entry 주소)&lt;br /&gt;
[3] → 0xC000 (dept_id=30의 entry 주소)&lt;br /&gt;
&lt;br /&gt;
Entry Pool (실제 데이터):&lt;br /&gt;
주소 0xA000: [hash=1, next=NULL, key=10, data=&#039;Sales&#039;]&lt;br /&gt;
주소 0xB000: [hash=2, next=NULL, key=20, data=&#039;HR&#039;]&lt;br /&gt;
주소 0xC000: [hash=3, next=NULL, key=30, data=&#039;IT&#039;]&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**Collision 발생 시:**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
dept_id=10: hash % 4 = 1 → Bucket[1]&lt;br /&gt;
dept_id=50: hash % 4 = 1 → Bucket[1]  (충돌!)&lt;br /&gt;
&lt;br /&gt;
Bucket Array:&lt;br /&gt;
[1] → 0xA000  (첫 번째 entry 주소)&lt;br /&gt;
&lt;br /&gt;
Entry Pool:&lt;br /&gt;
주소 0xA000: [hash=X, next=0xD000, key=10, data=&#039;Sales&#039;]&lt;br /&gt;
                              ↓&lt;br /&gt;
주소 0xD000: [hash=Y, next=NULL, key=50, data=&#039;Finance&#039;]&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 핵심 정리&lt;br /&gt;
&lt;br /&gt;
|항목        |Hash Bucket  |Hash Entry        |&lt;br /&gt;
|----------|-------------|------------------|&lt;br /&gt;
|**역할**    |색인 (Index)   |실제 데이터 저장소        |&lt;br /&gt;
|**저장 내용** |포인터 (8 bytes)|Hash값 + Key + Data|&lt;br /&gt;
|**메모리 크기**|매우 작음        |대부분의 메모리 사용       |&lt;br /&gt;
|**비유**    |책의 목차        |책의 본문             |&lt;br /&gt;
&lt;br /&gt;
**쉽게 비유하면:**&lt;br /&gt;
&lt;br /&gt;
- **Bucket** = 아파트 동 번호판 (몇 호인지 주소만 표시)&lt;br /&gt;
- **Entry** = 실제 집 (사람들과 가구가 있는 공간)&lt;br /&gt;
&lt;br /&gt;
Oracle DBA로서 성능 튜닝 시:&lt;br /&gt;
&lt;br /&gt;
- Bucket 수는 메모리에 큰 영향 없음 (포인터만)&lt;br /&gt;
- Entry Pool 크기가 실제 메모리 사용량 결정&lt;br /&gt;
- `v$sql_workarea`에서 보는 메모리는 대부분 Entry Pool 크기입니다!&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1625</id>
		<title>해시 버킷의 저장구조</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1625"/>
		<updated>2025-10-23T04:08:14Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Oracle Hash Join의 해시테이블 내부 구조==&lt;br /&gt;
=== 전체 구조 개요 ===&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
Hash Table (PGA Memory)&lt;br /&gt;
├── Bucket Array (해시 테이블의 배열)&lt;br /&gt;
│   ├── Bucket #0 → Hash Entry Chain&lt;br /&gt;
│   ├── Bucket #1 → Hash Entry Chain&lt;br /&gt;
│   ├── Bucket #2 → Hash Entry Chain&lt;br /&gt;
│   ├── ...&lt;br /&gt;
│   └── Bucket #N → Hash Entry Chain&lt;br /&gt;
│&lt;br /&gt;
└── Hash Entry Pool (실제 데이터 저장 영역)&lt;br /&gt;
    ├── Entry1 [hash, key, data, next*]&lt;br /&gt;
    ├── Entry2 [hash, key, data, next*]&lt;br /&gt;
    └── ...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 해시 버킷 (Hash Bucket) ===&lt;br /&gt;
:::* Bucket이란?&lt;br /&gt;
:::- Hash Table을 구성하는 **논리적 슬롯(Slot)**&lt;br /&gt;
:::- 포인터 배열로 구현됨&lt;br /&gt;
:::- 각 bucket은 hash entry chain의 **시작점(head pointer)**를 가리킴&lt;br /&gt;
&lt;br /&gt;
=== Bucket 개수 결정 ===&lt;br /&gt;
:::&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
Bucket 수 = 2의 거듭제곱 (예: 256, 512, 1024, 2048...)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:::* Oracle이 자동 계산:&lt;br /&gt;
:::- Build input의 cardinality&lt;br /&gt;
:::- 사용 가능한 메모리&lt;br /&gt;
:::- 통계 정보 (NUM_ROWS, NUM_DISTINCT)&lt;br /&gt;
&lt;br /&gt;
=== Bucket 선택 방식 ===&lt;br /&gt;
:::&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 의사 코드&lt;br /&gt;
hash_value = hash_function(join_key)&lt;br /&gt;
bucket_number = hash_value % bucket_count  # 또는 비트 마스킹&lt;br /&gt;
bucket[bucket_number]  # 해당 bucket 접근&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 해시 버킷의 저장 구조&lt;br /&gt;
&lt;br /&gt;
=== 해시 버킷에 저장되는 내용 ===&lt;br /&gt;
&lt;br /&gt;
:::* Hash Bucket은 Hash Entry의 주소(포인터)만 가지고 있습니다.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 해시 테이블 구조:&lt;br /&gt;
#[해시 버킷 배열]&lt;br /&gt;
Bucket 0  → [emp_id=101, name=&#039;김철수&#039;, dept_id=10] → [emp_id=501, name=&#039;이영희&#039;, dept_id=10] → NULL&lt;br /&gt;
Bucket 1  → [emp_id=202, name=&#039;박민수&#039;, dept_id=20] → NULL&lt;br /&gt;
Bucket 2  → NULL&lt;br /&gt;
Bucket 3  → [emp_id=303, name=&#039;정수진&#039;, dept_id=30] → [emp_id=403, name=&#039;최지원&#039;, dept_id=30] → NULL&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Hash Entry ===&lt;br /&gt;
&lt;br /&gt;
==== Entry 구성 요소 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
+---------------------------+&lt;br /&gt;
| Hash Entry Structure      |&lt;br /&gt;
+---------------------------+&lt;br /&gt;
| 1. Hash Value (4-8 bytes) | ← 해시함수 결과값&lt;br /&gt;
| 2. Next Pointer (8 bytes) | ← 다음 entry 주소 (chaining)&lt;br /&gt;
| 3. Join Key Value(s)      | ← 실제 join 컬럼 원본값&lt;br /&gt;
| 4. Row Data               | ← SELECT에 필요한 컬럼들&lt;br /&gt;
+---------------------------+&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Entry 저장 정보 예시 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 쿼리 예시&lt;br /&gt;
SELECT e.emp_id, e.name, d.dept_name&lt;br /&gt;
  FROM employees e&lt;br /&gt;
     , departments d&lt;br /&gt;
 WHERE e.dept_id = d.dept_id;&lt;br /&gt;
&lt;br /&gt;
-- departments (작은 테이블) → Build Input&lt;br /&gt;
-- employees (큰 테이블) → Probe Input&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::::* Hash Entry 내용:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Entry for dept_id = 10:&lt;br /&gt;
- Hash Value: 2348756 (hash 함수 결과)&lt;br /&gt;
- Next Pointer: 0x7F8A3C (다음 entry 주소 또는 NULL)&lt;br /&gt;
- Join Key: dept_id = 10 (원본값)&lt;br /&gt;
- Row Data: dept_name = &#039;Sales&#039;, location = &#039;Seoul&#039;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### **Phase 1: Build Phase**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
1. 작은 테이블(Build Input) 읽기&lt;br /&gt;
   ↓&lt;br /&gt;
2. 각 row에 대해:&lt;br /&gt;
   hash_value = hash(join_key)&lt;br /&gt;
   bucket_num = hash_value % bucket_count&lt;br /&gt;
   ↓&lt;br /&gt;
3. Hash Entry 생성:&lt;br /&gt;
   - Hash value 저장&lt;br /&gt;
   - Join key 원본값 저장&lt;br /&gt;
   - 필요한 컬럼 데이터 저장&lt;br /&gt;
   ↓&lt;br /&gt;
4. 해당 bucket의 chain에 추가 (linked list)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**메모리 구조:**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
Bucket Array          Hash Entry Pool&lt;br /&gt;
[0] → NULL           &lt;br /&gt;
[1] → Entry_A ─────→ [Hash:1234, Key:10, Data:..., Next→Entry_B]&lt;br /&gt;
[2] → Entry_C                                              ↓&lt;br /&gt;
[3] → NULL                    [Hash:5678, Key:20, Data:..., Next:NULL]&lt;br /&gt;
...&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **Phase 2: Probe Phase**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
1. 큰 테이블(Probe Input) 읽기&lt;br /&gt;
   ↓&lt;br /&gt;
2. 각 row에 대해:&lt;br /&gt;
   hash_value = hash(join_key)&lt;br /&gt;
   bucket_num = hash_value % bucket_count&lt;br /&gt;
   ↓&lt;br /&gt;
3. 해당 bucket의 chain을 순회:&lt;br /&gt;
   entry = bucket[bucket_num]&lt;br /&gt;
   while entry != NULL:&lt;br /&gt;
       if entry.hash_value == hash_value:  # 1차 비교 (빠름)&lt;br /&gt;
           if entry.join_key == probe_key:  # 2차 비교 (정확)&lt;br /&gt;
               → JOIN 성공, 결과 반환&lt;br /&gt;
       entry = entry.next&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 5. Hash Collision 처리&lt;br /&gt;
&lt;br /&gt;
### **충돌 발생 시나리오**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
hash(dept_id=10) = 1234 → 1234 % 256 = 50 → Bucket #50&lt;br /&gt;
hash(dept_id=30) = 5678 → 5678 % 256 = 50 → Bucket #50  ← 충돌!&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **Chaining으로 해결**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
Bucket #50&lt;br /&gt;
   ↓&lt;br /&gt;
[Entry1: hash=1234, key=10, next→]&lt;br /&gt;
   ↓&lt;br /&gt;
[Entry2: hash=5678, key=30, next→]&lt;br /&gt;
   ↓&lt;br /&gt;
[Entry3: hash=9012, key=70, next=NULL]&lt;br /&gt;
&lt;br /&gt;
Probe 시: Chain을 순차 탐색하며 실제 key 값 비교&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 6. 메모리 관리&lt;br /&gt;
&lt;br /&gt;
### **저장 위치: PGA**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- 메모리 할당 우선순위&lt;br /&gt;
PGA_AGGREGATE_TARGET (자동 관리)&lt;br /&gt;
  └── Work Area&lt;br /&gt;
      └── Hash Area&lt;br /&gt;
          ├── Bucket Array (포인터 배열)&lt;br /&gt;
          └── Hash Entry Pool (실제 데이터)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **메모리 부족 시: Temp Spill**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
Optimal: 전체 hash table이 메모리에 fit&lt;br /&gt;
   ↓&lt;br /&gt;
OnePass: 일부 partition이 디스크로 (1회 읽기)&lt;br /&gt;
   ↓&lt;br /&gt;
MultiPass: 여러 partition이 디스크로 (여러 번 읽기) ← 성능 저하!&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**확인 방법:**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
SELECT sql_id, operation_type, &lt;br /&gt;
       estimated_optimal_size/1024/1024 as optimal_mb,&lt;br /&gt;
       estimated_onepass_size/1024/1024 as onepass_mb,&lt;br /&gt;
       last_memory_used/1024/1024 as used_mb,&lt;br /&gt;
       last_execution,&lt;br /&gt;
       CASE &lt;br /&gt;
         WHEN last_memory_used &amp;lt; estimated_onepass_size THEN &#039;MultiPass&#039;&lt;br /&gt;
         WHEN last_memory_used &amp;lt; estimated_optimal_size THEN &#039;OnePass&#039;&lt;br /&gt;
         ELSE &#039;Optimal&#039;&lt;br /&gt;
       END as execution_type&lt;br /&gt;
FROM v$sql_workarea&lt;br /&gt;
WHERE operation_type = &#039;HASH-JOIN&#039;&lt;br /&gt;
ORDER BY last_execution DESC;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 7. 성능 영향 요소&lt;br /&gt;
&lt;br /&gt;
### **좋은 경우 (O(1) lookup)**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
✓ 충분한 메모리 (Optimal execution)&lt;br /&gt;
✓ 적절한 bucket 수 (충돌 최소화)&lt;br /&gt;
✓ 균등한 hash 분포&lt;br /&gt;
✓ 작은 build input&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **나쁜 경우 (O(n) lookup)**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
✗ 메모리 부족 (MultiPass → 디스크 I/O)&lt;br /&gt;
✗ Bucket 수 부족 (긴 chain)&lt;br /&gt;
✗ 심한 데이터 skew (특정 bucket에 집중)&lt;br /&gt;
✗ 큰 build input&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 8. 실무 활용 예시&lt;br /&gt;
&lt;br /&gt;
### **Hash Join 모니터링**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- 현재 실행 중인 hash join 확인&lt;br /&gt;
SELECT s.sid, s.serial#, s.username,&lt;br /&gt;
       w.operation_type, w.policy,&lt;br /&gt;
       w.work_area_size/1024/1024 as workarea_mb,&lt;br /&gt;
       w.expected_size/1024/1024 as expected_mb&lt;br /&gt;
FROM v$sql_workarea_active w, v$session s&lt;br /&gt;
WHERE w.sid = s.sid&lt;br /&gt;
  AND w.operation_type = &#039;HASH-JOIN&#039;;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **통계 수집 (중요!)**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- Hash join 성능은 통계 정확도에 크게 의존&lt;br /&gt;
BEGIN&lt;br /&gt;
  DBMS_STATS.GATHER_TABLE_STATS(&lt;br /&gt;
    ownname =&amp;gt; &#039;SCOTT&#039;,&lt;br /&gt;
    tabname =&amp;gt; &#039;EMPLOYEES&#039;,&lt;br /&gt;
    estimate_percent =&amp;gt; DBMS_STATS.AUTO_SAMPLE_SIZE,&lt;br /&gt;
    method_opt =&amp;gt; &#039;FOR ALL COLUMNS SIZE AUTO&#039;&lt;br /&gt;
  );&lt;br /&gt;
END;&lt;br /&gt;
/&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **힌트 활용**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- Hash join 강제 + build input 지정&lt;br /&gt;
SELECT /*+ USE_HASH(e d) SWAP_JOIN_INPUTS(d) */ &lt;br /&gt;
  e.emp_id, d.dept_name&lt;br /&gt;
FROM employees e, departments d&lt;br /&gt;
WHERE e.dept_id = d.dept_id;&lt;br /&gt;
&lt;br /&gt;
-- SWAP_JOIN_INPUTS: departments를 build input으로 사용&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 9. 핵심 정리&lt;br /&gt;
&lt;br /&gt;
|구성요소          |역할    |저장 내용                           |&lt;br /&gt;
|--------------|------|--------------------------------|&lt;br /&gt;
|**Hash Table**|전체 구조 |PGA 메모리에 생성                     |&lt;br /&gt;
|**Bucket**    |논리적 슬롯|Entry chain의 head pointer       |&lt;br /&gt;
|**Hash Entry**|실제 데이터|Hash value + Join key + Row data|&lt;br /&gt;
|**Chain**     |충돌 해결 |Linked list로 같은 bucket의 entry 연결|&lt;br /&gt;
&lt;br /&gt;
**핵심 원리:**&lt;br /&gt;
&lt;br /&gt;
- Hash value로 **빠르게 bucket 찾기** (O(1))&lt;br /&gt;
- 원본 key 값으로 **정확한 매칭** (충돌 해결)&lt;br /&gt;
- Chaining으로 **여러 entry 관리**&lt;br /&gt;
- PGA 메모리 활용으로 **빠른 접근**&lt;br /&gt;
&lt;br /&gt;
DBA 업무 시 hash join 성능 문제가 있다면 통계 정보, PGA 메모리 설정, 그리고 build input 크기를 먼저 확인하시는 것이 좋습니다!​​​​​​​​​​​​​​​​&lt;br /&gt;
&lt;br /&gt;
=== 실제 메모리 구조 예시 ===&lt;br /&gt;
==== Build 단계 ====&lt;br /&gt;
* Build 단계: employees 테이블 (작은 테이블)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시 쿼리&lt;br /&gt;
SELECT e.emp_id, e.name, e.salary, d.dept_name&lt;br /&gt;
  FROM employees e  -- Build 테이블&lt;br /&gt;
  JOIN departments d  -- Probe 테이블&lt;br /&gt;
    ON e.dept_id = d.dept_id&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 단계에서 생성되는 해시 테이블:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
메모리 영역 (PGA - Work Area)&lt;br /&gt;
해시 테이블 버킷:&lt;br /&gt;
&lt;br /&gt;
Bucket[0] → NULL&lt;br /&gt;
&lt;br /&gt;
Bucket[1] → [Entry 1] → NULL&lt;br /&gt;
            ┌──────────────────────────────┐&lt;br /&gt;
            │ emp_id: 101     (조인 키)      │&lt;br /&gt;
            │ name: &#039;김철수&#039;   (일반 컬럼)     │&lt;br /&gt;
            │ salary: 5000000 (일반 컬럼)    │&lt;br /&gt;
            │ next: NULL      (체인 포인터)   │&lt;br /&gt;
            └──────────────────────────────┘&lt;br /&gt;
&lt;br /&gt;
Bucket[2] → [Entry 2] → [Entry 3] → NULL&lt;br /&gt;
            ┌──────────────────┐   ┌──────────────────┐&lt;br /&gt;
            │ emp_id: 202      │   │ emp_id: 302      │&lt;br /&gt;
            │ name: &#039;이영희&#039;     │    │ name: &#039;박민수&#039;    │&lt;br /&gt;
            │ salary: 6000000  │   │ salary: 5500000  │&lt;br /&gt;
            │ next: 0x3000     │   │ next: NULL       │&lt;br /&gt;
            └──────────────────┘   └──────────────────┘&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 실제 메모리 레이아웃&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
메모리 주소   | 내용&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x1000       | emp_id = 101&lt;br /&gt;
0x1004       | name = &amp;quot;김철수&amp;quot; (포인터 또는 인라인)&lt;br /&gt;
0x1008       | salary = 5000000&lt;br /&gt;
0x100C       | next = NULL&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x2000       | emp_id = 202&lt;br /&gt;
0x2004       | name = &amp;quot;이영희&amp;quot;&lt;br /&gt;
0x2008       | salary = 6000000&lt;br /&gt;
0x200C       | next = 0x3000  (다음 엔트리 주소)&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x3000       | emp_id = 302&lt;br /&gt;
0x3004       | name = &amp;quot;박민수&amp;quot;&lt;br /&gt;
0x3008       | salary = 5500000&lt;br /&gt;
0x300C       | next = NULL&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 과정 상세&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 의사 코드로 표현한 Build 단계&lt;br /&gt;
&lt;br /&gt;
hash_table = Array[BUCKET_SIZE]  # 버킷 배열 초기화&lt;br /&gt;
&lt;br /&gt;
for row in small_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 엔트리 생성 (실제 데이터 복사)&lt;br /&gt;
    entry = {&lt;br /&gt;
        &#039;join_key&#039;: row.join_key,&lt;br /&gt;
        &#039;column1&#039;: row.column1,&lt;br /&gt;
        &#039;column2&#039;: row.column2,&lt;br /&gt;
        &#039;next&#039;: NULL  # 체인 포인터&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    # 3. 버킷에 삽입 (체이닝 방식)&lt;br /&gt;
    if hash_table[bucket_index] is NULL:&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
    else:&lt;br /&gt;
        # 충돌 발생 - 체인의 맨 앞에 추가&lt;br /&gt;
        entry.next = hash_table[bucket_index]&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Probe 단계 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Probe 단계&lt;br /&gt;
&lt;br /&gt;
for probe_row in large_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(probe_row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 해당 버킷의 체인을 순회&lt;br /&gt;
    entry = hash_table[bucket_index]&lt;br /&gt;
    while entry is not NULL:&lt;br /&gt;
        # 3. 실제 조인 키 비교 (중요!)&lt;br /&gt;
        if entry.join_key == probe_row.join_key:&lt;br /&gt;
            # 조인 성공 - 결과 생성&lt;br /&gt;
            output(entry.data + probe_row.data)&lt;br /&gt;
        &lt;br /&gt;
        entry = entry.next  # 체인의 다음 엔트리로&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== [[hash join Probe 단계 처리과정|Probe 단계]] 에서도 해시버킷이 사용되나? =====&lt;br /&gt;
답변 : 아니요&lt;br /&gt;
&lt;br /&gt;
=== 왜 이렇게 설계 되었을까? ===&lt;br /&gt;
# 디스크 기반 접근 (주소만 저장한다면):&lt;br /&gt;
## 조인 매칭마다 디스크 I/O 발생&lt;br /&gt;
## 성능: 매우 느림&lt;br /&gt;
# 메모리 기반 접근 (실제 데이터 저장):&lt;br /&gt;
## 모든 매칭이 메모리에서 처리&lt;br /&gt;
## 성능: 매우 빠름 (Hash Join의 장점)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 결론 : 해시 버킷 구조의 핵심 포인트 ===&lt;br /&gt;
# 데이터 블럭의 주소만 저장 하나요? &lt;br /&gt;
#: NO → 실제 행 데이터를 메모리에 복사하여 저장&lt;br /&gt;
# 왜 데이터를 저장하나요? &lt;br /&gt;
#:Probe 단계에서 디스크 I/O 없이 빠르게 접근하기 위함&lt;br /&gt;
# DB의 어디 영역에 저장하나요? &lt;br /&gt;
#: 저장 위치: PGA의 Work Area (메모리)&lt;br /&gt;
# 해시충돌시 처리방법은?&lt;br /&gt;
#: 체이닝(Chaining) 방식 - 링크드 리스트&lt;br /&gt;
# 메모리 부족 시 에는?&lt;br /&gt;
#: 디스크 임시 테이블스페이스 사용 (성능 저하)&lt;br /&gt;
&lt;br /&gt;
* 따라서 Hash Join은 **작은 테이블 전체를 메모리에 로드**하는 것이 핵심입니다!&lt;br /&gt;
&lt;br /&gt;
**Hash Bucket은 실제 데이터를 가지고 있지 않습니다!**&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
## 구조 비교&lt;br /&gt;
&lt;br /&gt;
### **Hash Bucket (Pointer Array)**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
단순한 포인터 배열&lt;br /&gt;
&lt;br /&gt;
Bucket[0] → 0x00000000 (NULL, 비어있음)&lt;br /&gt;
Bucket[1] → 0x7F8A3C00 (Entry 시작 주소)&lt;br /&gt;
Bucket[2] → 0x00000000 (NULL, 비어있음)&lt;br /&gt;
Bucket[3] → 0x7F8A4D20 (Entry 시작 주소)&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
각 bucket = 8 bytes (포인터 크기)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **Hash Entry (Actual Data)**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
실제 데이터를 담는 구조체&lt;br /&gt;
&lt;br /&gt;
메모리 주소: 0x7F8A3C00&lt;br /&gt;
+---------------------------+&lt;br /&gt;
| Hash Value: 1234          | 4-8 bytes&lt;br /&gt;
| Next Pointer: 0x7F8A3C40  | 8 bytes (다음 entry)&lt;br /&gt;
| Join Key: dept_id = 10    | 가변 크기&lt;br /&gt;
| Row Data: &#039;Sales&#039;, ...    | 가변 크기&lt;br /&gt;
+---------------------------+&lt;br /&gt;
&lt;br /&gt;
메모리 주소: 0x7F8A3C40&lt;br /&gt;
+---------------------------+&lt;br /&gt;
| Hash Value: 5678          |&lt;br /&gt;
| Next Pointer: NULL        |&lt;br /&gt;
| Join Key: dept_id = 20    |&lt;br /&gt;
| Row Data: &#039;Marketing&#039;,... |&lt;br /&gt;
+---------------------------+&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 시각적 구조&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
┌─────────────────────────────────────────────────────────┐&lt;br /&gt;
│  Hash Table (PGA Memory)                                │&lt;br /&gt;
├─────────────────────────────────────────────────────────┤&lt;br /&gt;
│                                                          │&lt;br /&gt;
│  [Bucket Array - 포인터만 저장]                          │&lt;br /&gt;
│  ┌──────┬──────────┐                                    │&lt;br /&gt;
│  │ #0   │ NULL     │                                    │&lt;br /&gt;
│  ├──────┼──────────┤                                    │&lt;br /&gt;
│  │ #1   │ 0x1000 ──┼──┐                                 │&lt;br /&gt;
│  ├──────┼──────────┤  │                                 │&lt;br /&gt;
│  │ #2   │ NULL     │  │                                 │&lt;br /&gt;
│  ├──────┼──────────┤  │                                 │&lt;br /&gt;
│  │ #3   │ 0x2000 ──┼──┼──┐                              │&lt;br /&gt;
│  └──────┴──────────┘  │  │                              │&lt;br /&gt;
│                        │  │                              │&lt;br /&gt;
│  [Hash Entry Pool - 실제 데이터 저장]                    │&lt;br /&gt;
│                        │  │                              │&lt;br /&gt;
│  ┌──────────────────┐ │  │                              │&lt;br /&gt;
│  │ 0x1000 주소      │←┘  │                              │&lt;br /&gt;
│  ├──────────────────┤    │                              │&lt;br /&gt;
│  │ Hash: 1234       │    │                              │&lt;br /&gt;
│  │ Next: 0x1100 ────┼──┐ │                              │&lt;br /&gt;
│  │ Key: 10          │  │ │                              │&lt;br /&gt;
│  │ Data: &#039;Sales&#039;    │  │ │                              │&lt;br /&gt;
│  └──────────────────┘  │ │                              │&lt;br /&gt;
│                         │ │                              │&lt;br /&gt;
│  ┌──────────────────┐  │ │                              │&lt;br /&gt;
│  │ 0x1100 주소      │←─┘ │                              │&lt;br /&gt;
│  ├──────────────────┤    │                              │&lt;br /&gt;
│  │ Hash: 5678       │    │                              │&lt;br /&gt;
│  │ Next: NULL       │    │                              │&lt;br /&gt;
│  │ Key: 30          │    │                              │&lt;br /&gt;
│  │ Data: &#039;IT&#039;       │    │                              │&lt;br /&gt;
│  └──────────────────┘    │                              │&lt;br /&gt;
│                           │                              │&lt;br /&gt;
│  ┌──────────────────┐    │                              │&lt;br /&gt;
│  │ 0x2000 주소      │←───┘                              │&lt;br /&gt;
│  ├──────────────────┤                                   │&lt;br /&gt;
│  │ Hash: 9012       │                                   │&lt;br /&gt;
│  │ Next: NULL       │                                   │&lt;br /&gt;
│  │ Key: 20          │                                   │&lt;br /&gt;
│  │ Data: &#039;HR&#039;       │                                   │&lt;br /&gt;
│  └──────────────────┘                                   │&lt;br /&gt;
│                                                          │&lt;br /&gt;
└─────────────────────────────────────────────────────────┘&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## C 언어 스타일 구조체로 설명&lt;br /&gt;
&lt;br /&gt;
```c&lt;br /&gt;
// Hash Bucket (포인터 배열)&lt;br /&gt;
typedef struct {&lt;br /&gt;
    HashEntry* head;  // 8 bytes (포인터만!)&lt;br /&gt;
} HashBucket;&lt;br /&gt;
&lt;br /&gt;
// Hash Entry (실제 데이터)&lt;br /&gt;
typedef struct HashEntry {&lt;br /&gt;
    uint32_t hash_value;      // 4 bytes&lt;br /&gt;
    struct HashEntry* next;   // 8 bytes (다음 entry 포인터)&lt;br /&gt;
    void* join_key;           // 가변 크기 (실제 join key 값)&lt;br /&gt;
    void* row_data;           // 가변 크기 (실제 row 데이터)&lt;br /&gt;
} HashEntry;&lt;br /&gt;
&lt;br /&gt;
// Hash Table&lt;br /&gt;
typedef struct {&lt;br /&gt;
    uint32_t bucket_count;    // bucket 개수&lt;br /&gt;
    HashBucket* buckets;      // bucket 배열 (포인터만!)&lt;br /&gt;
    HashEntry* entry_pool;    // entry 저장 공간 (실제 데이터!)&lt;br /&gt;
} HashTable;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 메모리 크기 비교&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
예시: 1024개 bucket, 10000개 entry&lt;br /&gt;
&lt;br /&gt;
Bucket Array:&lt;br /&gt;
= 1024 buckets × 8 bytes (포인터)&lt;br /&gt;
= 8 KB (매우 작음!)&lt;br /&gt;
&lt;br /&gt;
Hash Entry Pool:&lt;br /&gt;
= 10000 entries × 평균 100 bytes (데이터 크기)&lt;br /&gt;
= 1 MB (실제 데이터 공간이 큼!)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 동작 과정 상세&lt;br /&gt;
&lt;br /&gt;
### **1. Build Phase - Entry 추가**&lt;br /&gt;
&lt;br /&gt;
```python&lt;br /&gt;
# 의사 코드&lt;br /&gt;
def insert_entry(join_key, row_data):&lt;br /&gt;
    # 1. Hash 계산&lt;br /&gt;
    hash_value = hash_function(join_key)&lt;br /&gt;
    bucket_index = hash_value % bucket_count&lt;br /&gt;
    &lt;br /&gt;
    # 2. 새 Entry 생성 (실제 데이터 저장)&lt;br /&gt;
    new_entry = HashEntry()&lt;br /&gt;
    new_entry.hash_value = hash_value&lt;br /&gt;
    new_entry.join_key = join_key      # 실제 값&lt;br /&gt;
    new_entry.row_data = row_data      # 실제 데이터&lt;br /&gt;
    new_entry.next = NULL&lt;br /&gt;
    &lt;br /&gt;
    # 3. Bucket의 chain 맨 앞에 추가&lt;br /&gt;
    new_entry.next = buckets[bucket_index].head  # 기존 head&lt;br /&gt;
    buckets[bucket_index].head = new_entry       # 새 entry를 head로&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **2. Probe Phase - Entry 검색**&lt;br /&gt;
&lt;br /&gt;
```python&lt;br /&gt;
def probe_entry(probe_key):&lt;br /&gt;
    # 1. Hash 계산으로 bucket 찾기&lt;br /&gt;
    hash_value = hash_function(probe_key)&lt;br /&gt;
    bucket_index = hash_value % bucket_count&lt;br /&gt;
    &lt;br /&gt;
    # 2. Bucket에서 시작 포인터 가져오기&lt;br /&gt;
    current_entry = buckets[bucket_index].head  # 포인터!&lt;br /&gt;
    &lt;br /&gt;
    # 3. Chain 순회 (실제 데이터 비교)&lt;br /&gt;
    while current_entry != NULL:&lt;br /&gt;
        if current_entry.hash_value == hash_value:  # 1차 필터&lt;br /&gt;
            if current_entry.join_key == probe_key:  # 2차 비교 (실제 값!)&lt;br /&gt;
                return current_entry.row_data  # 매칭 성공&lt;br /&gt;
        current_entry = current_entry.next  # 다음 entry로&lt;br /&gt;
    &lt;br /&gt;
    return NULL  # 매칭 실패&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 실제 예시&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- 테이블&lt;br /&gt;
departments: dept_id(10, 20, 30)&lt;br /&gt;
&lt;br /&gt;
-- Hash Table 생성 (bucket_count = 4)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**Build 후 상태:**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
Bucket Array (포인터만):&lt;br /&gt;
[0] → NULL&lt;br /&gt;
[1] → 0xA000 (dept_id=10의 entry 주소)&lt;br /&gt;
[2] → 0xB000 (dept_id=20의 entry 주소)&lt;br /&gt;
[3] → 0xC000 (dept_id=30의 entry 주소)&lt;br /&gt;
&lt;br /&gt;
Entry Pool (실제 데이터):&lt;br /&gt;
주소 0xA000: [hash=1, next=NULL, key=10, data=&#039;Sales&#039;]&lt;br /&gt;
주소 0xB000: [hash=2, next=NULL, key=20, data=&#039;HR&#039;]&lt;br /&gt;
주소 0xC000: [hash=3, next=NULL, key=30, data=&#039;IT&#039;]&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**Collision 발생 시:**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
dept_id=10: hash % 4 = 1 → Bucket[1]&lt;br /&gt;
dept_id=50: hash % 4 = 1 → Bucket[1]  (충돌!)&lt;br /&gt;
&lt;br /&gt;
Bucket Array:&lt;br /&gt;
[1] → 0xA000  (첫 번째 entry 주소)&lt;br /&gt;
&lt;br /&gt;
Entry Pool:&lt;br /&gt;
주소 0xA000: [hash=X, next=0xD000, key=10, data=&#039;Sales&#039;]&lt;br /&gt;
                              ↓&lt;br /&gt;
주소 0xD000: [hash=Y, next=NULL, key=50, data=&#039;Finance&#039;]&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 핵심 정리&lt;br /&gt;
&lt;br /&gt;
|항목        |Hash Bucket  |Hash Entry        |&lt;br /&gt;
|----------|-------------|------------------|&lt;br /&gt;
|**역할**    |색인 (Index)   |실제 데이터 저장소        |&lt;br /&gt;
|**저장 내용** |포인터 (8 bytes)|Hash값 + Key + Data|&lt;br /&gt;
|**메모리 크기**|매우 작음        |대부분의 메모리 사용       |&lt;br /&gt;
|**비유**    |책의 목차        |책의 본문             |&lt;br /&gt;
&lt;br /&gt;
**쉽게 비유하면:**&lt;br /&gt;
&lt;br /&gt;
- **Bucket** = 아파트 동 번호판 (몇 호인지 주소만 표시)&lt;br /&gt;
- **Entry** = 실제 집 (사람들과 가구가 있는 공간)&lt;br /&gt;
&lt;br /&gt;
Oracle DBA로서 성능 튜닝 시:&lt;br /&gt;
&lt;br /&gt;
- Bucket 수는 메모리에 큰 영향 없음 (포인터만)&lt;br /&gt;
- Entry Pool 크기가 실제 메모리 사용량 결정&lt;br /&gt;
- `v$sql_workarea`에서 보는 메모리는 대부분 Entry Pool 크기입니다!&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1624</id>
		<title>해시 버킷의 저장구조</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1624"/>
		<updated>2025-10-23T04:06:19Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 해시 버킷에 저장되는 내용 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Oracle Hash Join의 해시테이블 내부 구조==&lt;br /&gt;
=== 전체 구조 개요 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
Hash Table (PGA Memory)&lt;br /&gt;
├── Bucket Array (해시 테이블의 배열)&lt;br /&gt;
│   ├── Bucket #0 → Hash Entry Chain&lt;br /&gt;
│   ├── Bucket #1 → Hash Entry Chain&lt;br /&gt;
│   ├── Bucket #2 → Hash Entry Chain&lt;br /&gt;
│   ├── ...&lt;br /&gt;
│   └── Bucket #N → Hash Entry Chain&lt;br /&gt;
│&lt;br /&gt;
└── Hash Entry Pool (실제 데이터 저장 영역)&lt;br /&gt;
    ├── Entry1 [hash, key, data, next*]&lt;br /&gt;
    ├── Entry2 [hash, key, data, next*]&lt;br /&gt;
    └── ...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 해시 버킷 (Hash Bucket) ===&lt;br /&gt;
:::* Bucket이란?&lt;br /&gt;
:::- Hash Table을 구성하는 **논리적 슬롯(Slot)**&lt;br /&gt;
:::- 포인터 배열로 구현됨&lt;br /&gt;
:::- 각 bucket은 hash entry chain의 **시작점(head pointer)**를 가리킴&lt;br /&gt;
&lt;br /&gt;
=== Bucket 개수 결정 ===&lt;br /&gt;
:::&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
Bucket 수 = 2의 거듭제곱 (예: 256, 512, 1024, 2048...)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:::* Oracle이 자동 계산:&lt;br /&gt;
:::- Build input의 cardinality&lt;br /&gt;
:::- 사용 가능한 메모리&lt;br /&gt;
:::- 통계 정보 (NUM_ROWS, NUM_DISTINCT)&lt;br /&gt;
&lt;br /&gt;
=== Bucket 선택 방식 ===&lt;br /&gt;
:::&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 의사 코드&lt;br /&gt;
hash_value = hash_function(join_key)&lt;br /&gt;
bucket_number = hash_value % bucket_count  # 또는 비트 마스킹&lt;br /&gt;
bucket[bucket_number]  # 해당 bucket 접근&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 해시 버킷의 저장 구조&lt;br /&gt;
&lt;br /&gt;
=== 해시 버킷에 저장되는 내용 ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 해시 테이블 구조:&lt;br /&gt;
#[해시 버킷 배열]&lt;br /&gt;
Bucket 0  → [emp_id=101, name=&#039;김철수&#039;, dept_id=10] → [emp_id=501, name=&#039;이영희&#039;, dept_id=10] → NULL&lt;br /&gt;
Bucket 1  → [emp_id=202, name=&#039;박민수&#039;, dept_id=20] → NULL&lt;br /&gt;
Bucket 2  → NULL&lt;br /&gt;
Bucket 3  → [emp_id=303, name=&#039;정수진&#039;, dept_id=30] → [emp_id=403, name=&#039;최지원&#039;, dept_id=30] → NULL&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Hash Entry ===&lt;br /&gt;
&lt;br /&gt;
==== Entry 구성 요소 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
+---------------------------+&lt;br /&gt;
| Hash Entry Structure      |&lt;br /&gt;
+---------------------------+&lt;br /&gt;
| 1. Hash Value (4-8 bytes) | ← 해시함수 결과값&lt;br /&gt;
| 2. Next Pointer (8 bytes) | ← 다음 entry 주소 (chaining)&lt;br /&gt;
| 3. Join Key Value(s)      | ← 실제 join 컬럼 원본값&lt;br /&gt;
| 4. Row Data               | ← SELECT에 필요한 컬럼들&lt;br /&gt;
+---------------------------+&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Entry 저장 정보 예시 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 쿼리 예시&lt;br /&gt;
SELECT e.emp_id, e.name, d.dept_name&lt;br /&gt;
  FROM employees e&lt;br /&gt;
     , departments d&lt;br /&gt;
 WHERE e.dept_id = d.dept_id;&lt;br /&gt;
&lt;br /&gt;
-- departments (작은 테이블) → Build Input&lt;br /&gt;
-- employees (큰 테이블) → Probe Input&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
::::* Hash Entry 내용:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Entry for dept_id = 10:&lt;br /&gt;
- Hash Value: 2348756 (hash 함수 결과)&lt;br /&gt;
- Next Pointer: 0x7F8A3C (다음 entry 주소 또는 NULL)&lt;br /&gt;
- Join Key: dept_id = 10 (원본값)&lt;br /&gt;
- Row Data: dept_name = &#039;Sales&#039;, location = &#039;Seoul&#039;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### **Phase 1: Build Phase**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
1. 작은 테이블(Build Input) 읽기&lt;br /&gt;
   ↓&lt;br /&gt;
2. 각 row에 대해:&lt;br /&gt;
   hash_value = hash(join_key)&lt;br /&gt;
   bucket_num = hash_value % bucket_count&lt;br /&gt;
   ↓&lt;br /&gt;
3. Hash Entry 생성:&lt;br /&gt;
   - Hash value 저장&lt;br /&gt;
   - Join key 원본값 저장&lt;br /&gt;
   - 필요한 컬럼 데이터 저장&lt;br /&gt;
   ↓&lt;br /&gt;
4. 해당 bucket의 chain에 추가 (linked list)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**메모리 구조:**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
Bucket Array          Hash Entry Pool&lt;br /&gt;
[0] → NULL           &lt;br /&gt;
[1] → Entry_A ─────→ [Hash:1234, Key:10, Data:..., Next→Entry_B]&lt;br /&gt;
[2] → Entry_C                                              ↓&lt;br /&gt;
[3] → NULL                    [Hash:5678, Key:20, Data:..., Next:NULL]&lt;br /&gt;
...&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **Phase 2: Probe Phase**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
1. 큰 테이블(Probe Input) 읽기&lt;br /&gt;
   ↓&lt;br /&gt;
2. 각 row에 대해:&lt;br /&gt;
   hash_value = hash(join_key)&lt;br /&gt;
   bucket_num = hash_value % bucket_count&lt;br /&gt;
   ↓&lt;br /&gt;
3. 해당 bucket의 chain을 순회:&lt;br /&gt;
   entry = bucket[bucket_num]&lt;br /&gt;
   while entry != NULL:&lt;br /&gt;
       if entry.hash_value == hash_value:  # 1차 비교 (빠름)&lt;br /&gt;
           if entry.join_key == probe_key:  # 2차 비교 (정확)&lt;br /&gt;
               → JOIN 성공, 결과 반환&lt;br /&gt;
       entry = entry.next&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 5. Hash Collision 처리&lt;br /&gt;
&lt;br /&gt;
### **충돌 발생 시나리오**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
hash(dept_id=10) = 1234 → 1234 % 256 = 50 → Bucket #50&lt;br /&gt;
hash(dept_id=30) = 5678 → 5678 % 256 = 50 → Bucket #50  ← 충돌!&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **Chaining으로 해결**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
Bucket #50&lt;br /&gt;
   ↓&lt;br /&gt;
[Entry1: hash=1234, key=10, next→]&lt;br /&gt;
   ↓&lt;br /&gt;
[Entry2: hash=5678, key=30, next→]&lt;br /&gt;
   ↓&lt;br /&gt;
[Entry3: hash=9012, key=70, next=NULL]&lt;br /&gt;
&lt;br /&gt;
Probe 시: Chain을 순차 탐색하며 실제 key 값 비교&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 6. 메모리 관리&lt;br /&gt;
&lt;br /&gt;
### **저장 위치: PGA**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- 메모리 할당 우선순위&lt;br /&gt;
PGA_AGGREGATE_TARGET (자동 관리)&lt;br /&gt;
  └── Work Area&lt;br /&gt;
      └── Hash Area&lt;br /&gt;
          ├── Bucket Array (포인터 배열)&lt;br /&gt;
          └── Hash Entry Pool (실제 데이터)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **메모리 부족 시: Temp Spill**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
Optimal: 전체 hash table이 메모리에 fit&lt;br /&gt;
   ↓&lt;br /&gt;
OnePass: 일부 partition이 디스크로 (1회 읽기)&lt;br /&gt;
   ↓&lt;br /&gt;
MultiPass: 여러 partition이 디스크로 (여러 번 읽기) ← 성능 저하!&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**확인 방법:**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
SELECT sql_id, operation_type, &lt;br /&gt;
       estimated_optimal_size/1024/1024 as optimal_mb,&lt;br /&gt;
       estimated_onepass_size/1024/1024 as onepass_mb,&lt;br /&gt;
       last_memory_used/1024/1024 as used_mb,&lt;br /&gt;
       last_execution,&lt;br /&gt;
       CASE &lt;br /&gt;
         WHEN last_memory_used &amp;lt; estimated_onepass_size THEN &#039;MultiPass&#039;&lt;br /&gt;
         WHEN last_memory_used &amp;lt; estimated_optimal_size THEN &#039;OnePass&#039;&lt;br /&gt;
         ELSE &#039;Optimal&#039;&lt;br /&gt;
       END as execution_type&lt;br /&gt;
FROM v$sql_workarea&lt;br /&gt;
WHERE operation_type = &#039;HASH-JOIN&#039;&lt;br /&gt;
ORDER BY last_execution DESC;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 7. 성능 영향 요소&lt;br /&gt;
&lt;br /&gt;
### **좋은 경우 (O(1) lookup)**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
✓ 충분한 메모리 (Optimal execution)&lt;br /&gt;
✓ 적절한 bucket 수 (충돌 최소화)&lt;br /&gt;
✓ 균등한 hash 분포&lt;br /&gt;
✓ 작은 build input&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **나쁜 경우 (O(n) lookup)**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
✗ 메모리 부족 (MultiPass → 디스크 I/O)&lt;br /&gt;
✗ Bucket 수 부족 (긴 chain)&lt;br /&gt;
✗ 심한 데이터 skew (특정 bucket에 집중)&lt;br /&gt;
✗ 큰 build input&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 8. 실무 활용 예시&lt;br /&gt;
&lt;br /&gt;
### **Hash Join 모니터링**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- 현재 실행 중인 hash join 확인&lt;br /&gt;
SELECT s.sid, s.serial#, s.username,&lt;br /&gt;
       w.operation_type, w.policy,&lt;br /&gt;
       w.work_area_size/1024/1024 as workarea_mb,&lt;br /&gt;
       w.expected_size/1024/1024 as expected_mb&lt;br /&gt;
FROM v$sql_workarea_active w, v$session s&lt;br /&gt;
WHERE w.sid = s.sid&lt;br /&gt;
  AND w.operation_type = &#039;HASH-JOIN&#039;;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **통계 수집 (중요!)**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- Hash join 성능은 통계 정확도에 크게 의존&lt;br /&gt;
BEGIN&lt;br /&gt;
  DBMS_STATS.GATHER_TABLE_STATS(&lt;br /&gt;
    ownname =&amp;gt; &#039;SCOTT&#039;,&lt;br /&gt;
    tabname =&amp;gt; &#039;EMPLOYEES&#039;,&lt;br /&gt;
    estimate_percent =&amp;gt; DBMS_STATS.AUTO_SAMPLE_SIZE,&lt;br /&gt;
    method_opt =&amp;gt; &#039;FOR ALL COLUMNS SIZE AUTO&#039;&lt;br /&gt;
  );&lt;br /&gt;
END;&lt;br /&gt;
/&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **힌트 활용**&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- Hash join 강제 + build input 지정&lt;br /&gt;
SELECT /*+ USE_HASH(e d) SWAP_JOIN_INPUTS(d) */ &lt;br /&gt;
  e.emp_id, d.dept_name&lt;br /&gt;
FROM employees e, departments d&lt;br /&gt;
WHERE e.dept_id = d.dept_id;&lt;br /&gt;
&lt;br /&gt;
-- SWAP_JOIN_INPUTS: departments를 build input으로 사용&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 9. 핵심 정리&lt;br /&gt;
&lt;br /&gt;
|구성요소          |역할    |저장 내용                           |&lt;br /&gt;
|--------------|------|--------------------------------|&lt;br /&gt;
|**Hash Table**|전체 구조 |PGA 메모리에 생성                     |&lt;br /&gt;
|**Bucket**    |논리적 슬롯|Entry chain의 head pointer       |&lt;br /&gt;
|**Hash Entry**|실제 데이터|Hash value + Join key + Row data|&lt;br /&gt;
|**Chain**     |충돌 해결 |Linked list로 같은 bucket의 entry 연결|&lt;br /&gt;
&lt;br /&gt;
**핵심 원리:**&lt;br /&gt;
&lt;br /&gt;
- Hash value로 **빠르게 bucket 찾기** (O(1))&lt;br /&gt;
- 원본 key 값으로 **정확한 매칭** (충돌 해결)&lt;br /&gt;
- Chaining으로 **여러 entry 관리**&lt;br /&gt;
- PGA 메모리 활용으로 **빠른 접근**&lt;br /&gt;
&lt;br /&gt;
DBA 업무 시 hash join 성능 문제가 있다면 통계 정보, PGA 메모리 설정, 그리고 build input 크기를 먼저 확인하시는 것이 좋습니다!​​​​​​​​​​​​​​​​&lt;br /&gt;
&lt;br /&gt;
=== 실제 메모리 구조 예시 ===&lt;br /&gt;
==== Build 단계 ====&lt;br /&gt;
* Build 단계: employees 테이블 (작은 테이블)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시 쿼리&lt;br /&gt;
SELECT e.emp_id, e.name, e.salary, d.dept_name&lt;br /&gt;
  FROM employees e  -- Build 테이블&lt;br /&gt;
  JOIN departments d  -- Probe 테이블&lt;br /&gt;
    ON e.dept_id = d.dept_id&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 단계에서 생성되는 해시 테이블:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
메모리 영역 (PGA - Work Area)&lt;br /&gt;
해시 테이블 버킷:&lt;br /&gt;
&lt;br /&gt;
Bucket[0] → NULL&lt;br /&gt;
&lt;br /&gt;
Bucket[1] → [Entry 1] → NULL&lt;br /&gt;
            ┌──────────────────────────────┐&lt;br /&gt;
            │ emp_id: 101     (조인 키)      │&lt;br /&gt;
            │ name: &#039;김철수&#039;   (일반 컬럼)     │&lt;br /&gt;
            │ salary: 5000000 (일반 컬럼)    │&lt;br /&gt;
            │ next: NULL      (체인 포인터)   │&lt;br /&gt;
            └──────────────────────────────┘&lt;br /&gt;
&lt;br /&gt;
Bucket[2] → [Entry 2] → [Entry 3] → NULL&lt;br /&gt;
            ┌──────────────────┐   ┌──────────────────┐&lt;br /&gt;
            │ emp_id: 202      │   │ emp_id: 302      │&lt;br /&gt;
            │ name: &#039;이영희&#039;     │    │ name: &#039;박민수&#039;    │&lt;br /&gt;
            │ salary: 6000000  │   │ salary: 5500000  │&lt;br /&gt;
            │ next: 0x3000     │   │ next: NULL       │&lt;br /&gt;
            └──────────────────┘   └──────────────────┘&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 실제 메모리 레이아웃&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
메모리 주소   | 내용&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x1000       | emp_id = 101&lt;br /&gt;
0x1004       | name = &amp;quot;김철수&amp;quot; (포인터 또는 인라인)&lt;br /&gt;
0x1008       | salary = 5000000&lt;br /&gt;
0x100C       | next = NULL&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x2000       | emp_id = 202&lt;br /&gt;
0x2004       | name = &amp;quot;이영희&amp;quot;&lt;br /&gt;
0x2008       | salary = 6000000&lt;br /&gt;
0x200C       | next = 0x3000  (다음 엔트리 주소)&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x3000       | emp_id = 302&lt;br /&gt;
0x3004       | name = &amp;quot;박민수&amp;quot;&lt;br /&gt;
0x3008       | salary = 5500000&lt;br /&gt;
0x300C       | next = NULL&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 과정 상세&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 의사 코드로 표현한 Build 단계&lt;br /&gt;
&lt;br /&gt;
hash_table = Array[BUCKET_SIZE]  # 버킷 배열 초기화&lt;br /&gt;
&lt;br /&gt;
for row in small_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 엔트리 생성 (실제 데이터 복사)&lt;br /&gt;
    entry = {&lt;br /&gt;
        &#039;join_key&#039;: row.join_key,&lt;br /&gt;
        &#039;column1&#039;: row.column1,&lt;br /&gt;
        &#039;column2&#039;: row.column2,&lt;br /&gt;
        &#039;next&#039;: NULL  # 체인 포인터&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    # 3. 버킷에 삽입 (체이닝 방식)&lt;br /&gt;
    if hash_table[bucket_index] is NULL:&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
    else:&lt;br /&gt;
        # 충돌 발생 - 체인의 맨 앞에 추가&lt;br /&gt;
        entry.next = hash_table[bucket_index]&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Probe 단계 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Probe 단계&lt;br /&gt;
&lt;br /&gt;
for probe_row in large_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(probe_row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 해당 버킷의 체인을 순회&lt;br /&gt;
    entry = hash_table[bucket_index]&lt;br /&gt;
    while entry is not NULL:&lt;br /&gt;
        # 3. 실제 조인 키 비교 (중요!)&lt;br /&gt;
        if entry.join_key == probe_row.join_key:&lt;br /&gt;
            # 조인 성공 - 결과 생성&lt;br /&gt;
            output(entry.data + probe_row.data)&lt;br /&gt;
        &lt;br /&gt;
        entry = entry.next  # 체인의 다음 엔트리로&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== [[hash join Probe 단계 처리과정|Probe 단계]] 에서도 해시버킷이 사용되나? =====&lt;br /&gt;
답변 : 아니요&lt;br /&gt;
&lt;br /&gt;
=== 왜 이렇게 설계 되었을까? ===&lt;br /&gt;
# 디스크 기반 접근 (주소만 저장한다면):&lt;br /&gt;
## 조인 매칭마다 디스크 I/O 발생&lt;br /&gt;
## 성능: 매우 느림&lt;br /&gt;
# 메모리 기반 접근 (실제 데이터 저장):&lt;br /&gt;
## 모든 매칭이 메모리에서 처리&lt;br /&gt;
## 성능: 매우 빠름 (Hash Join의 장점)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 결론 : 해시 버킷 구조의 핵심 포인트 ===&lt;br /&gt;
# 데이터 블럭의 주소만 저장 하나요? &lt;br /&gt;
#: NO → 실제 행 데이터를 메모리에 복사하여 저장&lt;br /&gt;
# 왜 데이터를 저장하나요? &lt;br /&gt;
#:Probe 단계에서 디스크 I/O 없이 빠르게 접근하기 위함&lt;br /&gt;
# DB의 어디 영역에 저장하나요? &lt;br /&gt;
#: 저장 위치: PGA의 Work Area (메모리)&lt;br /&gt;
# 해시충돌시 처리방법은?&lt;br /&gt;
#: 체이닝(Chaining) 방식 - 링크드 리스트&lt;br /&gt;
# 메모리 부족 시 에는?&lt;br /&gt;
#: 디스크 임시 테이블스페이스 사용 (성능 저하)&lt;br /&gt;
&lt;br /&gt;
* 따라서 Hash Join은 **작은 테이블 전체를 메모리에 로드**하는 것이 핵심입니다!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
명확하게 설명드리겠습니다! 이 부분이 정말 중요한 개념입니다.&lt;br /&gt;
&lt;br /&gt;
## 핵심 답변&lt;br /&gt;
&lt;br /&gt;
**Hash Bucket은 실제 데이터를 가지고 있지 않습니다!**&lt;br /&gt;
**Hash Bucket은 Hash Entry의 주소(포인터)만 가지고 있습니다.**&lt;br /&gt;
&lt;br /&gt;
## 구조 비교&lt;br /&gt;
&lt;br /&gt;
### **Hash Bucket (Pointer Array)**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
단순한 포인터 배열&lt;br /&gt;
&lt;br /&gt;
Bucket[0] → 0x00000000 (NULL, 비어있음)&lt;br /&gt;
Bucket[1] → 0x7F8A3C00 (Entry 시작 주소)&lt;br /&gt;
Bucket[2] → 0x00000000 (NULL, 비어있음)&lt;br /&gt;
Bucket[3] → 0x7F8A4D20 (Entry 시작 주소)&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
각 bucket = 8 bytes (포인터 크기)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **Hash Entry (Actual Data)**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
실제 데이터를 담는 구조체&lt;br /&gt;
&lt;br /&gt;
메모리 주소: 0x7F8A3C00&lt;br /&gt;
+---------------------------+&lt;br /&gt;
| Hash Value: 1234          | 4-8 bytes&lt;br /&gt;
| Next Pointer: 0x7F8A3C40  | 8 bytes (다음 entry)&lt;br /&gt;
| Join Key: dept_id = 10    | 가변 크기&lt;br /&gt;
| Row Data: &#039;Sales&#039;, ...    | 가변 크기&lt;br /&gt;
+---------------------------+&lt;br /&gt;
&lt;br /&gt;
메모리 주소: 0x7F8A3C40&lt;br /&gt;
+---------------------------+&lt;br /&gt;
| Hash Value: 5678          |&lt;br /&gt;
| Next Pointer: NULL        |&lt;br /&gt;
| Join Key: dept_id = 20    |&lt;br /&gt;
| Row Data: &#039;Marketing&#039;,... |&lt;br /&gt;
+---------------------------+&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 시각적 구조&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
┌─────────────────────────────────────────────────────────┐&lt;br /&gt;
│  Hash Table (PGA Memory)                                │&lt;br /&gt;
├─────────────────────────────────────────────────────────┤&lt;br /&gt;
│                                                          │&lt;br /&gt;
│  [Bucket Array - 포인터만 저장]                          │&lt;br /&gt;
│  ┌──────┬──────────┐                                    │&lt;br /&gt;
│  │ #0   │ NULL     │                                    │&lt;br /&gt;
│  ├──────┼──────────┤                                    │&lt;br /&gt;
│  │ #1   │ 0x1000 ──┼──┐                                 │&lt;br /&gt;
│  ├──────┼──────────┤  │                                 │&lt;br /&gt;
│  │ #2   │ NULL     │  │                                 │&lt;br /&gt;
│  ├──────┼──────────┤  │                                 │&lt;br /&gt;
│  │ #3   │ 0x2000 ──┼──┼──┐                              │&lt;br /&gt;
│  └──────┴──────────┘  │  │                              │&lt;br /&gt;
│                        │  │                              │&lt;br /&gt;
│  [Hash Entry Pool - 실제 데이터 저장]                    │&lt;br /&gt;
│                        │  │                              │&lt;br /&gt;
│  ┌──────────────────┐ │  │                              │&lt;br /&gt;
│  │ 0x1000 주소      │←┘  │                              │&lt;br /&gt;
│  ├──────────────────┤    │                              │&lt;br /&gt;
│  │ Hash: 1234       │    │                              │&lt;br /&gt;
│  │ Next: 0x1100 ────┼──┐ │                              │&lt;br /&gt;
│  │ Key: 10          │  │ │                              │&lt;br /&gt;
│  │ Data: &#039;Sales&#039;    │  │ │                              │&lt;br /&gt;
│  └──────────────────┘  │ │                              │&lt;br /&gt;
│                         │ │                              │&lt;br /&gt;
│  ┌──────────────────┐  │ │                              │&lt;br /&gt;
│  │ 0x1100 주소      │←─┘ │                              │&lt;br /&gt;
│  ├──────────────────┤    │                              │&lt;br /&gt;
│  │ Hash: 5678       │    │                              │&lt;br /&gt;
│  │ Next: NULL       │    │                              │&lt;br /&gt;
│  │ Key: 30          │    │                              │&lt;br /&gt;
│  │ Data: &#039;IT&#039;       │    │                              │&lt;br /&gt;
│  └──────────────────┘    │                              │&lt;br /&gt;
│                           │                              │&lt;br /&gt;
│  ┌──────────────────┐    │                              │&lt;br /&gt;
│  │ 0x2000 주소      │←───┘                              │&lt;br /&gt;
│  ├──────────────────┤                                   │&lt;br /&gt;
│  │ Hash: 9012       │                                   │&lt;br /&gt;
│  │ Next: NULL       │                                   │&lt;br /&gt;
│  │ Key: 20          │                                   │&lt;br /&gt;
│  │ Data: &#039;HR&#039;       │                                   │&lt;br /&gt;
│  └──────────────────┘                                   │&lt;br /&gt;
│                                                          │&lt;br /&gt;
└─────────────────────────────────────────────────────────┘&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## C 언어 스타일 구조체로 설명&lt;br /&gt;
&lt;br /&gt;
```c&lt;br /&gt;
// Hash Bucket (포인터 배열)&lt;br /&gt;
typedef struct {&lt;br /&gt;
    HashEntry* head;  // 8 bytes (포인터만!)&lt;br /&gt;
} HashBucket;&lt;br /&gt;
&lt;br /&gt;
// Hash Entry (실제 데이터)&lt;br /&gt;
typedef struct HashEntry {&lt;br /&gt;
    uint32_t hash_value;      // 4 bytes&lt;br /&gt;
    struct HashEntry* next;   // 8 bytes (다음 entry 포인터)&lt;br /&gt;
    void* join_key;           // 가변 크기 (실제 join key 값)&lt;br /&gt;
    void* row_data;           // 가변 크기 (실제 row 데이터)&lt;br /&gt;
} HashEntry;&lt;br /&gt;
&lt;br /&gt;
// Hash Table&lt;br /&gt;
typedef struct {&lt;br /&gt;
    uint32_t bucket_count;    // bucket 개수&lt;br /&gt;
    HashBucket* buckets;      // bucket 배열 (포인터만!)&lt;br /&gt;
    HashEntry* entry_pool;    // entry 저장 공간 (실제 데이터!)&lt;br /&gt;
} HashTable;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 메모리 크기 비교&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
예시: 1024개 bucket, 10000개 entry&lt;br /&gt;
&lt;br /&gt;
Bucket Array:&lt;br /&gt;
= 1024 buckets × 8 bytes (포인터)&lt;br /&gt;
= 8 KB (매우 작음!)&lt;br /&gt;
&lt;br /&gt;
Hash Entry Pool:&lt;br /&gt;
= 10000 entries × 평균 100 bytes (데이터 크기)&lt;br /&gt;
= 1 MB (실제 데이터 공간이 큼!)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 동작 과정 상세&lt;br /&gt;
&lt;br /&gt;
### **1. Build Phase - Entry 추가**&lt;br /&gt;
&lt;br /&gt;
```python&lt;br /&gt;
# 의사 코드&lt;br /&gt;
def insert_entry(join_key, row_data):&lt;br /&gt;
    # 1. Hash 계산&lt;br /&gt;
    hash_value = hash_function(join_key)&lt;br /&gt;
    bucket_index = hash_value % bucket_count&lt;br /&gt;
    &lt;br /&gt;
    # 2. 새 Entry 생성 (실제 데이터 저장)&lt;br /&gt;
    new_entry = HashEntry()&lt;br /&gt;
    new_entry.hash_value = hash_value&lt;br /&gt;
    new_entry.join_key = join_key      # 실제 값&lt;br /&gt;
    new_entry.row_data = row_data      # 실제 데이터&lt;br /&gt;
    new_entry.next = NULL&lt;br /&gt;
    &lt;br /&gt;
    # 3. Bucket의 chain 맨 앞에 추가&lt;br /&gt;
    new_entry.next = buckets[bucket_index].head  # 기존 head&lt;br /&gt;
    buckets[bucket_index].head = new_entry       # 새 entry를 head로&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### **2. Probe Phase - Entry 검색**&lt;br /&gt;
&lt;br /&gt;
```python&lt;br /&gt;
def probe_entry(probe_key):&lt;br /&gt;
    # 1. Hash 계산으로 bucket 찾기&lt;br /&gt;
    hash_value = hash_function(probe_key)&lt;br /&gt;
    bucket_index = hash_value % bucket_count&lt;br /&gt;
    &lt;br /&gt;
    # 2. Bucket에서 시작 포인터 가져오기&lt;br /&gt;
    current_entry = buckets[bucket_index].head  # 포인터!&lt;br /&gt;
    &lt;br /&gt;
    # 3. Chain 순회 (실제 데이터 비교)&lt;br /&gt;
    while current_entry != NULL:&lt;br /&gt;
        if current_entry.hash_value == hash_value:  # 1차 필터&lt;br /&gt;
            if current_entry.join_key == probe_key:  # 2차 비교 (실제 값!)&lt;br /&gt;
                return current_entry.row_data  # 매칭 성공&lt;br /&gt;
        current_entry = current_entry.next  # 다음 entry로&lt;br /&gt;
    &lt;br /&gt;
    return NULL  # 매칭 실패&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 실제 예시&lt;br /&gt;
&lt;br /&gt;
```sql&lt;br /&gt;
-- 테이블&lt;br /&gt;
departments: dept_id(10, 20, 30)&lt;br /&gt;
&lt;br /&gt;
-- Hash Table 생성 (bucket_count = 4)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**Build 후 상태:**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
Bucket Array (포인터만):&lt;br /&gt;
[0] → NULL&lt;br /&gt;
[1] → 0xA000 (dept_id=10의 entry 주소)&lt;br /&gt;
[2] → 0xB000 (dept_id=20의 entry 주소)&lt;br /&gt;
[3] → 0xC000 (dept_id=30의 entry 주소)&lt;br /&gt;
&lt;br /&gt;
Entry Pool (실제 데이터):&lt;br /&gt;
주소 0xA000: [hash=1, next=NULL, key=10, data=&#039;Sales&#039;]&lt;br /&gt;
주소 0xB000: [hash=2, next=NULL, key=20, data=&#039;HR&#039;]&lt;br /&gt;
주소 0xC000: [hash=3, next=NULL, key=30, data=&#039;IT&#039;]&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**Collision 발생 시:**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
dept_id=10: hash % 4 = 1 → Bucket[1]&lt;br /&gt;
dept_id=50: hash % 4 = 1 → Bucket[1]  (충돌!)&lt;br /&gt;
&lt;br /&gt;
Bucket Array:&lt;br /&gt;
[1] → 0xA000  (첫 번째 entry 주소)&lt;br /&gt;
&lt;br /&gt;
Entry Pool:&lt;br /&gt;
주소 0xA000: [hash=X, next=0xD000, key=10, data=&#039;Sales&#039;]&lt;br /&gt;
                              ↓&lt;br /&gt;
주소 0xD000: [hash=Y, next=NULL, key=50, data=&#039;Finance&#039;]&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 핵심 정리&lt;br /&gt;
&lt;br /&gt;
|항목        |Hash Bucket  |Hash Entry        |&lt;br /&gt;
|----------|-------------|------------------|&lt;br /&gt;
|**역할**    |색인 (Index)   |실제 데이터 저장소        |&lt;br /&gt;
|**저장 내용** |포인터 (8 bytes)|Hash값 + Key + Data|&lt;br /&gt;
|**메모리 크기**|매우 작음        |대부분의 메모리 사용       |&lt;br /&gt;
|**비유**    |책의 목차        |책의 본문             |&lt;br /&gt;
&lt;br /&gt;
**쉽게 비유하면:**&lt;br /&gt;
&lt;br /&gt;
- **Bucket** = 아파트 동 번호판 (몇 호인지 주소만 표시)&lt;br /&gt;
- **Entry** = 실제 집 (사람들과 가구가 있는 공간)&lt;br /&gt;
&lt;br /&gt;
Oracle DBA로서 성능 튜닝 시:&lt;br /&gt;
&lt;br /&gt;
- Bucket 수는 메모리에 큰 영향 없음 (포인터만)&lt;br /&gt;
- Entry Pool 크기가 실제 메모리 사용량 결정&lt;br /&gt;
- `v$sql_workarea`에서 보는 메모리는 대부분 Entry Pool 크기입니다!&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1600</id>
		<title>DBA 수행 방법론 (공공/대기업 SI 프로젝트)</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1600"/>
		<updated>2025-10-22T03:58:45Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 전개 단계 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=DBA 수행 방법론 (공공/대기업 SI 프로젝트) =&lt;br /&gt;
{{FAQ&lt;br /&gt;
|제목= *게시판 - [[https://dbstudy.co.kr/w/index.php?title=%ED%8A%B9%EC%88%98:WikiForum&amp;amp;forum=3| DBA 수행 방법론 묻고답하기(FAQ)]]&lt;br /&gt;
}}&lt;br /&gt;
== 개요 ==&lt;br /&gt;
&amp;lt;htmltag tagname=&amp;quot;iframe&amp;quot;frameborder=&amp;quot;0&amp;quot; width=&amp;quot;1024px&amp;quot; height=&amp;quot;768px&amp;quot; src=&amp;quot;https://viewer.diagrams.net/?tags=%7B%7D&amp;amp;lightbox=1&amp;amp;highlight=0000ff&amp;amp;edit=_blank&amp;amp;layers=1&amp;amp;nav=1&amp;amp;title=DBA%EB%8B%A8%EA%B3%84%EB%B3%84TASK%EC%99%80%EC%82%B0%EC%B6%9C%EB%AC%BC.drawio.png&amp;amp;transparent=1&amp;amp;dark=auto#R%3Cmxfile%3E%3Cdiagram%20name%3D%22%ED%8E%98%EC%9D%B4%EC%A7%80-1%22%20id%3D%22oy2hg6EIWq2iBE1s1H2V%22%3E7Vttb9s2EP41BLYPHSTrxeJHUXLabh2K1gUK7MugxqotTJEMmWmS%2Fvrd8UXvluXEdjdXQBBQZ%2FpIHp%2B7e3iUiRXcPb4uou3mz3wVp2RmrB6JFZLZjNou%2FEfBkxS4piEF6yJZSZFZCZbJ91gJdbf7ZBXvGh15nqc82TaFt3mWxbe8IYuKIn9odvuap81Rt9E67giWt1HalX5OVnwjpd5sXsnfxMl6o0c2XSo%2FuYt0Z7WS3SZa5Q81kbUgVlDkOZetu8cgTtF22i7yezd7Pi0nVsQZH%2FOFNx%2B4s1u8ZR9e27%2F%2FlVh%2FP83fp6%2FwC6jmW5TeqxWHzAcBWQTE8wj1yCIk1CUeFUJGmEF8KhozQh1s%2BCbx9Rr5kzYcLHeLTR59QRG73STp6l30lN9zLdVPbMejgqu9tw3snKdptN0l4qshSor49r7YJd%2Fij%2FFOdkRp%2FLiNspV6%2BJpnWonpwrNaWVzw%2BHGvzcxyJwDBcX4X8%2BIJuqgveAq9Crym3suHCgqurWSbGgzmrhJGCn7rUnW1Q9BQm3TMhlmdDRs2%2FUfEHNvkRfId7BOlyli7h%2BQujTIAb7RqiVgunBVFPN%2BqVhp%2F5bofL%2FJ%2F4iBP8wIkSbaJiwR38UvOeX6n90sZA9urIt9%2Biop1zOs7peYrnpM01fqyPEO4bPMk48J4DoM%2FMGdg%2FOYQB9YXwLNZPcMfdi94kGcwtSgRWxtHO%2F4Q79roopdF14DPdTGnQGaPxJh3NojZXYhBNPAtwgJsUJ9QW4UIuiDUFI058RwQfvKXf3QBqZA1GndtgOyDnIr60ZchKP3A6DLgwntjDu3uv3Xs%2FivlH9E22RqWeby6KOVxkUUctuU%2BW%2B06qCon%2FgKgOb1A82aYZKDBXEIDkWEChN6Eqhegyra6sCpJ1mlgNUrdBWBl96RI5CtAYmyBL5vQcEqaV5U0RzOzsuPJ06bdTZv%2B8tXbpciUPmEOhjHMo6YIb11AGji%2B6uypEAgUHLm1IVg4FXTcR%2BbNQk3HW0qAsFommr7K1PXBIbZiI0Tq7pc6DOLdqDaGWk9M0xDBVzYM9Sl0k6PJ0FyN9uk9W%2BhTg018OaglOoNOR4RvSRhEBzkBGrSXy2xccUUt5JRAT1CzgTh0gHJmnz4ldEP%2FPsc9kBJO5jJRmqwzeJCxhKHvJHA89ZVYBJsX%2BJV9GjIy6FbPYCMdfZfIG%2F10BLyKiVMw4FY57hwbCrqWxrNPvFA7YtB2xGEdkvAIZZJi9%2BoY48zSXR01JvX6lYlP1LTLoz10ZMrlPV%2B4cIiUXjlpJ9Lsm6WjFOPktA5pGCwYeIdmOTn0eR16NA98gUeP03eJA8a8%2F4DRzFATE7wmJji6fmKer0bndWAXsiY7gmhIZTR8v9xP72rdZcCsYu1%2BhYNZqVm0wWxkdpXWU4zoUWUfkLiajbUd6RcR7QP8g3SBKU8e3m8aua9NOYVqWVGmN7VcpIsAYuG%2F9q0cVusLfgnqvZKqhmIowSmVXU1NZIFcGv0ktaNdzJDVK1s3mnU6lYFauQz6ixrY4ABVPQMbrrAzNMAGnmDYkimABIY1K4LgibWA8fE40EjlyuIhpm91aHDEXjGR0ENlBkzDgeYi2myqjtcZIZj1lvSnZHxsrJqfhl0PharnsOu2vkvkYtpblQHge06t4SMzlv6KAY22fWyA1UrnMWoYr%2FNtgejqI01y%2B8%2BsEx29kAeMp6PPd4Fx%2Bi7gApbZ4wK1ZItFFWeio1dFR23jhxcmre4d%2FygSUvIgGXCxZGD2VAwxZBpN3uqLQp5RKX8ue4M4bFXFBsTJzfFDKlqDxE2WSmiDD1XFjjpp7JLxDiuuj%2BALk9TpbNnLODRaWXhhyHqpV9sTbaYDvL2Prxp7%2BOp55jIRxtOkS2v4fZSfqRxrda%2Fxlh%2Fe9cOv4X%2F6fEOticJdAJM%2FVUXR6rsjGAipE5e7Ii7n%2FPhLZsvt5XL7Dr6zulBTJHWinut4ycT9bkkmdJAVGFZUoKwjDXyvFo2Z8ANNQipayfB2t34qV0WuQzoOcZaBOT2XmClCM0ix9pUEj4gH15eM%2BrNPK0W9IBk5E0HSpui73XrJQWTcTfQzThUDiieHOK9D%2FFzsrK%2FGLAoLnq1cow9zEyn7H5Myd%2FR97%2Fne%2FDNORcrUGz0assgw6q%2F5MCE0ql%2FotF%2B%2FH3UbMmsKqzeAqhGOKNDJ1wPLK8TOO0vyskUXD8vr1cOk8Pozw7nP7XSiStpD%2B25eOvAsXzqdaRxKBwD3UN3cs%2FwO4b%2BHzHNzlmFkXglngcfq152ye%2FUTWWvxLw%3D%3D%3C%2Fdiagram%3E%3C%2Fmxfile%3E&amp;quot; allowtransparency=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;/htmltag&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
&lt;br /&gt;
==== [[분석단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[AS-IS 구조 분석 기술환경 파악]]&lt;br /&gt;
::::# [[요구사항 및 문제점 도출]]&lt;br /&gt;
::::# [[TO-BE 설계를 위한 기초자료 확보]]&lt;br /&gt;
&lt;br /&gt;
==== [[분석 단계 산출물]] ====&lt;br /&gt;
::::# [[인터뷰 계획서]]&lt;br /&gt;
::::# [[인터뷰 결과서]]&lt;br /&gt;
::::# [[요구사항 정의서]]&lt;br /&gt;
::::# [[현행 시스템 분석서]]&lt;br /&gt;
::::## 별첨1)현행시스템분석서-DB아키텍처)-별첨1-현행_데이터파일현황.xlsx&lt;br /&gt;
::::## 별첨2)현행시스템분석서-DB아키텍처)-별첨1-현행_테이블스페이스현황.xlsx&lt;br /&gt;
::::# [[아키텍처 정의서]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[설계 단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[시스템 작업]](OS, DISK, DB 파라미터, 리두로그그룹 등)&lt;br /&gt;
::::# [[DB설치 계획서]](설치위치,인스턴스명 등)&lt;br /&gt;
::::# [[초기파라미터 및 용량 산정]]&lt;br /&gt;
::::# [[TO-BE 데이터베이스 표준 및 구조, 용량, 보안, 백업/복구 설계]]&lt;br /&gt;
::::# [[물리모델링 검토 ]]( 파티션 대상 등)&lt;br /&gt;
&lt;br /&gt;
==== [[설계 단계 산출물]] ====&lt;br /&gt;
::::# [[명명규칙 정의서]]&lt;br /&gt;
::::# [[데이터베이스 설계서((표준/구조/용량/보안 /백업 복구 설계 등)]]&lt;br /&gt;
::::## [[데이터베이스설계서-DB아키텍처-별첨1.DB상세설계내역.xlsx]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[구축 단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[물리모델반영및 변경관리]]&lt;br /&gt;
::::# [[오브젝트 관리]]&lt;br /&gt;
::::# [[통합테스트 환경구성]]&lt;br /&gt;
::::# [[가용성 테스트 시나리오 작성]]&lt;br /&gt;
::::# [[백업복구테스트 시나리오 작성]]&lt;br /&gt;
&lt;br /&gt;
==== [[구축 단계 산출물]] ====&lt;br /&gt;
::::# [[SQL 작성 가이드]]&lt;br /&gt;
::::## 별첨)SQL작성가이드-DB아키텍처_DB접속정보.pptx&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
::::# [[가용성 테스트 결과서]]&lt;br /&gt;
::::# [[복구 테스트 결과서]]&lt;br /&gt;
::::# [[튜닝결과서]]&lt;br /&gt;
::::# [[인스펙션 결과서(선택)]]&lt;br /&gt;
&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
::::# [[데이터 이관 지원]]&lt;br /&gt;
::::# [[SQL 튜닝 (튜너)]]&lt;br /&gt;
::::# [[성능 모니터링]]&lt;br /&gt;
::::# [[성능 테스트 지원]]&lt;br /&gt;
::::# [[가용성 테스트]]&lt;br /&gt;
::::# [[백업복구테스트]]&lt;br /&gt;
&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
::::# [[운영자 매뉴얼]]&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
::::# [[데이터 이관 사전/사후 작업]]&lt;br /&gt;
::::# [[이행후점검및운영 DB 전환]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[분류:DBA 방법론]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1599</id>
		<title>DBA 수행 방법론 (공공/대기업 SI 프로젝트)</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1599"/>
		<updated>2025-10-22T03:58:32Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 테스트 단계 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=DBA 수행 방법론 (공공/대기업 SI 프로젝트) =&lt;br /&gt;
{{FAQ&lt;br /&gt;
|제목= *게시판 - [[https://dbstudy.co.kr/w/index.php?title=%ED%8A%B9%EC%88%98:WikiForum&amp;amp;forum=3| DBA 수행 방법론 묻고답하기(FAQ)]]&lt;br /&gt;
}}&lt;br /&gt;
== 개요 ==&lt;br /&gt;
&amp;lt;htmltag tagname=&amp;quot;iframe&amp;quot;frameborder=&amp;quot;0&amp;quot; width=&amp;quot;1024px&amp;quot; height=&amp;quot;768px&amp;quot; src=&amp;quot;https://viewer.diagrams.net/?tags=%7B%7D&amp;amp;lightbox=1&amp;amp;highlight=0000ff&amp;amp;edit=_blank&amp;amp;layers=1&amp;amp;nav=1&amp;amp;title=DBA%EB%8B%A8%EA%B3%84%EB%B3%84TASK%EC%99%80%EC%82%B0%EC%B6%9C%EB%AC%BC.drawio.png&amp;amp;transparent=1&amp;amp;dark=auto#R%3Cmxfile%3E%3Cdiagram%20name%3D%22%ED%8E%98%EC%9D%B4%EC%A7%80-1%22%20id%3D%22oy2hg6EIWq2iBE1s1H2V%22%3E7Vttb9s2EP41BLYPHSTrxeJHUXLabh2K1gUK7MugxqotTJEMmWmS%2Fvrd8UXvluXEdjdXQBBQZ%2FpIHp%2B7e3iUiRXcPb4uou3mz3wVp2RmrB6JFZLZjNou%2FEfBkxS4piEF6yJZSZFZCZbJ91gJdbf7ZBXvGh15nqc82TaFt3mWxbe8IYuKIn9odvuap81Rt9E67giWt1HalX5OVnwjpd5sXsnfxMl6o0c2XSo%2FuYt0Z7WS3SZa5Q81kbUgVlDkOZetu8cgTtF22i7yezd7Pi0nVsQZH%2FOFNx%2B4s1u8ZR9e27%2F%2FlVh%2FP83fp6%2FwC6jmW5TeqxWHzAcBWQTE8wj1yCIk1CUeFUJGmEF8KhozQh1s%2BCbx9Rr5kzYcLHeLTR59QRG73STp6l30lN9zLdVPbMejgqu9tw3snKdptN0l4qshSor49r7YJd%2Fij%2FFOdkRp%2FLiNspV6%2BJpnWonpwrNaWVzw%2BHGvzcxyJwDBcX4X8%2BIJuqgveAq9Crym3suHCgqurWSbGgzmrhJGCn7rUnW1Q9BQm3TMhlmdDRs2%2FUfEHNvkRfId7BOlyli7h%2BQujTIAb7RqiVgunBVFPN%2BqVhp%2F5bofL%2FJ%2F4iBP8wIkSbaJiwR38UvOeX6n90sZA9urIt9%2Biop1zOs7peYrnpM01fqyPEO4bPMk48J4DoM%2FMGdg%2FOYQB9YXwLNZPcMfdi94kGcwtSgRWxtHO%2F4Q79roopdF14DPdTGnQGaPxJh3NojZXYhBNPAtwgJsUJ9QW4UIuiDUFI058RwQfvKXf3QBqZA1GndtgOyDnIr60ZchKP3A6DLgwntjDu3uv3Xs%2FivlH9E22RqWeby6KOVxkUUctuU%2BW%2B06qCon%2FgKgOb1A82aYZKDBXEIDkWEChN6Eqhegyra6sCpJ1mlgNUrdBWBl96RI5CtAYmyBL5vQcEqaV5U0RzOzsuPJ06bdTZv%2B8tXbpciUPmEOhjHMo6YIb11AGji%2B6uypEAgUHLm1IVg4FXTcR%2BbNQk3HW0qAsFommr7K1PXBIbZiI0Tq7pc6DOLdqDaGWk9M0xDBVzYM9Sl0k6PJ0FyN9uk9W%2BhTg018OaglOoNOR4RvSRhEBzkBGrSXy2xccUUt5JRAT1CzgTh0gHJmnz4ldEP%2FPsc9kBJO5jJRmqwzeJCxhKHvJHA89ZVYBJsX%2BJV9GjIy6FbPYCMdfZfIG%2F10BLyKiVMw4FY57hwbCrqWxrNPvFA7YtB2xGEdkvAIZZJi9%2BoY48zSXR01JvX6lYlP1LTLoz10ZMrlPV%2B4cIiUXjlpJ9Lsm6WjFOPktA5pGCwYeIdmOTn0eR16NA98gUeP03eJA8a8%2F4DRzFATE7wmJji6fmKer0bndWAXsiY7gmhIZTR8v9xP72rdZcCsYu1%2BhYNZqVm0wWxkdpXWU4zoUWUfkLiajbUd6RcR7QP8g3SBKU8e3m8aua9NOYVqWVGmN7VcpIsAYuG%2F9q0cVusLfgnqvZKqhmIowSmVXU1NZIFcGv0ktaNdzJDVK1s3mnU6lYFauQz6ixrY4ABVPQMbrrAzNMAGnmDYkimABIY1K4LgibWA8fE40EjlyuIhpm91aHDEXjGR0ENlBkzDgeYi2myqjtcZIZj1lvSnZHxsrJqfhl0PharnsOu2vkvkYtpblQHge06t4SMzlv6KAY22fWyA1UrnMWoYr%2FNtgejqI01y%2B8%2BsEx29kAeMp6PPd4Fx%2Bi7gApbZ4wK1ZItFFWeio1dFR23jhxcmre4d%2FygSUvIgGXCxZGD2VAwxZBpN3uqLQp5RKX8ue4M4bFXFBsTJzfFDKlqDxE2WSmiDD1XFjjpp7JLxDiuuj%2BALk9TpbNnLODRaWXhhyHqpV9sTbaYDvL2Prxp7%2BOp55jIRxtOkS2v4fZSfqRxrda%2Fxlh%2Fe9cOv4X%2F6fEOticJdAJM%2FVUXR6rsjGAipE5e7Ii7n%2FPhLZsvt5XL7Dr6zulBTJHWinut4ycT9bkkmdJAVGFZUoKwjDXyvFo2Z8ANNQipayfB2t34qV0WuQzoOcZaBOT2XmClCM0ix9pUEj4gH15eM%2BrNPK0W9IBk5E0HSpui73XrJQWTcTfQzThUDiieHOK9D%2FFzsrK%2FGLAoLnq1cow9zEyn7H5Myd%2FR97%2Fne%2FDNORcrUGz0assgw6q%2F5MCE0ql%2FotF%2B%2FH3UbMmsKqzeAqhGOKNDJ1wPLK8TOO0vyskUXD8vr1cOk8Pozw7nP7XSiStpD%2B25eOvAsXzqdaRxKBwD3UN3cs%2FwO4b%2BHzHNzlmFkXglngcfq152ye%2FUTWWvxLw%3D%3D%3C%2Fdiagram%3E%3C%2Fmxfile%3E&amp;quot; allowtransparency=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;/htmltag&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
&lt;br /&gt;
==== [[분석단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[AS-IS 구조 분석 기술환경 파악]]&lt;br /&gt;
::::# [[요구사항 및 문제점 도출]]&lt;br /&gt;
::::# [[TO-BE 설계를 위한 기초자료 확보]]&lt;br /&gt;
&lt;br /&gt;
==== [[분석 단계 산출물]] ====&lt;br /&gt;
::::# [[인터뷰 계획서]]&lt;br /&gt;
::::# [[인터뷰 결과서]]&lt;br /&gt;
::::# [[요구사항 정의서]]&lt;br /&gt;
::::# [[현행 시스템 분석서]]&lt;br /&gt;
::::## 별첨1)현행시스템분석서-DB아키텍처)-별첨1-현행_데이터파일현황.xlsx&lt;br /&gt;
::::## 별첨2)현행시스템분석서-DB아키텍처)-별첨1-현행_테이블스페이스현황.xlsx&lt;br /&gt;
::::# [[아키텍처 정의서]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[설계 단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[시스템 작업]](OS, DISK, DB 파라미터, 리두로그그룹 등)&lt;br /&gt;
::::# [[DB설치 계획서]](설치위치,인스턴스명 등)&lt;br /&gt;
::::# [[초기파라미터 및 용량 산정]]&lt;br /&gt;
::::# [[TO-BE 데이터베이스 표준 및 구조, 용량, 보안, 백업/복구 설계]]&lt;br /&gt;
::::# [[물리모델링 검토 ]]( 파티션 대상 등)&lt;br /&gt;
&lt;br /&gt;
==== [[설계 단계 산출물]] ====&lt;br /&gt;
::::# [[명명규칙 정의서]]&lt;br /&gt;
::::# [[데이터베이스 설계서((표준/구조/용량/보안 /백업 복구 설계 등)]]&lt;br /&gt;
::::## [[데이터베이스설계서-DB아키텍처-별첨1.DB상세설계내역.xlsx]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[구축 단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[물리모델반영및 변경관리]]&lt;br /&gt;
::::# [[오브젝트 관리]]&lt;br /&gt;
::::# [[통합테스트 환경구성]]&lt;br /&gt;
::::# [[가용성 테스트 시나리오 작성]]&lt;br /&gt;
::::# [[백업복구테스트 시나리오 작성]]&lt;br /&gt;
&lt;br /&gt;
==== [[구축 단계 산출물]] ====&lt;br /&gt;
::::# [[SQL 작성 가이드]]&lt;br /&gt;
::::## 별첨)SQL작성가이드-DB아키텍처_DB접속정보.pptx&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
::::# [[가용성 테스트 결과서]]&lt;br /&gt;
::::# [[복구 테스트 결과서]]&lt;br /&gt;
::::# [[튜닝결과서]]&lt;br /&gt;
::::# [[인스펙션 결과서(선택)]]&lt;br /&gt;
&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
::::# [[데이터 이관 지원]]&lt;br /&gt;
::::# [[SQL 튜닝 (튜너)]]&lt;br /&gt;
::::# [[성능 모니터링]]&lt;br /&gt;
::::# [[성능 테스트 지원]]&lt;br /&gt;
::::# [[가용성 테스트]]&lt;br /&gt;
::::# [[백업복구테스트]]&lt;br /&gt;
&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
# [[운영자 매뉴얼]]&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[데이터 이관 사전/사후 작업]]&lt;br /&gt;
# [[이행후점검및운영 DB 전환]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[분류:DBA 방법론]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1598</id>
		<title>DBA 수행 방법론 (공공/대기업 SI 프로젝트)</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1598"/>
		<updated>2025-10-22T03:58:04Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 구축 단계 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=DBA 수행 방법론 (공공/대기업 SI 프로젝트) =&lt;br /&gt;
{{FAQ&lt;br /&gt;
|제목= *게시판 - [[https://dbstudy.co.kr/w/index.php?title=%ED%8A%B9%EC%88%98:WikiForum&amp;amp;forum=3| DBA 수행 방법론 묻고답하기(FAQ)]]&lt;br /&gt;
}}&lt;br /&gt;
== 개요 ==&lt;br /&gt;
&amp;lt;htmltag tagname=&amp;quot;iframe&amp;quot;frameborder=&amp;quot;0&amp;quot; width=&amp;quot;1024px&amp;quot; height=&amp;quot;768px&amp;quot; src=&amp;quot;https://viewer.diagrams.net/?tags=%7B%7D&amp;amp;lightbox=1&amp;amp;highlight=0000ff&amp;amp;edit=_blank&amp;amp;layers=1&amp;amp;nav=1&amp;amp;title=DBA%EB%8B%A8%EA%B3%84%EB%B3%84TASK%EC%99%80%EC%82%B0%EC%B6%9C%EB%AC%BC.drawio.png&amp;amp;transparent=1&amp;amp;dark=auto#R%3Cmxfile%3E%3Cdiagram%20name%3D%22%ED%8E%98%EC%9D%B4%EC%A7%80-1%22%20id%3D%22oy2hg6EIWq2iBE1s1H2V%22%3E7Vttb9s2EP41BLYPHSTrxeJHUXLabh2K1gUK7MugxqotTJEMmWmS%2Fvrd8UXvluXEdjdXQBBQZ%2FpIHp%2B7e3iUiRXcPb4uou3mz3wVp2RmrB6JFZLZjNou%2FEfBkxS4piEF6yJZSZFZCZbJ91gJdbf7ZBXvGh15nqc82TaFt3mWxbe8IYuKIn9odvuap81Rt9E67giWt1HalX5OVnwjpd5sXsnfxMl6o0c2XSo%2FuYt0Z7WS3SZa5Q81kbUgVlDkOZetu8cgTtF22i7yezd7Pi0nVsQZH%2FOFNx%2B4s1u8ZR9e27%2F%2FlVh%2FP83fp6%2FwC6jmW5TeqxWHzAcBWQTE8wj1yCIk1CUeFUJGmEF8KhozQh1s%2BCbx9Rr5kzYcLHeLTR59QRG73STp6l30lN9zLdVPbMejgqu9tw3snKdptN0l4qshSor49r7YJd%2Fij%2FFOdkRp%2FLiNspV6%2BJpnWonpwrNaWVzw%2BHGvzcxyJwDBcX4X8%2BIJuqgveAq9Crym3suHCgqurWSbGgzmrhJGCn7rUnW1Q9BQm3TMhlmdDRs2%2FUfEHNvkRfId7BOlyli7h%2BQujTIAb7RqiVgunBVFPN%2BqVhp%2F5bofL%2FJ%2F4iBP8wIkSbaJiwR38UvOeX6n90sZA9urIt9%2Biop1zOs7peYrnpM01fqyPEO4bPMk48J4DoM%2FMGdg%2FOYQB9YXwLNZPcMfdi94kGcwtSgRWxtHO%2F4Q79roopdF14DPdTGnQGaPxJh3NojZXYhBNPAtwgJsUJ9QW4UIuiDUFI058RwQfvKXf3QBqZA1GndtgOyDnIr60ZchKP3A6DLgwntjDu3uv3Xs%2FivlH9E22RqWeby6KOVxkUUctuU%2BW%2B06qCon%2FgKgOb1A82aYZKDBXEIDkWEChN6Eqhegyra6sCpJ1mlgNUrdBWBl96RI5CtAYmyBL5vQcEqaV5U0RzOzsuPJ06bdTZv%2B8tXbpciUPmEOhjHMo6YIb11AGji%2B6uypEAgUHLm1IVg4FXTcR%2BbNQk3HW0qAsFommr7K1PXBIbZiI0Tq7pc6DOLdqDaGWk9M0xDBVzYM9Sl0k6PJ0FyN9uk9W%2BhTg018OaglOoNOR4RvSRhEBzkBGrSXy2xccUUt5JRAT1CzgTh0gHJmnz4ldEP%2FPsc9kBJO5jJRmqwzeJCxhKHvJHA89ZVYBJsX%2BJV9GjIy6FbPYCMdfZfIG%2F10BLyKiVMw4FY57hwbCrqWxrNPvFA7YtB2xGEdkvAIZZJi9%2BoY48zSXR01JvX6lYlP1LTLoz10ZMrlPV%2B4cIiUXjlpJ9Lsm6WjFOPktA5pGCwYeIdmOTn0eR16NA98gUeP03eJA8a8%2F4DRzFATE7wmJji6fmKer0bndWAXsiY7gmhIZTR8v9xP72rdZcCsYu1%2BhYNZqVm0wWxkdpXWU4zoUWUfkLiajbUd6RcR7QP8g3SBKU8e3m8aua9NOYVqWVGmN7VcpIsAYuG%2F9q0cVusLfgnqvZKqhmIowSmVXU1NZIFcGv0ktaNdzJDVK1s3mnU6lYFauQz6ixrY4ABVPQMbrrAzNMAGnmDYkimABIY1K4LgibWA8fE40EjlyuIhpm91aHDEXjGR0ENlBkzDgeYi2myqjtcZIZj1lvSnZHxsrJqfhl0PharnsOu2vkvkYtpblQHge06t4SMzlv6KAY22fWyA1UrnMWoYr%2FNtgejqI01y%2B8%2BsEx29kAeMp6PPd4Fx%2Bi7gApbZ4wK1ZItFFWeio1dFR23jhxcmre4d%2FygSUvIgGXCxZGD2VAwxZBpN3uqLQp5RKX8ue4M4bFXFBsTJzfFDKlqDxE2WSmiDD1XFjjpp7JLxDiuuj%2BALk9TpbNnLODRaWXhhyHqpV9sTbaYDvL2Prxp7%2BOp55jIRxtOkS2v4fZSfqRxrda%2Fxlh%2Fe9cOv4X%2F6fEOticJdAJM%2FVUXR6rsjGAipE5e7Ii7n%2FPhLZsvt5XL7Dr6zulBTJHWinut4ycT9bkkmdJAVGFZUoKwjDXyvFo2Z8ANNQipayfB2t34qV0WuQzoOcZaBOT2XmClCM0ix9pUEj4gH15eM%2BrNPK0W9IBk5E0HSpui73XrJQWTcTfQzThUDiieHOK9D%2FFzsrK%2FGLAoLnq1cow9zEyn7H5Myd%2FR97%2Fne%2FDNORcrUGz0assgw6q%2F5MCE0ql%2FotF%2B%2FH3UbMmsKqzeAqhGOKNDJ1wPLK8TOO0vyskUXD8vr1cOk8Pozw7nP7XSiStpD%2B25eOvAsXzqdaRxKBwD3UN3cs%2FwO4b%2BHzHNzlmFkXglngcfq152ye%2FUTWWvxLw%3D%3D%3C%2Fdiagram%3E%3C%2Fmxfile%3E&amp;quot; allowtransparency=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;/htmltag&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
&lt;br /&gt;
==== [[분석단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[AS-IS 구조 분석 기술환경 파악]]&lt;br /&gt;
::::# [[요구사항 및 문제점 도출]]&lt;br /&gt;
::::# [[TO-BE 설계를 위한 기초자료 확보]]&lt;br /&gt;
&lt;br /&gt;
==== [[분석 단계 산출물]] ====&lt;br /&gt;
::::# [[인터뷰 계획서]]&lt;br /&gt;
::::# [[인터뷰 결과서]]&lt;br /&gt;
::::# [[요구사항 정의서]]&lt;br /&gt;
::::# [[현행 시스템 분석서]]&lt;br /&gt;
::::## 별첨1)현행시스템분석서-DB아키텍처)-별첨1-현행_데이터파일현황.xlsx&lt;br /&gt;
::::## 별첨2)현행시스템분석서-DB아키텍처)-별첨1-현행_테이블스페이스현황.xlsx&lt;br /&gt;
::::# [[아키텍처 정의서]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[설계 단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[시스템 작업]](OS, DISK, DB 파라미터, 리두로그그룹 등)&lt;br /&gt;
::::# [[DB설치 계획서]](설치위치,인스턴스명 등)&lt;br /&gt;
::::# [[초기파라미터 및 용량 산정]]&lt;br /&gt;
::::# [[TO-BE 데이터베이스 표준 및 구조, 용량, 보안, 백업/복구 설계]]&lt;br /&gt;
::::# [[물리모델링 검토 ]]( 파티션 대상 등)&lt;br /&gt;
&lt;br /&gt;
==== [[설계 단계 산출물]] ====&lt;br /&gt;
::::# [[명명규칙 정의서]]&lt;br /&gt;
::::# [[데이터베이스 설계서((표준/구조/용량/보안 /백업 복구 설계 등)]]&lt;br /&gt;
::::## [[데이터베이스설계서-DB아키텍처-별첨1.DB상세설계내역.xlsx]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[구축 단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[물리모델반영및 변경관리]]&lt;br /&gt;
::::# [[오브젝트 관리]]&lt;br /&gt;
::::# [[통합테스트 환경구성]]&lt;br /&gt;
::::# [[가용성 테스트 시나리오 작성]]&lt;br /&gt;
::::# [[백업복구테스트 시나리오 작성]]&lt;br /&gt;
&lt;br /&gt;
==== [[구축 단계 산출물]] ====&lt;br /&gt;
::::# [[SQL 작성 가이드]]&lt;br /&gt;
::::## 별첨)SQL작성가이드-DB아키텍처_DB접속정보.pptx&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
# [[가용성 테스트 결과서]]&lt;br /&gt;
# [[복구 테스트 결과서]]&lt;br /&gt;
# [[튜닝결과서]]&lt;br /&gt;
# [[인스펙션 결과서(선택)]]&lt;br /&gt;
&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[데이터 이관 지원]]&lt;br /&gt;
# [[SQL 튜닝 (튜너)]]&lt;br /&gt;
# [[성능 모니터링]]&lt;br /&gt;
# [[성능 테스트 지원]]&lt;br /&gt;
# [[가용성 테스트]]&lt;br /&gt;
# [[백업복구테스트]]&lt;br /&gt;
&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
# [[운영자 매뉴얼]]&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[데이터 이관 사전/사후 작업]]&lt;br /&gt;
# [[이행후점검및운영 DB 전환]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[분류:DBA 방법론]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1597</id>
		<title>DBA 수행 방법론 (공공/대기업 SI 프로젝트)</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1597"/>
		<updated>2025-10-22T03:57:44Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 구축 단계 주요 작업 TASK */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=DBA 수행 방법론 (공공/대기업 SI 프로젝트) =&lt;br /&gt;
{{FAQ&lt;br /&gt;
|제목= *게시판 - [[https://dbstudy.co.kr/w/index.php?title=%ED%8A%B9%EC%88%98:WikiForum&amp;amp;forum=3| DBA 수행 방법론 묻고답하기(FAQ)]]&lt;br /&gt;
}}&lt;br /&gt;
== 개요 ==&lt;br /&gt;
&amp;lt;htmltag tagname=&amp;quot;iframe&amp;quot;frameborder=&amp;quot;0&amp;quot; width=&amp;quot;1024px&amp;quot; height=&amp;quot;768px&amp;quot; src=&amp;quot;https://viewer.diagrams.net/?tags=%7B%7D&amp;amp;lightbox=1&amp;amp;highlight=0000ff&amp;amp;edit=_blank&amp;amp;layers=1&amp;amp;nav=1&amp;amp;title=DBA%EB%8B%A8%EA%B3%84%EB%B3%84TASK%EC%99%80%EC%82%B0%EC%B6%9C%EB%AC%BC.drawio.png&amp;amp;transparent=1&amp;amp;dark=auto#R%3Cmxfile%3E%3Cdiagram%20name%3D%22%ED%8E%98%EC%9D%B4%EC%A7%80-1%22%20id%3D%22oy2hg6EIWq2iBE1s1H2V%22%3E7Vttb9s2EP41BLYPHSTrxeJHUXLabh2K1gUK7MugxqotTJEMmWmS%2Fvrd8UXvluXEdjdXQBBQZ%2FpIHp%2B7e3iUiRXcPb4uou3mz3wVp2RmrB6JFZLZjNou%2FEfBkxS4piEF6yJZSZFZCZbJ91gJdbf7ZBXvGh15nqc82TaFt3mWxbe8IYuKIn9odvuap81Rt9E67giWt1HalX5OVnwjpd5sXsnfxMl6o0c2XSo%2FuYt0Z7WS3SZa5Q81kbUgVlDkOZetu8cgTtF22i7yezd7Pi0nVsQZH%2FOFNx%2B4s1u8ZR9e27%2F%2FlVh%2FP83fp6%2FwC6jmW5TeqxWHzAcBWQTE8wj1yCIk1CUeFUJGmEF8KhozQh1s%2BCbx9Rr5kzYcLHeLTR59QRG73STp6l30lN9zLdVPbMejgqu9tw3snKdptN0l4qshSor49r7YJd%2Fij%2FFOdkRp%2FLiNspV6%2BJpnWonpwrNaWVzw%2BHGvzcxyJwDBcX4X8%2BIJuqgveAq9Crym3suHCgqurWSbGgzmrhJGCn7rUnW1Q9BQm3TMhlmdDRs2%2FUfEHNvkRfId7BOlyli7h%2BQujTIAb7RqiVgunBVFPN%2BqVhp%2F5bofL%2FJ%2F4iBP8wIkSbaJiwR38UvOeX6n90sZA9urIt9%2Biop1zOs7peYrnpM01fqyPEO4bPMk48J4DoM%2FMGdg%2FOYQB9YXwLNZPcMfdi94kGcwtSgRWxtHO%2F4Q79roopdF14DPdTGnQGaPxJh3NojZXYhBNPAtwgJsUJ9QW4UIuiDUFI058RwQfvKXf3QBqZA1GndtgOyDnIr60ZchKP3A6DLgwntjDu3uv3Xs%2FivlH9E22RqWeby6KOVxkUUctuU%2BW%2B06qCon%2FgKgOb1A82aYZKDBXEIDkWEChN6Eqhegyra6sCpJ1mlgNUrdBWBl96RI5CtAYmyBL5vQcEqaV5U0RzOzsuPJ06bdTZv%2B8tXbpciUPmEOhjHMo6YIb11AGji%2B6uypEAgUHLm1IVg4FXTcR%2BbNQk3HW0qAsFommr7K1PXBIbZiI0Tq7pc6DOLdqDaGWk9M0xDBVzYM9Sl0k6PJ0FyN9uk9W%2BhTg018OaglOoNOR4RvSRhEBzkBGrSXy2xccUUt5JRAT1CzgTh0gHJmnz4ldEP%2FPsc9kBJO5jJRmqwzeJCxhKHvJHA89ZVYBJsX%2BJV9GjIy6FbPYCMdfZfIG%2F10BLyKiVMw4FY57hwbCrqWxrNPvFA7YtB2xGEdkvAIZZJi9%2BoY48zSXR01JvX6lYlP1LTLoz10ZMrlPV%2B4cIiUXjlpJ9Lsm6WjFOPktA5pGCwYeIdmOTn0eR16NA98gUeP03eJA8a8%2F4DRzFATE7wmJji6fmKer0bndWAXsiY7gmhIZTR8v9xP72rdZcCsYu1%2BhYNZqVm0wWxkdpXWU4zoUWUfkLiajbUd6RcR7QP8g3SBKU8e3m8aua9NOYVqWVGmN7VcpIsAYuG%2F9q0cVusLfgnqvZKqhmIowSmVXU1NZIFcGv0ktaNdzJDVK1s3mnU6lYFauQz6ixrY4ABVPQMbrrAzNMAGnmDYkimABIY1K4LgibWA8fE40EjlyuIhpm91aHDEXjGR0ENlBkzDgeYi2myqjtcZIZj1lvSnZHxsrJqfhl0PharnsOu2vkvkYtpblQHge06t4SMzlv6KAY22fWyA1UrnMWoYr%2FNtgejqI01y%2B8%2BsEx29kAeMp6PPd4Fx%2Bi7gApbZ4wK1ZItFFWeio1dFR23jhxcmre4d%2FygSUvIgGXCxZGD2VAwxZBpN3uqLQp5RKX8ue4M4bFXFBsTJzfFDKlqDxE2WSmiDD1XFjjpp7JLxDiuuj%2BALk9TpbNnLODRaWXhhyHqpV9sTbaYDvL2Prxp7%2BOp55jIRxtOkS2v4fZSfqRxrda%2Fxlh%2Fe9cOv4X%2F6fEOticJdAJM%2FVUXR6rsjGAipE5e7Ii7n%2FPhLZsvt5XL7Dr6zulBTJHWinut4ycT9bkkmdJAVGFZUoKwjDXyvFo2Z8ANNQipayfB2t34qV0WuQzoOcZaBOT2XmClCM0ix9pUEj4gH15eM%2BrNPK0W9IBk5E0HSpui73XrJQWTcTfQzThUDiieHOK9D%2FFzsrK%2FGLAoLnq1cow9zEyn7H5Myd%2FR97%2Fne%2FDNORcrUGz0assgw6q%2F5MCE0ql%2FotF%2B%2FH3UbMmsKqzeAqhGOKNDJ1wPLK8TOO0vyskUXD8vr1cOk8Pozw7nP7XSiStpD%2B25eOvAsXzqdaRxKBwD3UN3cs%2FwO4b%2BHzHNzlmFkXglngcfq152ye%2FUTWWvxLw%3D%3D%3C%2Fdiagram%3E%3C%2Fmxfile%3E&amp;quot; allowtransparency=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;/htmltag&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
&lt;br /&gt;
==== [[분석단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[AS-IS 구조 분석 기술환경 파악]]&lt;br /&gt;
::::# [[요구사항 및 문제점 도출]]&lt;br /&gt;
::::# [[TO-BE 설계를 위한 기초자료 확보]]&lt;br /&gt;
&lt;br /&gt;
==== [[분석 단계 산출물]] ====&lt;br /&gt;
::::# [[인터뷰 계획서]]&lt;br /&gt;
::::# [[인터뷰 결과서]]&lt;br /&gt;
::::# [[요구사항 정의서]]&lt;br /&gt;
::::# [[현행 시스템 분석서]]&lt;br /&gt;
::::## 별첨1)현행시스템분석서-DB아키텍처)-별첨1-현행_데이터파일현황.xlsx&lt;br /&gt;
::::## 별첨2)현행시스템분석서-DB아키텍처)-별첨1-현행_테이블스페이스현황.xlsx&lt;br /&gt;
::::# [[아키텍처 정의서]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[설계 단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[시스템 작업]](OS, DISK, DB 파라미터, 리두로그그룹 등)&lt;br /&gt;
::::# [[DB설치 계획서]](설치위치,인스턴스명 등)&lt;br /&gt;
::::# [[초기파라미터 및 용량 산정]]&lt;br /&gt;
::::# [[TO-BE 데이터베이스 표준 및 구조, 용량, 보안, 백업/복구 설계]]&lt;br /&gt;
::::# [[물리모델링 검토 ]]( 파티션 대상 등)&lt;br /&gt;
&lt;br /&gt;
==== [[설계 단계 산출물]] ====&lt;br /&gt;
::::# [[명명규칙 정의서]]&lt;br /&gt;
::::# [[데이터베이스 설계서((표준/구조/용량/보안 /백업 복구 설계 등)]]&lt;br /&gt;
::::## [[데이터베이스설계서-DB아키텍처-별첨1.DB상세설계내역.xlsx]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[구축 단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[물리모델반영및 변경관리]]&lt;br /&gt;
::::# [[오브젝트 관리]]&lt;br /&gt;
::::# [[통합테스트 환경구성]]&lt;br /&gt;
::::# [[가용성 테스트 시나리오 작성]]&lt;br /&gt;
::::# [[백업복구테스트 시나리오 작성]]&lt;br /&gt;
&lt;br /&gt;
==== [[구축 단계 산출물]] ====&lt;br /&gt;
# [[SQL 작성 가이드]]&lt;br /&gt;
## 별첨)SQL작성가이드-DB아키텍처_DB접속정보.pptx&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
# [[가용성 테스트 결과서]]&lt;br /&gt;
# [[복구 테스트 결과서]]&lt;br /&gt;
# [[튜닝결과서]]&lt;br /&gt;
# [[인스펙션 결과서(선택)]]&lt;br /&gt;
&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[데이터 이관 지원]]&lt;br /&gt;
# [[SQL 튜닝 (튜너)]]&lt;br /&gt;
# [[성능 모니터링]]&lt;br /&gt;
# [[성능 테스트 지원]]&lt;br /&gt;
# [[가용성 테스트]]&lt;br /&gt;
# [[백업복구테스트]]&lt;br /&gt;
&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
# [[운영자 매뉴얼]]&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[데이터 이관 사전/사후 작업]]&lt;br /&gt;
# [[이행후점검및운영 DB 전환]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[분류:DBA 방법론]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1596</id>
		<title>DBA 수행 방법론 (공공/대기업 SI 프로젝트)</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1596"/>
		<updated>2025-10-22T03:57:25Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 설계 단계 산출물 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=DBA 수행 방법론 (공공/대기업 SI 프로젝트) =&lt;br /&gt;
{{FAQ&lt;br /&gt;
|제목= *게시판 - [[https://dbstudy.co.kr/w/index.php?title=%ED%8A%B9%EC%88%98:WikiForum&amp;amp;forum=3| DBA 수행 방법론 묻고답하기(FAQ)]]&lt;br /&gt;
}}&lt;br /&gt;
== 개요 ==&lt;br /&gt;
&amp;lt;htmltag tagname=&amp;quot;iframe&amp;quot;frameborder=&amp;quot;0&amp;quot; width=&amp;quot;1024px&amp;quot; height=&amp;quot;768px&amp;quot; src=&amp;quot;https://viewer.diagrams.net/?tags=%7B%7D&amp;amp;lightbox=1&amp;amp;highlight=0000ff&amp;amp;edit=_blank&amp;amp;layers=1&amp;amp;nav=1&amp;amp;title=DBA%EB%8B%A8%EA%B3%84%EB%B3%84TASK%EC%99%80%EC%82%B0%EC%B6%9C%EB%AC%BC.drawio.png&amp;amp;transparent=1&amp;amp;dark=auto#R%3Cmxfile%3E%3Cdiagram%20name%3D%22%ED%8E%98%EC%9D%B4%EC%A7%80-1%22%20id%3D%22oy2hg6EIWq2iBE1s1H2V%22%3E7Vttb9s2EP41BLYPHSTrxeJHUXLabh2K1gUK7MugxqotTJEMmWmS%2Fvrd8UXvluXEdjdXQBBQZ%2FpIHp%2B7e3iUiRXcPb4uou3mz3wVp2RmrB6JFZLZjNou%2FEfBkxS4piEF6yJZSZFZCZbJ91gJdbf7ZBXvGh15nqc82TaFt3mWxbe8IYuKIn9odvuap81Rt9E67giWt1HalX5OVnwjpd5sXsnfxMl6o0c2XSo%2FuYt0Z7WS3SZa5Q81kbUgVlDkOZetu8cgTtF22i7yezd7Pi0nVsQZH%2FOFNx%2B4s1u8ZR9e27%2F%2FlVh%2FP83fp6%2FwC6jmW5TeqxWHzAcBWQTE8wj1yCIk1CUeFUJGmEF8KhozQh1s%2BCbx9Rr5kzYcLHeLTR59QRG73STp6l30lN9zLdVPbMejgqu9tw3snKdptN0l4qshSor49r7YJd%2Fij%2FFOdkRp%2FLiNspV6%2BJpnWonpwrNaWVzw%2BHGvzcxyJwDBcX4X8%2BIJuqgveAq9Crym3suHCgqurWSbGgzmrhJGCn7rUnW1Q9BQm3TMhlmdDRs2%2FUfEHNvkRfId7BOlyli7h%2BQujTIAb7RqiVgunBVFPN%2BqVhp%2F5bofL%2FJ%2F4iBP8wIkSbaJiwR38UvOeX6n90sZA9urIt9%2Biop1zOs7peYrnpM01fqyPEO4bPMk48J4DoM%2FMGdg%2FOYQB9YXwLNZPcMfdi94kGcwtSgRWxtHO%2F4Q79roopdF14DPdTGnQGaPxJh3NojZXYhBNPAtwgJsUJ9QW4UIuiDUFI058RwQfvKXf3QBqZA1GndtgOyDnIr60ZchKP3A6DLgwntjDu3uv3Xs%2FivlH9E22RqWeby6KOVxkUUctuU%2BW%2B06qCon%2FgKgOb1A82aYZKDBXEIDkWEChN6Eqhegyra6sCpJ1mlgNUrdBWBl96RI5CtAYmyBL5vQcEqaV5U0RzOzsuPJ06bdTZv%2B8tXbpciUPmEOhjHMo6YIb11AGji%2B6uypEAgUHLm1IVg4FXTcR%2BbNQk3HW0qAsFommr7K1PXBIbZiI0Tq7pc6DOLdqDaGWk9M0xDBVzYM9Sl0k6PJ0FyN9uk9W%2BhTg018OaglOoNOR4RvSRhEBzkBGrSXy2xccUUt5JRAT1CzgTh0gHJmnz4ldEP%2FPsc9kBJO5jJRmqwzeJCxhKHvJHA89ZVYBJsX%2BJV9GjIy6FbPYCMdfZfIG%2F10BLyKiVMw4FY57hwbCrqWxrNPvFA7YtB2xGEdkvAIZZJi9%2BoY48zSXR01JvX6lYlP1LTLoz10ZMrlPV%2B4cIiUXjlpJ9Lsm6WjFOPktA5pGCwYeIdmOTn0eR16NA98gUeP03eJA8a8%2F4DRzFATE7wmJji6fmKer0bndWAXsiY7gmhIZTR8v9xP72rdZcCsYu1%2BhYNZqVm0wWxkdpXWU4zoUWUfkLiajbUd6RcR7QP8g3SBKU8e3m8aua9NOYVqWVGmN7VcpIsAYuG%2F9q0cVusLfgnqvZKqhmIowSmVXU1NZIFcGv0ktaNdzJDVK1s3mnU6lYFauQz6ixrY4ABVPQMbrrAzNMAGnmDYkimABIY1K4LgibWA8fE40EjlyuIhpm91aHDEXjGR0ENlBkzDgeYi2myqjtcZIZj1lvSnZHxsrJqfhl0PharnsOu2vkvkYtpblQHge06t4SMzlv6KAY22fWyA1UrnMWoYr%2FNtgejqI01y%2B8%2BsEx29kAeMp6PPd4Fx%2Bi7gApbZ4wK1ZItFFWeio1dFR23jhxcmre4d%2FygSUvIgGXCxZGD2VAwxZBpN3uqLQp5RKX8ue4M4bFXFBsTJzfFDKlqDxE2WSmiDD1XFjjpp7JLxDiuuj%2BALk9TpbNnLODRaWXhhyHqpV9sTbaYDvL2Prxp7%2BOp55jIRxtOkS2v4fZSfqRxrda%2Fxlh%2Fe9cOv4X%2F6fEOticJdAJM%2FVUXR6rsjGAipE5e7Ii7n%2FPhLZsvt5XL7Dr6zulBTJHWinut4ycT9bkkmdJAVGFZUoKwjDXyvFo2Z8ANNQipayfB2t34qV0WuQzoOcZaBOT2XmClCM0ix9pUEj4gH15eM%2BrNPK0W9IBk5E0HSpui73XrJQWTcTfQzThUDiieHOK9D%2FFzsrK%2FGLAoLnq1cow9zEyn7H5Myd%2FR97%2Fne%2FDNORcrUGz0assgw6q%2F5MCE0ql%2FotF%2B%2FH3UbMmsKqzeAqhGOKNDJ1wPLK8TOO0vyskUXD8vr1cOk8Pozw7nP7XSiStpD%2B25eOvAsXzqdaRxKBwD3UN3cs%2FwO4b%2BHzHNzlmFkXglngcfq152ye%2FUTWWvxLw%3D%3D%3C%2Fdiagram%3E%3C%2Fmxfile%3E&amp;quot; allowtransparency=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;/htmltag&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
&lt;br /&gt;
==== [[분석단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[AS-IS 구조 분석 기술환경 파악]]&lt;br /&gt;
::::# [[요구사항 및 문제점 도출]]&lt;br /&gt;
::::# [[TO-BE 설계를 위한 기초자료 확보]]&lt;br /&gt;
&lt;br /&gt;
==== [[분석 단계 산출물]] ====&lt;br /&gt;
::::# [[인터뷰 계획서]]&lt;br /&gt;
::::# [[인터뷰 결과서]]&lt;br /&gt;
::::# [[요구사항 정의서]]&lt;br /&gt;
::::# [[현행 시스템 분석서]]&lt;br /&gt;
::::## 별첨1)현행시스템분석서-DB아키텍처)-별첨1-현행_데이터파일현황.xlsx&lt;br /&gt;
::::## 별첨2)현행시스템분석서-DB아키텍처)-별첨1-현행_테이블스페이스현황.xlsx&lt;br /&gt;
::::# [[아키텍처 정의서]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[설계 단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[시스템 작업]](OS, DISK, DB 파라미터, 리두로그그룹 등)&lt;br /&gt;
::::# [[DB설치 계획서]](설치위치,인스턴스명 등)&lt;br /&gt;
::::# [[초기파라미터 및 용량 산정]]&lt;br /&gt;
::::# [[TO-BE 데이터베이스 표준 및 구조, 용량, 보안, 백업/복구 설계]]&lt;br /&gt;
::::# [[물리모델링 검토 ]]( 파티션 대상 등)&lt;br /&gt;
&lt;br /&gt;
==== [[설계 단계 산출물]] ====&lt;br /&gt;
::::# [[명명규칙 정의서]]&lt;br /&gt;
::::# [[데이터베이스 설계서((표준/구조/용량/보안 /백업 복구 설계 등)]]&lt;br /&gt;
::::## [[데이터베이스설계서-DB아키텍처-별첨1.DB상세설계내역.xlsx]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[구축 단계 주요 작업 TASK]] ====&lt;br /&gt;
# [[물리모델반영및 변경관리]]&lt;br /&gt;
# [[오브젝트 관리]]&lt;br /&gt;
# [[통합테스트 환경구성]]&lt;br /&gt;
# [[가용성 테스트 시나리오 작성]]&lt;br /&gt;
# [[백업복구테스트 시나리오 작성]]&lt;br /&gt;
&lt;br /&gt;
==== [[구축 단계 산출물]] ====&lt;br /&gt;
# [[SQL 작성 가이드]]&lt;br /&gt;
## 별첨)SQL작성가이드-DB아키텍처_DB접속정보.pptx&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
# [[가용성 테스트 결과서]]&lt;br /&gt;
# [[복구 테스트 결과서]]&lt;br /&gt;
# [[튜닝결과서]]&lt;br /&gt;
# [[인스펙션 결과서(선택)]]&lt;br /&gt;
&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[데이터 이관 지원]]&lt;br /&gt;
# [[SQL 튜닝 (튜너)]]&lt;br /&gt;
# [[성능 모니터링]]&lt;br /&gt;
# [[성능 테스트 지원]]&lt;br /&gt;
# [[가용성 테스트]]&lt;br /&gt;
# [[백업복구테스트]]&lt;br /&gt;
&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
# [[운영자 매뉴얼]]&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[데이터 이관 사전/사후 작업]]&lt;br /&gt;
# [[이행후점검및운영 DB 전환]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[분류:DBA 방법론]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1595</id>
		<title>DBA 수행 방법론 (공공/대기업 SI 프로젝트)</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1595"/>
		<updated>2025-10-22T03:57:13Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 설계 단계 주요 작업 TASK */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=DBA 수행 방법론 (공공/대기업 SI 프로젝트) =&lt;br /&gt;
{{FAQ&lt;br /&gt;
|제목= *게시판 - [[https://dbstudy.co.kr/w/index.php?title=%ED%8A%B9%EC%88%98:WikiForum&amp;amp;forum=3| DBA 수행 방법론 묻고답하기(FAQ)]]&lt;br /&gt;
}}&lt;br /&gt;
== 개요 ==&lt;br /&gt;
&amp;lt;htmltag tagname=&amp;quot;iframe&amp;quot;frameborder=&amp;quot;0&amp;quot; width=&amp;quot;1024px&amp;quot; height=&amp;quot;768px&amp;quot; src=&amp;quot;https://viewer.diagrams.net/?tags=%7B%7D&amp;amp;lightbox=1&amp;amp;highlight=0000ff&amp;amp;edit=_blank&amp;amp;layers=1&amp;amp;nav=1&amp;amp;title=DBA%EB%8B%A8%EA%B3%84%EB%B3%84TASK%EC%99%80%EC%82%B0%EC%B6%9C%EB%AC%BC.drawio.png&amp;amp;transparent=1&amp;amp;dark=auto#R%3Cmxfile%3E%3Cdiagram%20name%3D%22%ED%8E%98%EC%9D%B4%EC%A7%80-1%22%20id%3D%22oy2hg6EIWq2iBE1s1H2V%22%3E7Vttb9s2EP41BLYPHSTrxeJHUXLabh2K1gUK7MugxqotTJEMmWmS%2Fvrd8UXvluXEdjdXQBBQZ%2FpIHp%2B7e3iUiRXcPb4uou3mz3wVp2RmrB6JFZLZjNou%2FEfBkxS4piEF6yJZSZFZCZbJ91gJdbf7ZBXvGh15nqc82TaFt3mWxbe8IYuKIn9odvuap81Rt9E67giWt1HalX5OVnwjpd5sXsnfxMl6o0c2XSo%2FuYt0Z7WS3SZa5Q81kbUgVlDkOZetu8cgTtF22i7yezd7Pi0nVsQZH%2FOFNx%2B4s1u8ZR9e27%2F%2FlVh%2FP83fp6%2FwC6jmW5TeqxWHzAcBWQTE8wj1yCIk1CUeFUJGmEF8KhozQh1s%2BCbx9Rr5kzYcLHeLTR59QRG73STp6l30lN9zLdVPbMejgqu9tw3snKdptN0l4qshSor49r7YJd%2Fij%2FFOdkRp%2FLiNspV6%2BJpnWonpwrNaWVzw%2BHGvzcxyJwDBcX4X8%2BIJuqgveAq9Crym3suHCgqurWSbGgzmrhJGCn7rUnW1Q9BQm3TMhlmdDRs2%2FUfEHNvkRfId7BOlyli7h%2BQujTIAb7RqiVgunBVFPN%2BqVhp%2F5bofL%2FJ%2F4iBP8wIkSbaJiwR38UvOeX6n90sZA9urIt9%2Biop1zOs7peYrnpM01fqyPEO4bPMk48J4DoM%2FMGdg%2FOYQB9YXwLNZPcMfdi94kGcwtSgRWxtHO%2F4Q79roopdF14DPdTGnQGaPxJh3NojZXYhBNPAtwgJsUJ9QW4UIuiDUFI058RwQfvKXf3QBqZA1GndtgOyDnIr60ZchKP3A6DLgwntjDu3uv3Xs%2FivlH9E22RqWeby6KOVxkUUctuU%2BW%2B06qCon%2FgKgOb1A82aYZKDBXEIDkWEChN6Eqhegyra6sCpJ1mlgNUrdBWBl96RI5CtAYmyBL5vQcEqaV5U0RzOzsuPJ06bdTZv%2B8tXbpciUPmEOhjHMo6YIb11AGji%2B6uypEAgUHLm1IVg4FXTcR%2BbNQk3HW0qAsFommr7K1PXBIbZiI0Tq7pc6DOLdqDaGWk9M0xDBVzYM9Sl0k6PJ0FyN9uk9W%2BhTg018OaglOoNOR4RvSRhEBzkBGrSXy2xccUUt5JRAT1CzgTh0gHJmnz4ldEP%2FPsc9kBJO5jJRmqwzeJCxhKHvJHA89ZVYBJsX%2BJV9GjIy6FbPYCMdfZfIG%2F10BLyKiVMw4FY57hwbCrqWxrNPvFA7YtB2xGEdkvAIZZJi9%2BoY48zSXR01JvX6lYlP1LTLoz10ZMrlPV%2B4cIiUXjlpJ9Lsm6WjFOPktA5pGCwYeIdmOTn0eR16NA98gUeP03eJA8a8%2F4DRzFATE7wmJji6fmKer0bndWAXsiY7gmhIZTR8v9xP72rdZcCsYu1%2BhYNZqVm0wWxkdpXWU4zoUWUfkLiajbUd6RcR7QP8g3SBKU8e3m8aua9NOYVqWVGmN7VcpIsAYuG%2F9q0cVusLfgnqvZKqhmIowSmVXU1NZIFcGv0ktaNdzJDVK1s3mnU6lYFauQz6ixrY4ABVPQMbrrAzNMAGnmDYkimABIY1K4LgibWA8fE40EjlyuIhpm91aHDEXjGR0ENlBkzDgeYi2myqjtcZIZj1lvSnZHxsrJqfhl0PharnsOu2vkvkYtpblQHge06t4SMzlv6KAY22fWyA1UrnMWoYr%2FNtgejqI01y%2B8%2BsEx29kAeMp6PPd4Fx%2Bi7gApbZ4wK1ZItFFWeio1dFR23jhxcmre4d%2FygSUvIgGXCxZGD2VAwxZBpN3uqLQp5RKX8ue4M4bFXFBsTJzfFDKlqDxE2WSmiDD1XFjjpp7JLxDiuuj%2BALk9TpbNnLODRaWXhhyHqpV9sTbaYDvL2Prxp7%2BOp55jIRxtOkS2v4fZSfqRxrda%2Fxlh%2Fe9cOv4X%2F6fEOticJdAJM%2FVUXR6rsjGAipE5e7Ii7n%2FPhLZsvt5XL7Dr6zulBTJHWinut4ycT9bkkmdJAVGFZUoKwjDXyvFo2Z8ANNQipayfB2t34qV0WuQzoOcZaBOT2XmClCM0ix9pUEj4gH15eM%2BrNPK0W9IBk5E0HSpui73XrJQWTcTfQzThUDiieHOK9D%2FFzsrK%2FGLAoLnq1cow9zEyn7H5Myd%2FR97%2Fne%2FDNORcrUGz0assgw6q%2F5MCE0ql%2FotF%2B%2FH3UbMmsKqzeAqhGOKNDJ1wPLK8TOO0vyskUXD8vr1cOk8Pozw7nP7XSiStpD%2B25eOvAsXzqdaRxKBwD3UN3cs%2FwO4b%2BHzHNzlmFkXglngcfq152ye%2FUTWWvxLw%3D%3D%3C%2Fdiagram%3E%3C%2Fmxfile%3E&amp;quot; allowtransparency=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;/htmltag&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
&lt;br /&gt;
==== [[분석단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[AS-IS 구조 분석 기술환경 파악]]&lt;br /&gt;
::::# [[요구사항 및 문제점 도출]]&lt;br /&gt;
::::# [[TO-BE 설계를 위한 기초자료 확보]]&lt;br /&gt;
&lt;br /&gt;
==== [[분석 단계 산출물]] ====&lt;br /&gt;
::::# [[인터뷰 계획서]]&lt;br /&gt;
::::# [[인터뷰 결과서]]&lt;br /&gt;
::::# [[요구사항 정의서]]&lt;br /&gt;
::::# [[현행 시스템 분석서]]&lt;br /&gt;
::::## 별첨1)현행시스템분석서-DB아키텍처)-별첨1-현행_데이터파일현황.xlsx&lt;br /&gt;
::::## 별첨2)현행시스템분석서-DB아키텍처)-별첨1-현행_테이블스페이스현황.xlsx&lt;br /&gt;
::::# [[아키텍처 정의서]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[설계 단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[시스템 작업]](OS, DISK, DB 파라미터, 리두로그그룹 등)&lt;br /&gt;
::::# [[DB설치 계획서]](설치위치,인스턴스명 등)&lt;br /&gt;
::::# [[초기파라미터 및 용량 산정]]&lt;br /&gt;
::::# [[TO-BE 데이터베이스 표준 및 구조, 용량, 보안, 백업/복구 설계]]&lt;br /&gt;
::::# [[물리모델링 검토 ]]( 파티션 대상 등)&lt;br /&gt;
&lt;br /&gt;
==== [[설계 단계 산출물]] ====&lt;br /&gt;
# [[명명규칙 정의서]]&lt;br /&gt;
# [[데이터베이스 설계서((표준/구조/용량/보안 /백업 복구 설계 등)]]&lt;br /&gt;
## [[데이터베이스설계서-DB아키텍처-별첨1.DB상세설계내역.xlsx]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[구축 단계 주요 작업 TASK]] ====&lt;br /&gt;
# [[물리모델반영및 변경관리]]&lt;br /&gt;
# [[오브젝트 관리]]&lt;br /&gt;
# [[통합테스트 환경구성]]&lt;br /&gt;
# [[가용성 테스트 시나리오 작성]]&lt;br /&gt;
# [[백업복구테스트 시나리오 작성]]&lt;br /&gt;
&lt;br /&gt;
==== [[구축 단계 산출물]] ====&lt;br /&gt;
# [[SQL 작성 가이드]]&lt;br /&gt;
## 별첨)SQL작성가이드-DB아키텍처_DB접속정보.pptx&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
# [[가용성 테스트 결과서]]&lt;br /&gt;
# [[복구 테스트 결과서]]&lt;br /&gt;
# [[튜닝결과서]]&lt;br /&gt;
# [[인스펙션 결과서(선택)]]&lt;br /&gt;
&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[데이터 이관 지원]]&lt;br /&gt;
# [[SQL 튜닝 (튜너)]]&lt;br /&gt;
# [[성능 모니터링]]&lt;br /&gt;
# [[성능 테스트 지원]]&lt;br /&gt;
# [[가용성 테스트]]&lt;br /&gt;
# [[백업복구테스트]]&lt;br /&gt;
&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
# [[운영자 매뉴얼]]&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[데이터 이관 사전/사후 작업]]&lt;br /&gt;
# [[이행후점검및운영 DB 전환]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[분류:DBA 방법론]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1594</id>
		<title>DBA 수행 방법론 (공공/대기업 SI 프로젝트)</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1594"/>
		<updated>2025-10-22T03:56:58Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 분석 단계 산출물 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=DBA 수행 방법론 (공공/대기업 SI 프로젝트) =&lt;br /&gt;
{{FAQ&lt;br /&gt;
|제목= *게시판 - [[https://dbstudy.co.kr/w/index.php?title=%ED%8A%B9%EC%88%98:WikiForum&amp;amp;forum=3| DBA 수행 방법론 묻고답하기(FAQ)]]&lt;br /&gt;
}}&lt;br /&gt;
== 개요 ==&lt;br /&gt;
&amp;lt;htmltag tagname=&amp;quot;iframe&amp;quot;frameborder=&amp;quot;0&amp;quot; width=&amp;quot;1024px&amp;quot; height=&amp;quot;768px&amp;quot; src=&amp;quot;https://viewer.diagrams.net/?tags=%7B%7D&amp;amp;lightbox=1&amp;amp;highlight=0000ff&amp;amp;edit=_blank&amp;amp;layers=1&amp;amp;nav=1&amp;amp;title=DBA%EB%8B%A8%EA%B3%84%EB%B3%84TASK%EC%99%80%EC%82%B0%EC%B6%9C%EB%AC%BC.drawio.png&amp;amp;transparent=1&amp;amp;dark=auto#R%3Cmxfile%3E%3Cdiagram%20name%3D%22%ED%8E%98%EC%9D%B4%EC%A7%80-1%22%20id%3D%22oy2hg6EIWq2iBE1s1H2V%22%3E7Vttb9s2EP41BLYPHSTrxeJHUXLabh2K1gUK7MugxqotTJEMmWmS%2Fvrd8UXvluXEdjdXQBBQZ%2FpIHp%2B7e3iUiRXcPb4uou3mz3wVp2RmrB6JFZLZjNou%2FEfBkxS4piEF6yJZSZFZCZbJ91gJdbf7ZBXvGh15nqc82TaFt3mWxbe8IYuKIn9odvuap81Rt9E67giWt1HalX5OVnwjpd5sXsnfxMl6o0c2XSo%2FuYt0Z7WS3SZa5Q81kbUgVlDkOZetu8cgTtF22i7yezd7Pi0nVsQZH%2FOFNx%2B4s1u8ZR9e27%2F%2FlVh%2FP83fp6%2FwC6jmW5TeqxWHzAcBWQTE8wj1yCIk1CUeFUJGmEF8KhozQh1s%2BCbx9Rr5kzYcLHeLTR59QRG73STp6l30lN9zLdVPbMejgqu9tw3snKdptN0l4qshSor49r7YJd%2Fij%2FFOdkRp%2FLiNspV6%2BJpnWonpwrNaWVzw%2BHGvzcxyJwDBcX4X8%2BIJuqgveAq9Crym3suHCgqurWSbGgzmrhJGCn7rUnW1Q9BQm3TMhlmdDRs2%2FUfEHNvkRfId7BOlyli7h%2BQujTIAb7RqiVgunBVFPN%2BqVhp%2F5bofL%2FJ%2F4iBP8wIkSbaJiwR38UvOeX6n90sZA9urIt9%2Biop1zOs7peYrnpM01fqyPEO4bPMk48J4DoM%2FMGdg%2FOYQB9YXwLNZPcMfdi94kGcwtSgRWxtHO%2F4Q79roopdF14DPdTGnQGaPxJh3NojZXYhBNPAtwgJsUJ9QW4UIuiDUFI058RwQfvKXf3QBqZA1GndtgOyDnIr60ZchKP3A6DLgwntjDu3uv3Xs%2FivlH9E22RqWeby6KOVxkUUctuU%2BW%2B06qCon%2FgKgOb1A82aYZKDBXEIDkWEChN6Eqhegyra6sCpJ1mlgNUrdBWBl96RI5CtAYmyBL5vQcEqaV5U0RzOzsuPJ06bdTZv%2B8tXbpciUPmEOhjHMo6YIb11AGji%2B6uypEAgUHLm1IVg4FXTcR%2BbNQk3HW0qAsFommr7K1PXBIbZiI0Tq7pc6DOLdqDaGWk9M0xDBVzYM9Sl0k6PJ0FyN9uk9W%2BhTg018OaglOoNOR4RvSRhEBzkBGrSXy2xccUUt5JRAT1CzgTh0gHJmnz4ldEP%2FPsc9kBJO5jJRmqwzeJCxhKHvJHA89ZVYBJsX%2BJV9GjIy6FbPYCMdfZfIG%2F10BLyKiVMw4FY57hwbCrqWxrNPvFA7YtB2xGEdkvAIZZJi9%2BoY48zSXR01JvX6lYlP1LTLoz10ZMrlPV%2B4cIiUXjlpJ9Lsm6WjFOPktA5pGCwYeIdmOTn0eR16NA98gUeP03eJA8a8%2F4DRzFATE7wmJji6fmKer0bndWAXsiY7gmhIZTR8v9xP72rdZcCsYu1%2BhYNZqVm0wWxkdpXWU4zoUWUfkLiajbUd6RcR7QP8g3SBKU8e3m8aua9NOYVqWVGmN7VcpIsAYuG%2F9q0cVusLfgnqvZKqhmIowSmVXU1NZIFcGv0ktaNdzJDVK1s3mnU6lYFauQz6ixrY4ABVPQMbrrAzNMAGnmDYkimABIY1K4LgibWA8fE40EjlyuIhpm91aHDEXjGR0ENlBkzDgeYi2myqjtcZIZj1lvSnZHxsrJqfhl0PharnsOu2vkvkYtpblQHge06t4SMzlv6KAY22fWyA1UrnMWoYr%2FNtgejqI01y%2B8%2BsEx29kAeMp6PPd4Fx%2Bi7gApbZ4wK1ZItFFWeio1dFR23jhxcmre4d%2FygSUvIgGXCxZGD2VAwxZBpN3uqLQp5RKX8ue4M4bFXFBsTJzfFDKlqDxE2WSmiDD1XFjjpp7JLxDiuuj%2BALk9TpbNnLODRaWXhhyHqpV9sTbaYDvL2Prxp7%2BOp55jIRxtOkS2v4fZSfqRxrda%2Fxlh%2Fe9cOv4X%2F6fEOticJdAJM%2FVUXR6rsjGAipE5e7Ii7n%2FPhLZsvt5XL7Dr6zulBTJHWinut4ycT9bkkmdJAVGFZUoKwjDXyvFo2Z8ANNQipayfB2t34qV0WuQzoOcZaBOT2XmClCM0ix9pUEj4gH15eM%2BrNPK0W9IBk5E0HSpui73XrJQWTcTfQzThUDiieHOK9D%2FFzsrK%2FGLAoLnq1cow9zEyn7H5Myd%2FR97%2Fne%2FDNORcrUGz0assgw6q%2F5MCE0ql%2FotF%2B%2FH3UbMmsKqzeAqhGOKNDJ1wPLK8TOO0vyskUXD8vr1cOk8Pozw7nP7XSiStpD%2B25eOvAsXzqdaRxKBwD3UN3cs%2FwO4b%2BHzHNzlmFkXglngcfq152ye%2FUTWWvxLw%3D%3D%3C%2Fdiagram%3E%3C%2Fmxfile%3E&amp;quot; allowtransparency=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;/htmltag&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
&lt;br /&gt;
==== [[분석단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[AS-IS 구조 분석 기술환경 파악]]&lt;br /&gt;
::::# [[요구사항 및 문제점 도출]]&lt;br /&gt;
::::# [[TO-BE 설계를 위한 기초자료 확보]]&lt;br /&gt;
&lt;br /&gt;
==== [[분석 단계 산출물]] ====&lt;br /&gt;
::::# [[인터뷰 계획서]]&lt;br /&gt;
::::# [[인터뷰 결과서]]&lt;br /&gt;
::::# [[요구사항 정의서]]&lt;br /&gt;
::::# [[현행 시스템 분석서]]&lt;br /&gt;
::::## 별첨1)현행시스템분석서-DB아키텍처)-별첨1-현행_데이터파일현황.xlsx&lt;br /&gt;
::::## 별첨2)현행시스템분석서-DB아키텍처)-별첨1-현행_테이블스페이스현황.xlsx&lt;br /&gt;
::::# [[아키텍처 정의서]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[설계 단계 주요 작업 TASK]] ====&lt;br /&gt;
# [[시스템 작업]](OS, DISK, DB 파라미터, 리두로그그룹 등)&lt;br /&gt;
# [[DB설치 계획서]](설치위치,인스턴스명 등)&lt;br /&gt;
# [[초기파라미터 및 용량 산정]]&lt;br /&gt;
# [[TO-BE 데이터베이스 표준 및 구조, 용량, 보안, 백업/복구 설계]]&lt;br /&gt;
# [[물리모델링 검토 ]]( 파티션 대상 등)&lt;br /&gt;
==== [[설계 단계 산출물]] ====&lt;br /&gt;
# [[명명규칙 정의서]]&lt;br /&gt;
# [[데이터베이스 설계서((표준/구조/용량/보안 /백업 복구 설계 등)]]&lt;br /&gt;
## [[데이터베이스설계서-DB아키텍처-별첨1.DB상세설계내역.xlsx]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[구축 단계 주요 작업 TASK]] ====&lt;br /&gt;
# [[물리모델반영및 변경관리]]&lt;br /&gt;
# [[오브젝트 관리]]&lt;br /&gt;
# [[통합테스트 환경구성]]&lt;br /&gt;
# [[가용성 테스트 시나리오 작성]]&lt;br /&gt;
# [[백업복구테스트 시나리오 작성]]&lt;br /&gt;
&lt;br /&gt;
==== [[구축 단계 산출물]] ====&lt;br /&gt;
# [[SQL 작성 가이드]]&lt;br /&gt;
## 별첨)SQL작성가이드-DB아키텍처_DB접속정보.pptx&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
# [[가용성 테스트 결과서]]&lt;br /&gt;
# [[복구 테스트 결과서]]&lt;br /&gt;
# [[튜닝결과서]]&lt;br /&gt;
# [[인스펙션 결과서(선택)]]&lt;br /&gt;
&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[데이터 이관 지원]]&lt;br /&gt;
# [[SQL 튜닝 (튜너)]]&lt;br /&gt;
# [[성능 모니터링]]&lt;br /&gt;
# [[성능 테스트 지원]]&lt;br /&gt;
# [[가용성 테스트]]&lt;br /&gt;
# [[백업복구테스트]]&lt;br /&gt;
&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
# [[운영자 매뉴얼]]&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[데이터 이관 사전/사후 작업]]&lt;br /&gt;
# [[이행후점검및운영 DB 전환]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[분류:DBA 방법론]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1593</id>
		<title>DBA 수행 방법론 (공공/대기업 SI 프로젝트)</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=DBA_%EC%88%98%ED%96%89_%EB%B0%A9%EB%B2%95%EB%A1%A0_(%EA%B3%B5%EA%B3%B5/%EB%8C%80%EA%B8%B0%EC%97%85_SI_%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8)&amp;diff=1593"/>
		<updated>2025-10-22T03:56:38Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 분석단계 주요 작업 TASK */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=DBA 수행 방법론 (공공/대기업 SI 프로젝트) =&lt;br /&gt;
{{FAQ&lt;br /&gt;
|제목= *게시판 - [[https://dbstudy.co.kr/w/index.php?title=%ED%8A%B9%EC%88%98:WikiForum&amp;amp;forum=3| DBA 수행 방법론 묻고답하기(FAQ)]]&lt;br /&gt;
}}&lt;br /&gt;
== 개요 ==&lt;br /&gt;
&amp;lt;htmltag tagname=&amp;quot;iframe&amp;quot;frameborder=&amp;quot;0&amp;quot; width=&amp;quot;1024px&amp;quot; height=&amp;quot;768px&amp;quot; src=&amp;quot;https://viewer.diagrams.net/?tags=%7B%7D&amp;amp;lightbox=1&amp;amp;highlight=0000ff&amp;amp;edit=_blank&amp;amp;layers=1&amp;amp;nav=1&amp;amp;title=DBA%EB%8B%A8%EA%B3%84%EB%B3%84TASK%EC%99%80%EC%82%B0%EC%B6%9C%EB%AC%BC.drawio.png&amp;amp;transparent=1&amp;amp;dark=auto#R%3Cmxfile%3E%3Cdiagram%20name%3D%22%ED%8E%98%EC%9D%B4%EC%A7%80-1%22%20id%3D%22oy2hg6EIWq2iBE1s1H2V%22%3E7Vttb9s2EP41BLYPHSTrxeJHUXLabh2K1gUK7MugxqotTJEMmWmS%2Fvrd8UXvluXEdjdXQBBQZ%2FpIHp%2B7e3iUiRXcPb4uou3mz3wVp2RmrB6JFZLZjNou%2FEfBkxS4piEF6yJZSZFZCZbJ91gJdbf7ZBXvGh15nqc82TaFt3mWxbe8IYuKIn9odvuap81Rt9E67giWt1HalX5OVnwjpd5sXsnfxMl6o0c2XSo%2FuYt0Z7WS3SZa5Q81kbUgVlDkOZetu8cgTtF22i7yezd7Pi0nVsQZH%2FOFNx%2B4s1u8ZR9e27%2F%2FlVh%2FP83fp6%2FwC6jmW5TeqxWHzAcBWQTE8wj1yCIk1CUeFUJGmEF8KhozQh1s%2BCbx9Rr5kzYcLHeLTR59QRG73STp6l30lN9zLdVPbMejgqu9tw3snKdptN0l4qshSor49r7YJd%2Fij%2FFOdkRp%2FLiNspV6%2BJpnWonpwrNaWVzw%2BHGvzcxyJwDBcX4X8%2BIJuqgveAq9Crym3suHCgqurWSbGgzmrhJGCn7rUnW1Q9BQm3TMhlmdDRs2%2FUfEHNvkRfId7BOlyli7h%2BQujTIAb7RqiVgunBVFPN%2BqVhp%2F5bofL%2FJ%2F4iBP8wIkSbaJiwR38UvOeX6n90sZA9urIt9%2Biop1zOs7peYrnpM01fqyPEO4bPMk48J4DoM%2FMGdg%2FOYQB9YXwLNZPcMfdi94kGcwtSgRWxtHO%2F4Q79roopdF14DPdTGnQGaPxJh3NojZXYhBNPAtwgJsUJ9QW4UIuiDUFI058RwQfvKXf3QBqZA1GndtgOyDnIr60ZchKP3A6DLgwntjDu3uv3Xs%2FivlH9E22RqWeby6KOVxkUUctuU%2BW%2B06qCon%2FgKgOb1A82aYZKDBXEIDkWEChN6Eqhegyra6sCpJ1mlgNUrdBWBl96RI5CtAYmyBL5vQcEqaV5U0RzOzsuPJ06bdTZv%2B8tXbpciUPmEOhjHMo6YIb11AGji%2B6uypEAgUHLm1IVg4FXTcR%2BbNQk3HW0qAsFommr7K1PXBIbZiI0Tq7pc6DOLdqDaGWk9M0xDBVzYM9Sl0k6PJ0FyN9uk9W%2BhTg018OaglOoNOR4RvSRhEBzkBGrSXy2xccUUt5JRAT1CzgTh0gHJmnz4ldEP%2FPsc9kBJO5jJRmqwzeJCxhKHvJHA89ZVYBJsX%2BJV9GjIy6FbPYCMdfZfIG%2F10BLyKiVMw4FY57hwbCrqWxrNPvFA7YtB2xGEdkvAIZZJi9%2BoY48zSXR01JvX6lYlP1LTLoz10ZMrlPV%2B4cIiUXjlpJ9Lsm6WjFOPktA5pGCwYeIdmOTn0eR16NA98gUeP03eJA8a8%2F4DRzFATE7wmJji6fmKer0bndWAXsiY7gmhIZTR8v9xP72rdZcCsYu1%2BhYNZqVm0wWxkdpXWU4zoUWUfkLiajbUd6RcR7QP8g3SBKU8e3m8aua9NOYVqWVGmN7VcpIsAYuG%2F9q0cVusLfgnqvZKqhmIowSmVXU1NZIFcGv0ktaNdzJDVK1s3mnU6lYFauQz6ixrY4ABVPQMbrrAzNMAGnmDYkimABIY1K4LgibWA8fE40EjlyuIhpm91aHDEXjGR0ENlBkzDgeYi2myqjtcZIZj1lvSnZHxsrJqfhl0PharnsOu2vkvkYtpblQHge06t4SMzlv6KAY22fWyA1UrnMWoYr%2FNtgejqI01y%2B8%2BsEx29kAeMp6PPd4Fx%2Bi7gApbZ4wK1ZItFFWeio1dFR23jhxcmre4d%2FygSUvIgGXCxZGD2VAwxZBpN3uqLQp5RKX8ue4M4bFXFBsTJzfFDKlqDxE2WSmiDD1XFjjpp7JLxDiuuj%2BALk9TpbNnLODRaWXhhyHqpV9sTbaYDvL2Prxp7%2BOp55jIRxtOkS2v4fZSfqRxrda%2Fxlh%2Fe9cOv4X%2F6fEOticJdAJM%2FVUXR6rsjGAipE5e7Ii7n%2FPhLZsvt5XL7Dr6zulBTJHWinut4ycT9bkkmdJAVGFZUoKwjDXyvFo2Z8ANNQipayfB2t34qV0WuQzoOcZaBOT2XmClCM0ix9pUEj4gH15eM%2BrNPK0W9IBk5E0HSpui73XrJQWTcTfQzThUDiieHOK9D%2FFzsrK%2FGLAoLnq1cow9zEyn7H5Myd%2FR97%2Fne%2FDNORcrUGz0assgw6q%2F5MCE0ql%2FotF%2B%2FH3UbMmsKqzeAqhGOKNDJ1wPLK8TOO0vyskUXD8vr1cOk8Pozw7nP7XSiStpD%2B25eOvAsXzqdaRxKBwD3UN3cs%2FwO4b%2BHzHNzlmFkXglngcfq152ye%2FUTWWvxLw%3D%3D%3C%2Fdiagram%3E%3C%2Fmxfile%3E&amp;quot; allowtransparency=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;/htmltag&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
&lt;br /&gt;
==== [[분석단계 주요 작업 TASK]] ====&lt;br /&gt;
::::# [[AS-IS 구조 분석 기술환경 파악]]&lt;br /&gt;
::::# [[요구사항 및 문제점 도출]]&lt;br /&gt;
::::# [[TO-BE 설계를 위한 기초자료 확보]]&lt;br /&gt;
&lt;br /&gt;
==== [[분석 단계 산출물]] ====&lt;br /&gt;
# [[인터뷰 계획서]]&lt;br /&gt;
# [[인터뷰 결과서]]&lt;br /&gt;
# [[요구사항 정의서]]&lt;br /&gt;
# [[현행 시스템 분석서]]&lt;br /&gt;
## 별첨1)현행시스템분석서-DB아키텍처)-별첨1-현행_데이터파일현황.xlsx&lt;br /&gt;
## 별첨2)현행시스템분석서-DB아키텍처)-별첨1-현행_테이블스페이스현황.xlsx&lt;br /&gt;
# [[아키텍처 정의서]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[설계 단계 주요 작업 TASK]] ====&lt;br /&gt;
# [[시스템 작업]](OS, DISK, DB 파라미터, 리두로그그룹 등)&lt;br /&gt;
# [[DB설치 계획서]](설치위치,인스턴스명 등)&lt;br /&gt;
# [[초기파라미터 및 용량 산정]]&lt;br /&gt;
# [[TO-BE 데이터베이스 표준 및 구조, 용량, 보안, 백업/복구 설계]]&lt;br /&gt;
# [[물리모델링 검토 ]]( 파티션 대상 등)&lt;br /&gt;
==== [[설계 단계 산출물]] ====&lt;br /&gt;
# [[명명규칙 정의서]]&lt;br /&gt;
# [[데이터베이스 설계서((표준/구조/용량/보안 /백업 복구 설계 등)]]&lt;br /&gt;
## [[데이터베이스설계서-DB아키텍처-별첨1.DB상세설계내역.xlsx]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[구축 단계 주요 작업 TASK]] ====&lt;br /&gt;
# [[물리모델반영및 변경관리]]&lt;br /&gt;
# [[오브젝트 관리]]&lt;br /&gt;
# [[통합테스트 환경구성]]&lt;br /&gt;
# [[가용성 테스트 시나리오 작성]]&lt;br /&gt;
# [[백업복구테스트 시나리오 작성]]&lt;br /&gt;
&lt;br /&gt;
==== [[구축 단계 산출물]] ====&lt;br /&gt;
# [[SQL 작성 가이드]]&lt;br /&gt;
## 별첨)SQL작성가이드-DB아키텍처_DB접속정보.pptx&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
# [[가용성 테스트 결과서]]&lt;br /&gt;
# [[복구 테스트 결과서]]&lt;br /&gt;
# [[튜닝결과서]]&lt;br /&gt;
# [[인스펙션 결과서(선택)]]&lt;br /&gt;
&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[데이터 이관 지원]]&lt;br /&gt;
# [[SQL 튜닝 (튜너)]]&lt;br /&gt;
# [[성능 모니터링]]&lt;br /&gt;
# [[성능 테스트 지원]]&lt;br /&gt;
# [[가용성 테스트]]&lt;br /&gt;
# [[백업복구테스트]]&lt;br /&gt;
&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
# [[운영자 매뉴얼]]&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
# [[데이터 이관 사전/사후 작업]]&lt;br /&gt;
# [[이행후점검및운영 DB 전환]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[분류:DBA 방법론]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=MERGE_%EC%A1%B0%EC%9D%B8&amp;diff=1592</id>
		<title>MERGE 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=MERGE_%EC%A1%B0%EC%9D%B8&amp;diff=1592"/>
		<updated>2025-10-22T03:25:12Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* MERGE 조인 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== MERGE 조인 ==&lt;br /&gt;
{{틀:핵심&lt;br /&gt;
|제목=&#039;&#039;&#039; &amp;lt;big&amp;gt;SORT MERGE 조인의 영문의 뜻 : SORT(정렬) 후 MERGE(합치다) 의 의미 &amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
|내용= &#039;&#039;&#039; &amp;lt;big&amp;gt;정확한 명칭은 &#039;MERGE JOIN&#039; 또는 &#039;SORT MERGE JOIN&#039;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== MERGE 조인이 필요한 경우 ===&lt;br /&gt;
# 조인에 사용되는 양쪽 테이블이 이미 정렬되어 있거나, 정렬 비용이 크지 않은 경우&lt;br /&gt;
# 대량의 데이터에 대해 효율적인 조인이 필요할 때&lt;br /&gt;
&lt;br /&gt;
===  MERGE 조인 동작 원리 ===&lt;br /&gt;
# 조인에 참여하는 두 테이블의 조인 컬럼을 기준으로 정렬(sorting).&lt;br /&gt;
# 두 데이터 집합을 &#039;&#039;&#039;동시에 순차적&#039;&#039;&#039; 으로 탐색하면서 일치하는 조건이 있는 행을 찾아 조인 결과를 만듭니다.&lt;br /&gt;
# 두 개의 정렬된 리스트를 한 번에 훑는 것과 비슷하게 동작합니다.&lt;br /&gt;
&lt;br /&gt;
=== 장점 ===&lt;br /&gt;
# 대량 데이터 조인에 효율적&lt;br /&gt;
# 인덱스가 없는 경우에도 성능이 좋은 편임&lt;br /&gt;
&lt;br /&gt;
=== 단점 ===&lt;br /&gt;
# SORT 단계가 필요하므로 메모리 및 디스크 I/O를 소모&lt;br /&gt;
# 작은 데이터셋에는 HASH JOIN이 더 빠를 수 있음&lt;br /&gt;
&lt;br /&gt;
=== MERGE 조인 사용 예시 ===&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT /*+ USE_MERGE(a b) */ *&lt;br /&gt;
FROM table_a a&lt;br /&gt;
JOIN table_b b ON a.id = b.id;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
=== 파이썬으로 MERGE JOIN 구현 ===&lt;br /&gt;
&lt;br /&gt;
* 두 리스트(또는 테이블)가 각각 다음과 같이 주어졌다고 가정&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# table_a: (id, value)&lt;br /&gt;
# table_b: (id, desc)&lt;br /&gt;
table_a = [&lt;br /&gt;
    (1, &#039;apple&#039;),&lt;br /&gt;
    (2, &#039;banana&#039;),&lt;br /&gt;
    (4, &#039;melon&#039;),&lt;br /&gt;
    (5, &#039;peach&#039;)&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
table_b = [&lt;br /&gt;
    (1, &#039;red&#039;),&lt;br /&gt;
    (2, &#039;yellow&#039;),&lt;br /&gt;
    (3, &#039;green&#039;),&lt;br /&gt;
    (4, &#039;green&#039;)&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
join_result = merge_join(table_a, table_b)&lt;br /&gt;
for row in join_result:&lt;br /&gt;
    print(row)&lt;br /&gt;
&lt;br /&gt;
def merge_join(table_a, table_b):&lt;br /&gt;
    # 두 테이블을 id 기준으로 정렬 (이미 정렬되어 있다고 가정)&lt;br /&gt;
    # 만약 정렬이 안 되어 있다면 아래 코드를 참고:&lt;br /&gt;
    # table_a = sorted(table_a, key=lambda x: x[0])&lt;br /&gt;
    # table_b = sorted(table_b, key=lambda x: x[0])&lt;br /&gt;
    &lt;br /&gt;
    i, j = 0, 0&lt;br /&gt;
    result = []&lt;br /&gt;
    &lt;br /&gt;
    while i &amp;lt; len(table_a) and j &amp;lt; len(table_b):&lt;br /&gt;
        id_a = table_a[i][0]&lt;br /&gt;
        id_b = table_b[j][0]&lt;br /&gt;
        &lt;br /&gt;
        if id_a == id_b:&lt;br /&gt;
            # 조건이 일치할 때 JOIN 결과에 추가&lt;br /&gt;
            result.append((id_a, table_a[i][1], table_b[j][1]))&lt;br /&gt;
            i += 1&lt;br /&gt;
            j += 1&lt;br /&gt;
        elif id_a &amp;lt; id_b:&lt;br /&gt;
            i += 1&lt;br /&gt;
        else:&lt;br /&gt;
            j += 1&lt;br /&gt;
    &lt;br /&gt;
    return result&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
* 실행 결과:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
(1, &#039;apple&#039;, &#039;red&#039;)&lt;br /&gt;
(2, &#039;banana&#039;, &#039;yellow&#039;)&lt;br /&gt;
(4, &#039;melon&#039;, &#039;green&#039;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 설명 ===&lt;br /&gt;
# 양쪽 리스트가 이미 정렬되어 있다는 가정 하에 두 인덱스를 비교해 가면서 일치하는 id를 찾고, 일치하지 않으면 더 작은 쪽 인덱스만 증가시켜 탐색하는 방식.&lt;br /&gt;
# Oracle의 MERGE JOIN과 거의 동일한 논리적 방식입니다.&lt;br /&gt;
&lt;br /&gt;
[[category:oracle]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=MERGE_%EC%A1%B0%EC%9D%B8&amp;diff=1591</id>
		<title>MERGE 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=MERGE_%EC%A1%B0%EC%9D%B8&amp;diff=1591"/>
		<updated>2025-10-22T03:24:56Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== MERGE 조인 ==&lt;br /&gt;
* 정확한 명칭은 &#039;MERGE JOIN&#039; 또는 &#039;SORT MERGE JOIN&#039;&lt;br /&gt;
* SORT(정렬) 후 MERGE(합치다) 의 의미  &lt;br /&gt;
{{틀:핵심&lt;br /&gt;
|제목=&#039;&#039;&#039; &amp;lt;big&amp;gt;SORT MERGE 조인의 영문의 뜻 : SORT(정렬) 후 MERGE(합치다) 의 의미 &amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
|내용= &#039;&#039;&#039; &amp;lt;big&amp;gt;정확한 명칭은 &#039;MERGE JOIN&#039; 또는 &#039;SORT MERGE JOIN&#039;&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== MERGE 조인이 필요한 경우 ===&lt;br /&gt;
# 조인에 사용되는 양쪽 테이블이 이미 정렬되어 있거나, 정렬 비용이 크지 않은 경우&lt;br /&gt;
# 대량의 데이터에 대해 효율적인 조인이 필요할 때&lt;br /&gt;
&lt;br /&gt;
===  MERGE 조인 동작 원리 ===&lt;br /&gt;
# 조인에 참여하는 두 테이블의 조인 컬럼을 기준으로 정렬(sorting).&lt;br /&gt;
# 두 데이터 집합을 &#039;&#039;&#039;동시에 순차적&#039;&#039;&#039; 으로 탐색하면서 일치하는 조건이 있는 행을 찾아 조인 결과를 만듭니다.&lt;br /&gt;
# 두 개의 정렬된 리스트를 한 번에 훑는 것과 비슷하게 동작합니다.&lt;br /&gt;
&lt;br /&gt;
=== 장점 ===&lt;br /&gt;
# 대량 데이터 조인에 효율적&lt;br /&gt;
# 인덱스가 없는 경우에도 성능이 좋은 편임&lt;br /&gt;
&lt;br /&gt;
=== 단점 ===&lt;br /&gt;
# SORT 단계가 필요하므로 메모리 및 디스크 I/O를 소모&lt;br /&gt;
# 작은 데이터셋에는 HASH JOIN이 더 빠를 수 있음&lt;br /&gt;
&lt;br /&gt;
=== MERGE 조인 사용 예시 ===&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT /*+ USE_MERGE(a b) */ *&lt;br /&gt;
FROM table_a a&lt;br /&gt;
JOIN table_b b ON a.id = b.id;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
=== 파이썬으로 MERGE JOIN 구현 ===&lt;br /&gt;
&lt;br /&gt;
* 두 리스트(또는 테이블)가 각각 다음과 같이 주어졌다고 가정&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# table_a: (id, value)&lt;br /&gt;
# table_b: (id, desc)&lt;br /&gt;
table_a = [&lt;br /&gt;
    (1, &#039;apple&#039;),&lt;br /&gt;
    (2, &#039;banana&#039;),&lt;br /&gt;
    (4, &#039;melon&#039;),&lt;br /&gt;
    (5, &#039;peach&#039;)&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
table_b = [&lt;br /&gt;
    (1, &#039;red&#039;),&lt;br /&gt;
    (2, &#039;yellow&#039;),&lt;br /&gt;
    (3, &#039;green&#039;),&lt;br /&gt;
    (4, &#039;green&#039;)&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
join_result = merge_join(table_a, table_b)&lt;br /&gt;
for row in join_result:&lt;br /&gt;
    print(row)&lt;br /&gt;
&lt;br /&gt;
def merge_join(table_a, table_b):&lt;br /&gt;
    # 두 테이블을 id 기준으로 정렬 (이미 정렬되어 있다고 가정)&lt;br /&gt;
    # 만약 정렬이 안 되어 있다면 아래 코드를 참고:&lt;br /&gt;
    # table_a = sorted(table_a, key=lambda x: x[0])&lt;br /&gt;
    # table_b = sorted(table_b, key=lambda x: x[0])&lt;br /&gt;
    &lt;br /&gt;
    i, j = 0, 0&lt;br /&gt;
    result = []&lt;br /&gt;
    &lt;br /&gt;
    while i &amp;lt; len(table_a) and j &amp;lt; len(table_b):&lt;br /&gt;
        id_a = table_a[i][0]&lt;br /&gt;
        id_b = table_b[j][0]&lt;br /&gt;
        &lt;br /&gt;
        if id_a == id_b:&lt;br /&gt;
            # 조건이 일치할 때 JOIN 결과에 추가&lt;br /&gt;
            result.append((id_a, table_a[i][1], table_b[j][1]))&lt;br /&gt;
            i += 1&lt;br /&gt;
            j += 1&lt;br /&gt;
        elif id_a &amp;lt; id_b:&lt;br /&gt;
            i += 1&lt;br /&gt;
        else:&lt;br /&gt;
            j += 1&lt;br /&gt;
    &lt;br /&gt;
    return result&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
* 실행 결과:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
(1, &#039;apple&#039;, &#039;red&#039;)&lt;br /&gt;
(2, &#039;banana&#039;, &#039;yellow&#039;)&lt;br /&gt;
(4, &#039;melon&#039;, &#039;green&#039;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 설명 ===&lt;br /&gt;
# 양쪽 리스트가 이미 정렬되어 있다는 가정 하에 두 인덱스를 비교해 가면서 일치하는 id를 찾고, 일치하지 않으면 더 작은 쪽 인덱스만 증가시켜 탐색하는 방식.&lt;br /&gt;
# Oracle의 MERGE JOIN과 거의 동일한 논리적 방식입니다.&lt;br /&gt;
&lt;br /&gt;
[[category:oracle]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1590</id>
		<title>해시 버킷의 저장구조</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1590"/>
		<updated>2025-10-22T02:42:08Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Oracle Hash Join의 내부 구조==&lt;br /&gt;
&lt;br /&gt;
* 해시 버킷의 저장 구조&lt;br /&gt;
&lt;br /&gt;
=== 해시 버킷에 저장되는 내용 ===&lt;br /&gt;
* 해시 버킷에는 &#039;&#039;&#039;주소 정보가 아닌 실제 데이터 행(row data)&#039;&#039;&#039; 이 저장됩니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 해시 테이블 구조:&lt;br /&gt;
#[해시 버킷 배열]&lt;br /&gt;
Bucket 0  → [emp_id=101, name=&#039;김철수&#039;, dept_id=10] → [emp_id=501, name=&#039;이영희&#039;, dept_id=10] → NULL&lt;br /&gt;
Bucket 1  → [emp_id=202, name=&#039;박민수&#039;, dept_id=20] → NULL&lt;br /&gt;
Bucket 2  → NULL&lt;br /&gt;
Bucket 3  → [emp_id=303, name=&#039;정수진&#039;, dept_id=30] → [emp_id=403, name=&#039;최지원&#039;, dept_id=30] → NULL&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 각 버킷 엔트리에 포함되는 정보 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
┌─────────────────────────────┐&lt;br /&gt;
│  해시 버킷 엔트리 (Entry)       │&lt;br /&gt;
├─────────────────────────────┤&lt;br /&gt;
│ 1. 조인 키 값                  │  ← emp_id = 101&lt;br /&gt;
│ 2. 필요한 컬럼 데이터            │  ← name, dept_id 등&lt;br /&gt;
│ 3. Next 포인터 (체이닝)         │  ← 다음 엔트리 주소&lt;br /&gt;
└─────────────────────────────┘&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 실제 메모리 구조 예시 ===&lt;br /&gt;
==== Build 단계 ====&lt;br /&gt;
* Build 단계: employees 테이블 (작은 테이블)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시 쿼리&lt;br /&gt;
SELECT e.emp_id, e.name, e.salary, d.dept_name&lt;br /&gt;
  FROM employees e  -- Build 테이블&lt;br /&gt;
  JOIN departments d  -- Probe 테이블&lt;br /&gt;
    ON e.dept_id = d.dept_id&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 단계에서 생성되는 해시 테이블:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
메모리 영역 (PGA - Work Area)&lt;br /&gt;
해시 테이블 버킷:&lt;br /&gt;
&lt;br /&gt;
Bucket[0] → NULL&lt;br /&gt;
&lt;br /&gt;
Bucket[1] → [Entry 1] → NULL&lt;br /&gt;
            ┌──────────────────────────────┐&lt;br /&gt;
            │ emp_id: 101     (조인 키)      │&lt;br /&gt;
            │ name: &#039;김철수&#039;   (일반 컬럼)     │&lt;br /&gt;
            │ salary: 5000000 (일반 컬럼)    │&lt;br /&gt;
            │ next: NULL      (체인 포인터)   │&lt;br /&gt;
            └──────────────────────────────┘&lt;br /&gt;
&lt;br /&gt;
Bucket[2] → [Entry 2] → [Entry 3] → NULL&lt;br /&gt;
            ┌──────────────────┐   ┌──────────────────┐&lt;br /&gt;
            │ emp_id: 202      │   │ emp_id: 302      │&lt;br /&gt;
            │ name: &#039;이영희&#039;     │    │ name: &#039;박민수&#039;    │&lt;br /&gt;
            │ salary: 6000000  │   │ salary: 5500000  │&lt;br /&gt;
            │ next: 0x3000     │   │ next: NULL       │&lt;br /&gt;
            └──────────────────┘   └──────────────────┘&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 실제 메모리 레이아웃&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
메모리 주소   | 내용&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x1000       | emp_id = 101&lt;br /&gt;
0x1004       | name = &amp;quot;김철수&amp;quot; (포인터 또는 인라인)&lt;br /&gt;
0x1008       | salary = 5000000&lt;br /&gt;
0x100C       | next = NULL&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x2000       | emp_id = 202&lt;br /&gt;
0x2004       | name = &amp;quot;이영희&amp;quot;&lt;br /&gt;
0x2008       | salary = 6000000&lt;br /&gt;
0x200C       | next = 0x3000  (다음 엔트리 주소)&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x3000       | emp_id = 302&lt;br /&gt;
0x3004       | name = &amp;quot;박민수&amp;quot;&lt;br /&gt;
0x3008       | salary = 5500000&lt;br /&gt;
0x300C       | next = NULL&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 과정 상세&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 의사 코드로 표현한 Build 단계&lt;br /&gt;
&lt;br /&gt;
hash_table = Array[BUCKET_SIZE]  # 버킷 배열 초기화&lt;br /&gt;
&lt;br /&gt;
for row in small_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 엔트리 생성 (실제 데이터 복사)&lt;br /&gt;
    entry = {&lt;br /&gt;
        &#039;join_key&#039;: row.join_key,&lt;br /&gt;
        &#039;column1&#039;: row.column1,&lt;br /&gt;
        &#039;column2&#039;: row.column2,&lt;br /&gt;
        &#039;next&#039;: NULL  # 체인 포인터&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    # 3. 버킷에 삽입 (체이닝 방식)&lt;br /&gt;
    if hash_table[bucket_index] is NULL:&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
    else:&lt;br /&gt;
        # 충돌 발생 - 체인의 맨 앞에 추가&lt;br /&gt;
        entry.next = hash_table[bucket_index]&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Probe 단계 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Probe 단계&lt;br /&gt;
&lt;br /&gt;
for probe_row in large_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(probe_row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 해당 버킷의 체인을 순회&lt;br /&gt;
    entry = hash_table[bucket_index]&lt;br /&gt;
    while entry is not NULL:&lt;br /&gt;
        # 3. 실제 조인 키 비교 (중요!)&lt;br /&gt;
        if entry.join_key == probe_row.join_key:&lt;br /&gt;
            # 조인 성공 - 결과 생성&lt;br /&gt;
            output(entry.data + probe_row.data)&lt;br /&gt;
        &lt;br /&gt;
        entry = entry.next  # 체인의 다음 엔트리로&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== [[hash join Probe 단계 처리과정|Probe 단계]] 에서도 해시버킷이 사용되나? =====&lt;br /&gt;
답변 : 아니요&lt;br /&gt;
&lt;br /&gt;
=== 왜 이렇게 설계 되었을까? ===&lt;br /&gt;
# 디스크 기반 접근 (주소만 저장한다면):&lt;br /&gt;
## 조인 매칭마다 디스크 I/O 발생&lt;br /&gt;
## 성능: 매우 느림&lt;br /&gt;
# 메모리 기반 접근 (실제 데이터 저장):&lt;br /&gt;
## 모든 매칭이 메모리에서 처리&lt;br /&gt;
## 성능: 매우 빠름 (Hash Join의 장점)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 결론 : 해시 버킷 구조의 핵심 포인트 ===&lt;br /&gt;
# 데이터 블럭의 주소만 저장 하나요? &lt;br /&gt;
#: NO → 실제 행 데이터를 메모리에 복사하여 저장&lt;br /&gt;
# 왜 데이터를 저장하나요? &lt;br /&gt;
#:Probe 단계에서 디스크 I/O 없이 빠르게 접근하기 위함&lt;br /&gt;
# DB의 어디 영역에 저장하나요? &lt;br /&gt;
#: 저장 위치: PGA의 Work Area (메모리)&lt;br /&gt;
# 해시충돌시 처리방법은?&lt;br /&gt;
#: 체이닝(Chaining) 방식 - 링크드 리스트&lt;br /&gt;
# 메모리 부족 시 에는?&lt;br /&gt;
#: 디스크 임시 테이블스페이스 사용 (성능 저하)&lt;br /&gt;
&lt;br /&gt;
* 따라서 Hash Join은 **작은 테이블 전체를 메모리에 로드**하는 것이 핵심입니다!&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1589</id>
		<title>해시 버킷의 저장구조</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1589"/>
		<updated>2025-10-22T02:41:46Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* Build 단계 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Oracle Hash Join의 내부 구조==&lt;br /&gt;
&lt;br /&gt;
* 해시 버킷의 저장 구조&lt;br /&gt;
&lt;br /&gt;
=== 해시 버킷에 저장되는 내용 ===&lt;br /&gt;
* 해시 버킷에는 &#039;&#039;&#039;주소 정보가 아닌 실제 데이터 행(row data)&#039;&#039;&#039; 이 저장됩니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 해시 테이블 구조:&lt;br /&gt;
#[해시 버킷 배열]&lt;br /&gt;
Bucket 0  → [emp_id=101, name=&#039;김철수&#039;, dept_id=10] → [emp_id=501, name=&#039;이영희&#039;, dept_id=10] → NULL&lt;br /&gt;
Bucket 1  → [emp_id=202, name=&#039;박민수&#039;, dept_id=20] → NULL&lt;br /&gt;
Bucket 2  → NULL&lt;br /&gt;
Bucket 3  → [emp_id=303, name=&#039;정수진&#039;, dept_id=30] → [emp_id=403, name=&#039;최지원&#039;, dept_id=30] → NULL&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 각 버킷 엔트리에 포함되는 정보 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
┌─────────────────────────────┐&lt;br /&gt;
│  해시 버킷 엔트리 (Entry)       │&lt;br /&gt;
├─────────────────────────────┤&lt;br /&gt;
│ 1. 조인 키 값                  │  ← emp_id = 101&lt;br /&gt;
│ 2. 필요한 컬럼 데이터            │  ← name, dept_id 등&lt;br /&gt;
│ 3. Next 포인터 (체이닝)         │  ← 다음 엔트리 주소&lt;br /&gt;
└─────────────────────────────┘&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 실제 메모리 구조 예시 ===&lt;br /&gt;
==== Build 단계 ====&lt;br /&gt;
* Build 단계: employees 테이블 (작은 테이블)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시 쿼리&lt;br /&gt;
SELECT e.emp_id, e.name, e.salary, d.dept_name&lt;br /&gt;
  FROM employees e  -- Build 테이블&lt;br /&gt;
  JOIN departments d  -- Probe 테이블&lt;br /&gt;
    ON e.dept_id = d.dept_id&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 단계에서 생성되는 해시 테이블:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
메모리 영역 (PGA - Work Area)&lt;br /&gt;
해시 테이블 버킷:&lt;br /&gt;
&lt;br /&gt;
Bucket[0] → NULL&lt;br /&gt;
&lt;br /&gt;
Bucket[1] → [Entry 1] → NULL&lt;br /&gt;
            ┌──────────────────────────────┐&lt;br /&gt;
            │ emp_id: 101     (조인 키)      │&lt;br /&gt;
            │ name: &#039;김철수&#039;   (일반 컬럼)     │&lt;br /&gt;
            │ salary: 5000000 (일반 컬럼)    │&lt;br /&gt;
            │ next: NULL      (체인 포인터)   │&lt;br /&gt;
            └──────────────────────────────┘&lt;br /&gt;
&lt;br /&gt;
Bucket[2] → [Entry 2] → [Entry 3] → NULL&lt;br /&gt;
            ┌──────────────────┐   ┌──────────────────┐&lt;br /&gt;
            │ emp_id: 202      │   │ emp_id: 302      │&lt;br /&gt;
            │ name: &#039;이영희&#039;     │   │ name: &#039;박민수&#039;    │&lt;br /&gt;
            │ salary: 6000000  │   │ salary: 5500000  │&lt;br /&gt;
            │ next: 0x3000     │   │ next: NULL       │&lt;br /&gt;
            └──────────────────┘   └──────────────────┘&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 실제 메모리 레이아웃&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
메모리 주소   | 내용&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x1000       | emp_id = 101&lt;br /&gt;
0x1004       | name = &amp;quot;김철수&amp;quot; (포인터 또는 인라인)&lt;br /&gt;
0x1008       | salary = 5000000&lt;br /&gt;
0x100C       | next = NULL&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x2000       | emp_id = 202&lt;br /&gt;
0x2004       | name = &amp;quot;이영희&amp;quot;&lt;br /&gt;
0x2008       | salary = 6000000&lt;br /&gt;
0x200C       | next = 0x3000  (다음 엔트리 주소)&lt;br /&gt;
─────────────┼────────────────────────────&lt;br /&gt;
0x3000       | emp_id = 302&lt;br /&gt;
0x3004       | name = &amp;quot;박민수&amp;quot;&lt;br /&gt;
0x3008       | salary = 5500000&lt;br /&gt;
0x300C       | next = NULL&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 과정 상세&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 의사 코드로 표현한 Build 단계&lt;br /&gt;
&lt;br /&gt;
hash_table = Array[BUCKET_SIZE]  # 버킷 배열 초기화&lt;br /&gt;
&lt;br /&gt;
for row in small_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 엔트리 생성 (실제 데이터 복사)&lt;br /&gt;
    entry = {&lt;br /&gt;
        &#039;join_key&#039;: row.join_key,&lt;br /&gt;
        &#039;column1&#039;: row.column1,&lt;br /&gt;
        &#039;column2&#039;: row.column2,&lt;br /&gt;
        &#039;next&#039;: NULL  # 체인 포인터&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    # 3. 버킷에 삽입 (체이닝 방식)&lt;br /&gt;
    if hash_table[bucket_index] is NULL:&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
    else:&lt;br /&gt;
        # 충돌 발생 - 체인의 맨 앞에 추가&lt;br /&gt;
        entry.next = hash_table[bucket_index]&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Probe 단계 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Probe 단계&lt;br /&gt;
&lt;br /&gt;
for probe_row in large_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(probe_row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 해당 버킷의 체인을 순회&lt;br /&gt;
    entry = hash_table[bucket_index]&lt;br /&gt;
    while entry is not NULL:&lt;br /&gt;
        # 3. 실제 조인 키 비교 (중요!)&lt;br /&gt;
        if entry.join_key == probe_row.join_key:&lt;br /&gt;
            # 조인 성공 - 결과 생성&lt;br /&gt;
            output(entry.data + probe_row.data)&lt;br /&gt;
        &lt;br /&gt;
        entry = entry.next  # 체인의 다음 엔트리로&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== [[hash join Probe 단계 처리과정|Probe 단계]] 에서도 해시버킷이 사용되나? =====&lt;br /&gt;
답변 : 아니요&lt;br /&gt;
&lt;br /&gt;
=== 왜 이렇게 설계 되었을까? ===&lt;br /&gt;
# 디스크 기반 접근 (주소만 저장한다면):&lt;br /&gt;
## 조인 매칭마다 디스크 I/O 발생&lt;br /&gt;
## 성능: 매우 느림&lt;br /&gt;
# 메모리 기반 접근 (실제 데이터 저장):&lt;br /&gt;
## 모든 매칭이 메모리에서 처리&lt;br /&gt;
## 성능: 매우 빠름 (Hash Join의 장점)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 결론 : 해시 버킷 구조의 핵심 포인트 ===&lt;br /&gt;
# 데이터 블럭의 주소만 저장 하나요? &lt;br /&gt;
#: NO → 실제 행 데이터를 메모리에 복사하여 저장&lt;br /&gt;
# 왜 데이터를 저장하나요? &lt;br /&gt;
#:Probe 단계에서 디스크 I/O 없이 빠르게 접근하기 위함&lt;br /&gt;
# DB의 어디 영역에 저장하나요? &lt;br /&gt;
#: 저장 위치: PGA의 Work Area (메모리)&lt;br /&gt;
# 해시충돌시 처리방법은?&lt;br /&gt;
#: 체이닝(Chaining) 방식 - 링크드 리스트&lt;br /&gt;
# 메모리 부족 시 에는?&lt;br /&gt;
#: 디스크 임시 테이블스페이스 사용 (성능 저하)&lt;br /&gt;
&lt;br /&gt;
* 따라서 Hash Join은 **작은 테이블 전체를 메모리에 로드**하는 것이 핵심입니다!&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=MERGE_%EC%A1%B0%EC%9D%B8&amp;diff=1576</id>
		<title>MERGE 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=MERGE_%EC%A1%B0%EC%9D%B8&amp;diff=1576"/>
		<updated>2025-10-21T02:15:57Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== MERGE 조인 ==&lt;br /&gt;
* 정확한 명칭은 &#039;MERGE JOIN&#039; 또는 &#039;SORT MERGE JOIN&#039;&lt;br /&gt;
* SORT(정렬) 후 MERGE(합치다) 의 의미  &lt;br /&gt;
&lt;br /&gt;
=== MERGE 조인이 필요한 경우 ===&lt;br /&gt;
# 조인에 사용되는 양쪽 테이블이 이미 정렬되어 있거나, 정렬 비용이 크지 않은 경우&lt;br /&gt;
# 대량의 데이터에 대해 효율적인 조인이 필요할 때&lt;br /&gt;
&lt;br /&gt;
===  MERGE 조인 동작 원리 ===&lt;br /&gt;
# 조인에 참여하는 두 테이블의 조인 컬럼을 기준으로 정렬(sorting).&lt;br /&gt;
# 두 데이터 집합을 &#039;&#039;&#039;동시에 순차적&#039;&#039;&#039; 으로 탐색하면서 일치하는 조건이 있는 행을 찾아 조인 결과를 만듭니다.&lt;br /&gt;
# 두 개의 정렬된 리스트를 한 번에 훑는 것과 비슷하게 동작합니다.&lt;br /&gt;
&lt;br /&gt;
=== 장점 ===&lt;br /&gt;
# 대량 데이터 조인에 효율적&lt;br /&gt;
# 인덱스가 없는 경우에도 성능이 좋은 편임&lt;br /&gt;
&lt;br /&gt;
=== 단점 ===&lt;br /&gt;
# SORT 단계가 필요하므로 메모리 및 디스크 I/O를 소모&lt;br /&gt;
# 작은 데이터셋에는 HASH JOIN이 더 빠를 수 있음&lt;br /&gt;
&lt;br /&gt;
=== MERGE 조인 사용 예시 ===&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT /*+ USE_MERGE(a b) */ *&lt;br /&gt;
FROM table_a a&lt;br /&gt;
JOIN table_b b ON a.id = b.id;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
=== 파이썬으로 MERGE JOIN 구현 ===&lt;br /&gt;
&lt;br /&gt;
* 두 리스트(또는 테이블)가 각각 다음과 같이 주어졌다고 가정&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# table_a: (id, value)&lt;br /&gt;
# table_b: (id, desc)&lt;br /&gt;
table_a = [&lt;br /&gt;
    (1, &#039;apple&#039;),&lt;br /&gt;
    (2, &#039;banana&#039;),&lt;br /&gt;
    (4, &#039;melon&#039;),&lt;br /&gt;
    (5, &#039;peach&#039;)&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
table_b = [&lt;br /&gt;
    (1, &#039;red&#039;),&lt;br /&gt;
    (2, &#039;yellow&#039;),&lt;br /&gt;
    (3, &#039;green&#039;),&lt;br /&gt;
    (4, &#039;green&#039;)&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
join_result = merge_join(table_a, table_b)&lt;br /&gt;
for row in join_result:&lt;br /&gt;
    print(row)&lt;br /&gt;
&lt;br /&gt;
def merge_join(table_a, table_b):&lt;br /&gt;
    # 두 테이블을 id 기준으로 정렬 (이미 정렬되어 있다고 가정)&lt;br /&gt;
    # 만약 정렬이 안 되어 있다면 아래 코드를 참고:&lt;br /&gt;
    # table_a = sorted(table_a, key=lambda x: x[0])&lt;br /&gt;
    # table_b = sorted(table_b, key=lambda x: x[0])&lt;br /&gt;
    &lt;br /&gt;
    i, j = 0, 0&lt;br /&gt;
    result = []&lt;br /&gt;
    &lt;br /&gt;
    while i &amp;lt; len(table_a) and j &amp;lt; len(table_b):&lt;br /&gt;
        id_a = table_a[i][0]&lt;br /&gt;
        id_b = table_b[j][0]&lt;br /&gt;
        &lt;br /&gt;
        if id_a == id_b:&lt;br /&gt;
            # 조건이 일치할 때 JOIN 결과에 추가&lt;br /&gt;
            result.append((id_a, table_a[i][1], table_b[j][1]))&lt;br /&gt;
            i += 1&lt;br /&gt;
            j += 1&lt;br /&gt;
        elif id_a &amp;lt; id_b:&lt;br /&gt;
            i += 1&lt;br /&gt;
        else:&lt;br /&gt;
            j += 1&lt;br /&gt;
    &lt;br /&gt;
    return result&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
* 실행 결과:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
(1, &#039;apple&#039;, &#039;red&#039;)&lt;br /&gt;
(2, &#039;banana&#039;, &#039;yellow&#039;)&lt;br /&gt;
(4, &#039;melon&#039;, &#039;green&#039;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 설명 ===&lt;br /&gt;
# 양쪽 리스트가 이미 정렬되어 있다는 가정 하에 두 인덱스를 비교해 가면서 일치하는 id를 찾고, 일치하지 않으면 더 작은 쪽 인덱스만 증가시켜 탐색하는 방식.&lt;br /&gt;
# Oracle의 MERGE JOIN과 거의 동일한 논리적 방식입니다.&lt;br /&gt;
&lt;br /&gt;
[[category:oracle]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1575</id>
		<title>대문</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=1575"/>
		<updated>2025-10-21T02:15:38Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* MERGE 조인 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__notoc__&lt;br /&gt;
https://dbstudy.co.kr/w/resources/assets/dbstudy_iconx1.png &lt;br /&gt;
= Welcome To DB STUDY - {{CURRENTYEAR}}.{{CURRENTMONTHNAME}}.{{CURRENTDAY}}({{CURRENTDAYNAME}}) =&lt;br /&gt;
= 전문가가 되고 싶다면 기본에 충실 하라. (Return To Basics) =&lt;br /&gt;
* {{SERVER}}&lt;br /&gt;
* 게시글 총 : {{NUMBEROFPAGES}} 건 , 사용자 : {{NUMBEROFUSERS}} 명 , &lt;br /&gt;
&amp;lt;p sizes=&amp;quot;(max-width: 600px) 480px,800px&amp;quot;&amp;gt;&lt;br /&gt;
https://dbstudy.co.kr/w/images/dbstudy_logo.jpg&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
{{틀:타이틀 라운드&lt;br /&gt;
|제목=[[:Category:oracle|오라클]]: {{PAGESINCATEGORY: oracle}} 건 ,  [[:Category:postgresql|PostgreSql]] : {{PAGESINCATEGORY: postgresql}} 건 ,  [[:Category:mysql|MySQL]]: {{PAGESINCATEGORY: mysql}} 건,  [[:Category:linux|LINUX]]: {{PAGESINCATEGORY: linux}} 건&lt;br /&gt;
|아이콘=emoji_objects&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= ORACLE =&lt;br /&gt;
== [[ORACLE SQL 튜닝]] ==&lt;br /&gt;
=== [[성능을 고려한 설계]] ===&lt;br /&gt;
=== [[효율적인 SQL 작성법]] ===&lt;br /&gt;
=== [[튜닝 환경 구축]] ===&lt;br /&gt;
# [[DBMS_XPLAN 사용법]]&lt;br /&gt;
# [[REAL PLAN 사용법]]&lt;br /&gt;
# [[SQL 트레이스 방법]]&lt;br /&gt;
&lt;br /&gt;
=== [[인덱스 설계]] ===&lt;br /&gt;
# [[인덱스 아키텍처]]&lt;br /&gt;
# [[오라클 인덱스 종류|인덱스 종류]]&lt;br /&gt;
# [[엑세스 패스]]&lt;br /&gt;
&lt;br /&gt;
=== [[테이블 조인 방식|오라클 조인 과 알고리즘 ]] ===&lt;br /&gt;
==== [[NL 조인]]====&lt;br /&gt;
==== [[HASH 조인]]====&lt;br /&gt;
==== [[MERGE 조인|SORT MERGE 조인]]====&lt;br /&gt;
&lt;br /&gt;
==== [[세미조인]] ====&lt;br /&gt;
==== [[안티조인]] ====&lt;br /&gt;
&lt;br /&gt;
=== [[옵티마이져]] ===&lt;br /&gt;
==== [[JPPD(Join Predicate PushDown,조인절 PUSHDOWN)]]====&lt;br /&gt;
&lt;br /&gt;
==== [[View pushed predicate (조건절 PUSHDOWN)]]====&lt;br /&gt;
&lt;br /&gt;
=== [[튜닝 힌트]] ===&lt;br /&gt;
&lt;br /&gt;
=== [[대용량 데이터 튜닝]] ===&lt;br /&gt;
==== [[병렬 쿼리 튜닝]] ====&lt;br /&gt;
&lt;br /&gt;
=== [[ORACLE 튜닝 대상 조회]] ===&lt;br /&gt;
=== [[파라미터 설계]] ===&lt;br /&gt;
=== [[ORACLE 아키텍쳐의 이해]] ===&lt;br /&gt;
==== [[데이터 블럭의 구조]] ====&lt;br /&gt;
==== [[오라클 컬럼의 구조]] ====&lt;br /&gt;
===== [[오라클 컬럼의 저장순서]] =====&lt;br /&gt;
===== [[컬럼의 순서가 변경시 ROW의 물리적 구조변화|컬럼 순서 변경시 ROW의 물리구조 변화]]=====&lt;br /&gt;
&lt;br /&gt;
===== [[오라클 컬럼저장 방식 개선 (12c 업그레이드) ]] =====&lt;br /&gt;
&lt;br /&gt;
==== [[오라클 테이블의 구조]]====&lt;br /&gt;
==== [[오라클 인덱스의 구조]] ====&lt;br /&gt;
=== [[성능 문제 식별 방법론과 튜닝 접근법]]===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)]]=&lt;br /&gt;
== [[DBA_수행_방법론_(공공/대기업_SI_프로젝트)#개요|개요]] ==&lt;br /&gt;
== [[단계별 수행 방법론]] ==&lt;br /&gt;
=== [[분석 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[설계 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[구축 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[테스트 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
=== [[전개 단계]] ===&lt;br /&gt;
==== [[산출물]] ====&lt;br /&gt;
==== [[주요 작업 TASK]] ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1574</id>
		<title>HASH 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1574"/>
		<updated>2025-10-21T01:55:56Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해시 조인(Hash Join) ==&lt;br /&gt;
{{틀:서브&lt;br /&gt;
|제목=&#039;&#039;&#039;&amp;lt;big&amp;gt;해시조인시 사용되는 해시(hash)함수는 입력된 데이터를 &#039;잘게 다져서&#039; 고유하고 일정한 크기의 식별자로 만들어 냄.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
* 해시의 주요특징&lt;br /&gt;
** 단방향성 : 해시값을 원래의 값으로 되돌릴수 없다.&lt;br /&gt;
** 고유성 : 입력값이 바뀌면 해시값은 완전히 달라진다.&lt;br /&gt;
** 고정된길이 : 입력값의 크기와 상관없이 항상 동일한 길이의 출력값을 가진다.&lt;br /&gt;
|내용=* hash의 영문의 뜻 : &#039;&#039;&#039;잘게 썰다, 다지다의 의미(요리에서 재료를 잘게 썰어서 섞을때 쓰이는말, 예로 맥도날도 &#039;해시브라운(감자를 다져서 튀긴요리)&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
# 해시 조인(Hash Join)은 두 개의 테이블을 조인할 때, 더 작은 테이블을 메모리에 올리고 해시 테이블을 만들어 효율적으로 조인하는 방식&lt;br /&gt;
# 대용량 테이블을 조인할 때 주로 사용되며, 조인 조건에 인덱스가 없는 경우에도 빠르게 작동&lt;br /&gt;
&lt;br /&gt;
=== 해시 조인의 원리 ===&lt;br /&gt;
* 해시 조인은 크게 두 단계로 나뉩니다.&lt;br /&gt;
# 빌드 단계 (Build Phase):&lt;br /&gt;
## 옵티마이저는 두 테이블 중 더 작은 테이블(빌드 테이블)을 선택&lt;br /&gt;
## 이 테이블의 조인 키(컬럼)를 사용하여 PGA 메모리 영역에 해시 테이블을 생성. 각 조인 키 값은 해시 함수를 통하여 해시값으로 변환되어 해시 테이블의 특정 위치에 저장.&lt;br /&gt;
## 해시테이블 저장시 변환된 해시값들은 해시버킷의 갯수 만큼 쪼개서 할당되고 &amp;quot;버킷&amp;quot;에 행을 연결리스트(체인) 형태로 저장됨.&lt;br /&gt;
## 각 버킷은 &amp;quot;해당 해시 범위에 속하는 행들의 포인터(체인 헤드)&amp;quot; 역활을 하며 , 동일 해시값 이나 충돌이 발생하면 같은 버킷내에서 체인으로 연결됨.(많이 연결될수록 성능 저하,이구간이 성능저하 구간) &lt;br /&gt;
# 프로브 단계 (Probe Phase):&lt;br /&gt;
## 더 큰 테이블(프로브 테이블)을 순차적으로 읽어옮&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 테이블에서 일치하는 행을 찾음.(대상 버킷을 찾고 해당 버킷의 체인을 스캔하여 조인키를 비교)&lt;br /&gt;
## 일치하는 행을 찾으면, 두 테이블의 행을 결합하여 결과를 반환.&lt;br /&gt;
&lt;br /&gt;
* 만약 빌드 테이블이 PGA 메모리에 다 들어가지 않을 정도로 크다면, 테이블을 여러 개의 파티션으로 나누어 디스크에 임시로 저장한 뒤(성능 저하 구간), 각 파티션별로 빌드와 프로브 단계를 반복하는 &#039;그레이스풀 해시 조인(Graceful Hash Join)&#039;이 수행(성능저하 구간)&lt;br /&gt;
* 런타임 파티셔닝을 수행해 부분(배치/파티션) 단위로 템프에 내린 후(DISK I/O발생,성능저하) 다시 합쳐서 1-pass/멀티패스 방식으로 진행함.&lt;br /&gt;
&lt;br /&gt;
=== 파이썬 예제 ===&lt;br /&gt;
* 두 개의 CSV 파일 sales.csv와 products.csv가 있다고 가정하고, 이를 파이썬 딕셔너리로 해시 조인을 구현하는 예제.&lt;br /&gt;
1. 데이터 준비 (sales.csv, products.csv 파일)&lt;br /&gt;
&lt;br /&gt;
1) 대량 테이블 - 판매(sales.csv 파일)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sale_id,product_id,amount&lt;br /&gt;
1,101,100&lt;br /&gt;
2,102,150&lt;br /&gt;
3,101,200&lt;br /&gt;
4,103,50&lt;br /&gt;
....&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2) 제품 (products.csv)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
product_id,product_name&lt;br /&gt;
101,Laptop&lt;br /&gt;
102,Mouse&lt;br /&gt;
103,Keyboard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 해시 조인 구현 코드&lt;br /&gt;
&lt;br /&gt;
소량인 products 테이블을 빌드 테이블로, sales 테이블을 프로브 테이블로 사용하여 조인을 수행.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import csv&lt;br /&gt;
&lt;br /&gt;
# 1. 빌드 테이블: products.csv를 읽어 메모리 해시 테이블(딕셔너리) 생성&lt;br /&gt;
# &#039;product_id&#039;를 키로, &#039;product_name&#039;을 값으로 저장&lt;br /&gt;
products_hash_table = {}&lt;br /&gt;
with open(&#039;products.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # 해시 테이블 생성 (딕셔너리)&lt;br /&gt;
        products_hash_table[row[&#039;product_id&#039;]] = row[&#039;product_name&#039;]&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;--- 빌드 단계 (해시 테이블) ---&amp;quot;)&lt;br /&gt;
print(products_hash_table)&lt;br /&gt;
# 출력: {&#039;101&#039;: &#039;Laptop&#039;, &#039;102&#039;: &#039;Mouse&#039;, &#039;103&#039;: &#039;Keyboard&#039;}&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;\n--- 프로브 단계 (조인 결과) ---&amp;quot;)&lt;br /&gt;
# 2. 프로브 테이블: sales.csv를 읽어 해시 테이블에서 찾기&lt;br /&gt;
with open(&#039;sales.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # sale_id 3의 &#039;product_id&#039;인 &#039;101&#039;을 해시 테이블에서 찾음&lt;br /&gt;
        product_id = row[&#039;product_id&#039;]&lt;br /&gt;
        if product_id in products_hash_table:&lt;br /&gt;
            # 해시 테이블에서 &#039;product_id&#039;를 키로 검색하여 &#039;product_name&#039;을 가져옴&lt;br /&gt;
            product_name = products_hash_table[product_id]&lt;br /&gt;
            print(f&amp;quot;Sale ID: {row[&#039;sale_id&#039;]}, Product Name: {product_name}, Amount: {row[&#039;amount&#039;]}&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 예상 출력:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Sale ID: 1, Product Name: Laptop, Amount: 100&lt;br /&gt;
# Sale ID: 2, Product Name: Mouse, Amount: 150&lt;br /&gt;
# Sale ID: 3, Product Name: Laptop, Amount: 200&lt;br /&gt;
# Sale ID: 4, Product Name: Keyboard, Amount: 50&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# products_hash_table이라는 딕셔너리가 바로 해시 테이블 역할을 합니다. &lt;br /&gt;
# sales 테이블의 각 행을 읽을 때마다 product_id를 딕셔너리(해시 테이블)에서 빠르게 찾아 product_name을 가져와 조인된 결과를 출력하는 방식&lt;br /&gt;
# 이를 통해 전체 products 테이블을 다시 스캔할 필요 없이 O(1)에 가까운 시간 복잡도로 조인 조건을 만족하는 데이터를 찾을 수 있음.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 성능 향상 원리 ===&lt;br /&gt;
==== Hash 버킷의 역할과 기능 ====&lt;br /&gt;
::::* 해시 버킷은 두 테이블을 효율적으로 조인하기 위한 핵심적인 논리적 저장 공간. &lt;br /&gt;
::::* 이는 조인 키(Join Key)를 기반으로 데이터를 빠르게 찾고 저장하는 데 사용.&lt;br /&gt;
&lt;br /&gt;
===== 역할: 데이터의 논리적 분류 및 저장소 =====&lt;br /&gt;
:::::* 해시 버킷은 조인 키 값을 해시 함수에 넣어 얻은 해시 값이 같은 데이터들을 모아두는 공간.&lt;br /&gt;
:::::* 이는 거대한 데이터를 조인 키를 기준으로 더 작은 그룹으로 나누어 관리하는 역할을 합니다.&lt;br /&gt;
:::::* 이를 통해 빌드 테이블의 모든 데이터를 일일이 탐색하지 않고도, 특정 조인 키에 해당하는 데이터가 어느 버킷에 있는지 바로 알 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
===== 기능: Build 단계와 Probe 단계에서의 활용 =====&lt;br /&gt;
# Build 단계:&lt;br /&gt;
## Oracle은 먼저 두 테이블 중 더 작은 테이블을 선택하여 빌드 테이블로 지정합니다.&lt;br /&gt;
## 빌드 테이블의 각 행을 읽어 조인 키에 해시 함수를 적용하여 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값에 해당하는 버킷에 해당 행의 데이터를 저장합니다. 만약 같은 버킷에 이미 다른 데이터가 있다면, 이를 연결 리스트(Linked List) 형태로 연결하여 **해시 충돌(Hash Collision)**을 처리합니다.&lt;br /&gt;
# Probe 단계:&lt;br /&gt;
## 이제 더 큰 테이블인 프로브 테이블을 순차적으로 읽습니다.&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 버킷 위치를 즉시 찾아갑니다.&lt;br /&gt;
## 해당 버킷 안의 연결 리스트를 탐색하여 조인 키가 일치하는 행을 찾습니다.&lt;br /&gt;
## 일치하는 행이 발견되면 두 테이블의 데이터를 결합하여 최종 결과를 생성합니다.&lt;br /&gt;
&lt;br /&gt;
* 이러한 해시 버킷의 활용 덕분에, 해시 조인은 테이블의 크기와 상관없이 조인 키가 같은 데이터들을 O(1)에 가까운 시간 복잡도로 찾아낼 수 있어 대용량 데이터 조인에 매우 효과적입니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== [[해시 버킷의 저장구조]]====&lt;br /&gt;
&lt;br /&gt;
==== 해시 버킷 개수의 결정 ====&lt;br /&gt;
* 해시 조인의 최대 해시 버킷 개수는 사전 설정된 고정값이 아니라, 조인하려는 테이블의 크기, 옵티마이저의 예측, 그리고 PGA_AGGREGATE_TARGET 파라미터에 의해 동적으로 결정&lt;br /&gt;
&lt;br /&gt;
# 빌드 테이블(Build Table)의 크기: 옵티마이저가 예측한 더 작은 테이블(빌드 테이블)의 크기에 따라 필요한 해시 버킷의 수가 결정됨&lt;br /&gt;
# 할당된 PGA 메모리: PGA_AGGREGATE_TARGET에 의해 세션에 할당된 PGA 메모리 내에서 해시 테이블이 생성됨.&lt;br /&gt;
# 메모리 부족 시: 만약 할당된 PGA 메모리만으로 해시 테이블을 모두 생성할 수 없다면, Oracle은 일부 해시 버킷을 디스크의 임시 테이블스페이스(temp tablespace)로 분할하여 저장합니다. &lt;br /&gt;
## 이를 &#039;해시 스필(Hash Spill)&#039;이라고 부르며, 이 경우 I/O가 발생하여 성능이 저하됨.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인이 느려지는 원인 ====&lt;br /&gt;
* 해시 조인 성능이 느려지는 주요 원인 중 하나는 해시 버킷 때문입니다.&lt;br /&gt;
* 정확히 말하면, 해시 충돌이 심하게 발생하거나 해시 테이블이 메모리에 맞지 않아 디스크 I/O가 발생하는 경우입니다.&lt;br /&gt;
* 해시 버킷 자체는 효율성을 위한 도구지만, 비정상적인 상황에서는 오히려 성능 저하의 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
1. 해시 충돌 (Hash Collisions)&lt;br /&gt;
* 원리: 해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다.&lt;br /&gt;
* 문제점: 해시 충돌이 발생하면, 하나의 버킷에 여러 행이 연결 리스트 형태로 쌓이게 됩니다. 이 버킷을 탐색할 때, 원하는 데이터를 찾기 위해 연결된 모든 데이터를 순차적으로 비교해야 하므로, 탐색 시간이 O(1)에서 O(N)으로 늘어납니다.&lt;br /&gt;
* 결과: 조인 키 데이터 분포가 불균형하거나(데이터 편중), 해시 함수가 비효율적이면 해시 충돌이 자주 발생하여 성능이 크게 저하됩니다.&lt;br /&gt;
* (결론) 해시조인은 조인키로 사용되는 컬럼이 중복이 많을수록  성능이 좋지 않음&lt;br /&gt;
&lt;br /&gt;
* 파이썬으로 해시 충돌 구현 예제&lt;br /&gt;
** 고의로 해시 충돌을 유발하는 데이터를 사용하여 해시 조인의 성능 차이를 보여줍니다. &lt;br /&gt;
** products_collision 리스트의 모든 product_id는 해시 함수를 거치면 같은 값을 반환하도록 설계되었습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터 (해시 충돌이 거의 없음)&lt;br /&gt;
products_normal = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 102, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 103, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 의도적으로 해시 충돌을 유발하는 데이터&lt;br /&gt;
# 101, 201, 301 모두 100으로 나눈 나머지가 1이 되도록 설계&lt;br /&gt;
products_collision = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 201, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 301, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인 대상 데이터 (각 product_id에 대한 sales 데이터)&lt;br /&gt;
sales = [{&#039;sale_id&#039;: i, &#039;product_id&#039;: 101, &#039;amount&#039;: 100} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+1000, &#039;product_id&#039;: 201, &#039;amount&#039;: 150} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+2000, &#039;product_id&#039;: 301, &#039;amount&#039;: 200} for i in range(1000)]&lt;br /&gt;
&lt;br /&gt;
# 사용자 정의 해시 함수 (단순화를 위해)&lt;br /&gt;
def custom_hash(key):&lt;br /&gt;
    return key % 100&lt;br /&gt;
&lt;br /&gt;
def hash_join(build_table, probe_table):&lt;br /&gt;
    # 빌드 단계: 해시 버킷(딕셔너리) 생성&lt;br /&gt;
    # 파이썬 딕셔너리의 키 충돌을 구현하기 위해, 딕셔너리의 값을 리스트로 저장&lt;br /&gt;
    hash_buckets = {}&lt;br /&gt;
    for item in build_table:&lt;br /&gt;
        key = item[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        if bucket_index not in hash_buckets:&lt;br /&gt;
            hash_buckets[bucket_index] = []&lt;br /&gt;
        hash_buckets[bucket_index].append(item)&lt;br /&gt;
&lt;br /&gt;
    # 프로브 단계: 조인&lt;br /&gt;
    start_time = time.time()&lt;br /&gt;
    joined_results = []&lt;br /&gt;
    for sale in probe_table:&lt;br /&gt;
        key = sale[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        &lt;br /&gt;
        # 해시 버킷에서 일치하는 데이터를 찾기 (충돌 발생 시 순차 탐색)&lt;br /&gt;
        if bucket_index in hash_buckets:&lt;br /&gt;
            for product_info in hash_buckets[bucket_index]:&lt;br /&gt;
                if product_info[&#039;product_id&#039;] == key:&lt;br /&gt;
                    joined_results.append({**sale, **product_info}) # **은 언패킹연산자 {**A,**B} 는 A,B딕셔너리를 합쳐줌.&lt;br /&gt;
                    break&lt;br /&gt;
    end_time = time.time()&lt;br /&gt;
    return end_time - start_time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터로 조인&lt;br /&gt;
normal_time = hash_join(products_normal, sales)&lt;br /&gt;
&lt;br /&gt;
# 해시 충돌 데이터로 조인&lt;br /&gt;
collision_time = hash_join(products_collision, sales)&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;일반 데이터 조인 시간: {normal_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;해시 충돌 조인 시간: {collision_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;성능 저하 비율: {(collision_time / normal_time):.2f}배 느림&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
일반 데이터 조인 시간: 0.000971 초&lt;br /&gt;
해시 충돌 조인 시간: 0.001568 초&lt;br /&gt;
성능 저하 비율: 1.61배 느림&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 메모리 부족 및 디스크 I/O&lt;br /&gt;
* 원리: 해시 조인은 더 작은 테이블(빌드 테이블)을 메모리에 올려 해시 테이블을 만드는 것이 기본 전제입니다.&lt;br /&gt;
* 문제점: 빌드 테이블이 너무 커서 **메모리(PGA)**에 다 담을 수 없게 되면, Oracle은 해시 테이블을 여러 파티션으로 나누어 일부를 **임시 테이블스페이스(Temporary Tablespace)**에 저장합니다.&lt;br /&gt;
* 결과: 조인을 수행하기 위해 디스크에 기록된 파티션 데이터를 읽고 쓰는 디스크 I/O 작업이 반복적으로 발생합니다. 이는 메모리에서만 작업할 때보다 훨씬 느리므로 성능 저하의 결정적인 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인에 대한 의문점 ====&lt;br /&gt;
* hash join에서  해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다. &lt;br /&gt;
* 또한 조인키로 사용되는 컬럼이 중복이 많을수록 성능이 좋지 않습니다.  &lt;br /&gt;
# 그러면 조인키로 사용되는 &#039;&#039;&#039;컬럼의 값&#039;&#039;&#039; 이 중복이 심하면 성능 않좋아 진다는것인가? &lt;br /&gt;
# 아니면 해시함수를 통하여 생성된 해시값이 동일한경우에 성능이 않좋아 진다는 것인가? &lt;br /&gt;
# 둘다 해당한다는것인가?&lt;br /&gt;
&lt;br /&gt;
* 둘 다 성능에 영향을 미치지만, 서로 다른 이유이며 실무에서는 1번이 훨씬 더 큰 문제입니다.&lt;br /&gt;
&lt;br /&gt;
===== 조인 키 값의 중복 (더 흔한 문제) =====&lt;br /&gt;
* 조인 키 컬럼 자체의 중복이 많으면 성능이 나빠집니다.&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시: 성별 컬럼으로 조인 (값이 &#039;M&#039;, &#039;F&#039; 두 개뿐)&lt;br /&gt;
SELECT /*+ USE_HASH(a b) */ *&lt;br /&gt;
FROM employees a&lt;br /&gt;
JOIN customers b ON a.gender = b.gender&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- `gender = &#039;M&#039;` 인 행들이 모두 &#039;&#039;&#039;같은 해시 버킷&#039;&#039;&#039;에 저장됨&lt;br /&gt;
:- Probe 단계에서 해당 버킷의 수많은 행들을 일일이 비교해야 함&lt;br /&gt;
:- 해시 조인의 장점(O(1) 검색)이 사라짐&lt;br /&gt;
&lt;br /&gt;
===== 해시 충돌 (드문 문제) =====&lt;br /&gt;
* 서로 다른 조인 키 값이 우연히 같은 해시 값을 생성하는 경우입니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 예시 (실제는 아니지만 개념 설명)&lt;br /&gt;
hash(&#039;emp_id_12345&#039;) = 789  # 해시값 789&lt;br /&gt;
hash(&#039;emp_id_99999&#039;) = 789  # 다른 키인데 같은 해시값!&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- 서로 다른 키인데 같은 버킷에 저장됨&lt;br /&gt;
:- 역시 Probe 시 추가 비교 필요&lt;br /&gt;
&lt;br /&gt;
===== 실제 상황 비교 =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 A: 카디널리티 낮음 (중복 많음) - 실제 큰 문제&lt;br /&gt;
employees: 1,000,000명&lt;br /&gt;
성별(gender)컬럼 값: 남자(&#039;M&#039;), 여자(&#039;F&#039;) 두 개만&lt;br /&gt;
→ 각 버킷에 약 500,000개 행 저장&lt;br /&gt;
→ 성능 매우 나쁨&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 B: 카디널리티 높음 (중복 적음) - 좋음&lt;br /&gt;
employees: 1,000,000명  &lt;br /&gt;
employee_id: 1,000,000개 유니크 값&lt;br /&gt;
→ 각 버킷에 평균 1~2개 행&lt;br /&gt;
→ 해시 충돌 있어도 영향 미미&lt;br /&gt;
→ 성능 좋음&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 결론&lt;br /&gt;
&lt;br /&gt;
:- 조인 키 중복 문제 : 실무에서 흔하고 심각한 성능 저하 원인&lt;br /&gt;
:- 해시 충돌 문제 : Oracle의 해시 함수가 잘 설계되어 있어 드물게 발생&lt;br /&gt;
:- **실무 팁**: 조인 키는 **카디널리티가 높은 컬럼**(예: ID, 주문번호)을 사용해야 함&lt;br /&gt;
&lt;br /&gt;
성별, 부서코드(10개 정도) 같은 낮은 카디널리티 컬럼으로 Hash Join하면 성능이 매우 나빠집니다!&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
* 해시 조인이 효율적으로 작동하는 것은 해시 버킷이 잘 분산되어 있고, 해시 테이블이 메모리에서 모두 처리될 때입니다. 만약 데이터가 한쪽으로 치우쳐 해시 충돌이 심하거나, 메모리 부족으로 디스크 I/O가 발생하면 해시 조인의 장점이 사라져 성능이 느려지게 됩니다.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 장/단점 ===&lt;br /&gt;
==== 장점 ====&lt;br /&gt;
# 대용량 테이블간 &amp;quot;=&amp;quot; 조건의 조인에 적합(정렬 불필요,입력 순서 무관)&lt;br /&gt;
# 빠른 접근 가능(버킷을 이용한 빠른 처리)&lt;br /&gt;
# 병렬처리에 적합하면 효율성이 좋음&lt;br /&gt;
&lt;br /&gt;
==== 단점 ==== &lt;br /&gt;
# &amp;quot;=&amp;quot; 조건에만 적합, 범위 조건(between 이나 like) 조인은 사용할수 없음.&lt;br /&gt;
# 메모리에 의존적임(빌드테이블이 메모리내에 들어가지 못하면 템프 i/o가 발생)&lt;br /&gt;
# 조인키값이 한쪽에 치우치거나(skew),중복도가 높으면 특정 버킷에 연결된 버킷이 길어져 탐색하는 시간이 증가함(CPU사용량 급증, 충돌증가)&lt;br /&gt;
# 버킷수가 데이터 대비 부족하면 충돌 발생과 체인 길이가 늘어 탐색 비용이 증가함  &lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1573</id>
		<title>HASH 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1573"/>
		<updated>2025-10-21T01:54:12Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해시 조인(Hash Join) ==&lt;br /&gt;
{{틀:서브&lt;br /&gt;
|제목=&#039;&#039;&#039;&amp;lt;big&amp;gt;해시조인시 사용되는 해시(hash)함수는 입력된 데이터를 &#039;잘게 다져서&#039; 고유하고 일정한 크기의 식별자로 만들어 냄.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
* 해시의 주요특징&lt;br /&gt;
** 단방향성 : 해시값을 원래의 값으로 되돌릴수 없다.&lt;br /&gt;
** 고유성 : 입력값이 바뀌면 해시값은 완전히 달라진다.&lt;br /&gt;
** 고정된길이 : 입력값의 크기와 상관없이 항상 동일한 길이의 출력값을 가진다.&lt;br /&gt;
|내용=* hash의 영문의 뜻 : &#039;&#039;&#039;잘게 썰다, 다지다의 의미(요리에서 재료를 잘게 썰어서 섞을때 쓰이는말, 예로 맥도날도 &#039;해시브라운(감자를 다져서 튀긴요리)&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
# 해시 조인(Hash Join)은 두 개의 테이블을 조인할 때, 더 작은 테이블을 메모리에 올리고 해시 테이블을 만들어 효율적으로 조인하는 방식&lt;br /&gt;
# 대용량 테이블을 조인할 때 주로 사용되며, 조인 조건에 인덱스가 없는 경우에도 빠르게 작동&lt;br /&gt;
&lt;br /&gt;
=== 해시 조인의 원리 ===&lt;br /&gt;
* 해시 조인은 크게 두 단계로 나뉩니다.&lt;br /&gt;
# 빌드 단계 (Build Phase):&lt;br /&gt;
## 옵티마이저는 두 테이블 중 더 작은 테이블(빌드 테이블)을 선택&lt;br /&gt;
## 이 테이블의 조인 키(컬럼)를 사용하여 PGA 메모리 영역에 해시 테이블을 생성. 각 조인 키 값은 해시 함수를 통하여 해시값으로 변환되어 해시 테이블의 특정 위치에 저장.&lt;br /&gt;
## 해시테이블 저장시 변환된 해시값들은 해시버킷의 갯수 만큼 쪼개서 할당되고 &amp;quot;버킷&amp;quot;에 행을 연결리스트(체인) 형태로 저장됨.&lt;br /&gt;
## 각 버킷은 &amp;quot;해당 해시 범위에 속하는 행들의 포인터(체인 헤드)&amp;quot; 역활을 하며 , 동일 해시값 이나 충돌이 발생하면 같은 버킷내에서 체인으로 연결됨.(많이 연결될수록 성능 저하,이구간이 성능저하 구간) &lt;br /&gt;
# 프로브 단계 (Probe Phase):&lt;br /&gt;
## 더 큰 테이블(프로브 테이블)을 순차적으로 읽어옮&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 테이블에서 일치하는 행을 찾음.(대상 버킷을 찾고 해당 버킷의 체인을 스캔하여 조인키를 비교)&lt;br /&gt;
## 일치하는 행을 찾으면, 두 테이블의 행을 결합하여 결과를 반환.&lt;br /&gt;
&lt;br /&gt;
* 만약 빌드 테이블이 PGA 메모리에 다 들어가지 않을 정도로 크다면, 테이블을 여러 개의 파티션으로 나누어 디스크에 임시로 저장한 뒤(성능 저하 구간), 각 파티션별로 빌드와 프로브 단계를 반복하는 &#039;그레이스풀 해시 조인(Graceful Hash Join)&#039;이 수행(성능저하 구간)&lt;br /&gt;
* 런타임 파티셔닝을 수행해 부분(배치/파티션) 단위로 템프에 내린 후(DISK I/O발생,성능저하) 다시 합쳐서 1-pass/멀티패스 방식으로 진행함.&lt;br /&gt;
&lt;br /&gt;
=== 파이썬 예제 ===&lt;br /&gt;
* 두 개의 CSV 파일 sales.csv와 products.csv가 있다고 가정하고, 이를 파이썬 딕셔너리로 해시 조인을 구현하는 예제.&lt;br /&gt;
1. 데이터 준비 (sales.csv, products.csv 파일)&lt;br /&gt;
&lt;br /&gt;
1) 대량 테이블 - 판매(sales.csv 파일)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sale_id,product_id,amount&lt;br /&gt;
1,101,100&lt;br /&gt;
2,102,150&lt;br /&gt;
3,101,200&lt;br /&gt;
4,103,50&lt;br /&gt;
....&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2) 제품 (products.csv)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
product_id,product_name&lt;br /&gt;
101,Laptop&lt;br /&gt;
102,Mouse&lt;br /&gt;
103,Keyboard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 해시 조인 구현 코드&lt;br /&gt;
&lt;br /&gt;
소량인 products 테이블을 빌드 테이블로, sales 테이블을 프로브 테이블로 사용하여 조인을 수행.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import csv&lt;br /&gt;
&lt;br /&gt;
# 1. 빌드 테이블: products.csv를 읽어 메모리 해시 테이블(딕셔너리) 생성&lt;br /&gt;
# &#039;product_id&#039;를 키로, &#039;product_name&#039;을 값으로 저장&lt;br /&gt;
products_hash_table = {}&lt;br /&gt;
with open(&#039;products.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # 해시 테이블 생성 (딕셔너리)&lt;br /&gt;
        products_hash_table[row[&#039;product_id&#039;]] = row[&#039;product_name&#039;]&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;--- 빌드 단계 (해시 테이블) ---&amp;quot;)&lt;br /&gt;
print(products_hash_table)&lt;br /&gt;
# 출력: {&#039;101&#039;: &#039;Laptop&#039;, &#039;102&#039;: &#039;Mouse&#039;, &#039;103&#039;: &#039;Keyboard&#039;}&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;\n--- 프로브 단계 (조인 결과) ---&amp;quot;)&lt;br /&gt;
# 2. 프로브 테이블: sales.csv를 읽어 해시 테이블에서 찾기&lt;br /&gt;
with open(&#039;sales.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # sale_id 3의 &#039;product_id&#039;인 &#039;101&#039;을 해시 테이블에서 찾음&lt;br /&gt;
        product_id = row[&#039;product_id&#039;]&lt;br /&gt;
        if product_id in products_hash_table:&lt;br /&gt;
            # 해시 테이블에서 &#039;product_id&#039;를 키로 검색하여 &#039;product_name&#039;을 가져옴&lt;br /&gt;
            product_name = products_hash_table[product_id]&lt;br /&gt;
            print(f&amp;quot;Sale ID: {row[&#039;sale_id&#039;]}, Product Name: {product_name}, Amount: {row[&#039;amount&#039;]}&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 예상 출력:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Sale ID: 1, Product Name: Laptop, Amount: 100&lt;br /&gt;
# Sale ID: 2, Product Name: Mouse, Amount: 150&lt;br /&gt;
# Sale ID: 3, Product Name: Laptop, Amount: 200&lt;br /&gt;
# Sale ID: 4, Product Name: Keyboard, Amount: 50&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# products_hash_table이라는 딕셔너리가 바로 해시 테이블 역할을 합니다. &lt;br /&gt;
# sales 테이블의 각 행을 읽을 때마다 product_id를 딕셔너리(해시 테이블)에서 빠르게 찾아 product_name을 가져와 조인된 결과를 출력하는 방식&lt;br /&gt;
# 이를 통해 전체 products 테이블을 다시 스캔할 필요 없이 O(1)에 가까운 시간 복잡도로 조인 조건을 만족하는 데이터를 찾을 수 있음.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 성능 향상 원리 ===&lt;br /&gt;
==== Hash 버킷의 역할과 기능 ====&lt;br /&gt;
::::* 해시 버킷은 두 테이블을 효율적으로 조인하기 위한 핵심적인 논리적 저장 공간. &lt;br /&gt;
::::* 이는 조인 키(Join Key)를 기반으로 데이터를 빠르게 찾고 저장하는 데 사용.&lt;br /&gt;
&lt;br /&gt;
===== 역할: 데이터의 논리적 분류 및 저장소 =====&lt;br /&gt;
:::::* 해시 버킷은 조인 키 값을 해시 함수에 넣어 얻은 해시 값이 같은 데이터들을 모아두는 공간.&lt;br /&gt;
:::::* 이는 거대한 데이터를 조인 키를 기준으로 더 작은 그룹으로 나누어 관리하는 역할을 합니다.&lt;br /&gt;
:::::* 이를 통해 빌드 테이블의 모든 데이터를 일일이 탐색하지 않고도, 특정 조인 키에 해당하는 데이터가 어느 버킷에 있는지 바로 알 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
===== 기능: Build 단계와 Probe 단계에서의 활용 =====&lt;br /&gt;
# Build 단계:&lt;br /&gt;
## Oracle은 먼저 두 테이블 중 더 작은 테이블을 선택하여 빌드 테이블로 지정합니다.&lt;br /&gt;
## 빌드 테이블의 각 행을 읽어 조인 키에 해시 함수를 적용하여 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값에 해당하는 버킷에 해당 행의 데이터를 저장합니다. 만약 같은 버킷에 이미 다른 데이터가 있다면, 이를 연결 리스트(Linked List) 형태로 연결하여 **해시 충돌(Hash Collision)**을 처리합니다.&lt;br /&gt;
# Probe 단계:&lt;br /&gt;
## 이제 더 큰 테이블인 프로브 테이블을 순차적으로 읽습니다.&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 버킷 위치를 즉시 찾아갑니다.&lt;br /&gt;
## 해당 버킷 안의 연결 리스트를 탐색하여 조인 키가 일치하는 행을 찾습니다.&lt;br /&gt;
## 일치하는 행이 발견되면 두 테이블의 데이터를 결합하여 최종 결과를 생성합니다.&lt;br /&gt;
&lt;br /&gt;
* 이러한 해시 버킷의 활용 덕분에, 해시 조인은 테이블의 크기와 상관없이 조인 키가 같은 데이터들을 O(1)에 가까운 시간 복잡도로 찾아낼 수 있어 대용량 데이터 조인에 매우 효과적입니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{:해시 버킷의 저장구조}}&lt;br /&gt;
&lt;br /&gt;
==== 해시 버킷 개수의 결정 ====&lt;br /&gt;
* 해시 조인의 최대 해시 버킷 개수는 사전 설정된 고정값이 아니라, 조인하려는 테이블의 크기, 옵티마이저의 예측, 그리고 PGA_AGGREGATE_TARGET 파라미터에 의해 동적으로 결정&lt;br /&gt;
&lt;br /&gt;
# 빌드 테이블(Build Table)의 크기: 옵티마이저가 예측한 더 작은 테이블(빌드 테이블)의 크기에 따라 필요한 해시 버킷의 수가 결정됨&lt;br /&gt;
# 할당된 PGA 메모리: PGA_AGGREGATE_TARGET에 의해 세션에 할당된 PGA 메모리 내에서 해시 테이블이 생성됨.&lt;br /&gt;
# 메모리 부족 시: 만약 할당된 PGA 메모리만으로 해시 테이블을 모두 생성할 수 없다면, Oracle은 일부 해시 버킷을 디스크의 임시 테이블스페이스(temp tablespace)로 분할하여 저장합니다. &lt;br /&gt;
## 이를 &#039;해시 스필(Hash Spill)&#039;이라고 부르며, 이 경우 I/O가 발생하여 성능이 저하됨.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인이 느려지는 원인 ====&lt;br /&gt;
* 해시 조인 성능이 느려지는 주요 원인 중 하나는 해시 버킷 때문입니다.&lt;br /&gt;
* 정확히 말하면, 해시 충돌이 심하게 발생하거나 해시 테이블이 메모리에 맞지 않아 디스크 I/O가 발생하는 경우입니다.&lt;br /&gt;
* 해시 버킷 자체는 효율성을 위한 도구지만, 비정상적인 상황에서는 오히려 성능 저하의 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
1. 해시 충돌 (Hash Collisions)&lt;br /&gt;
* 원리: 해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다.&lt;br /&gt;
* 문제점: 해시 충돌이 발생하면, 하나의 버킷에 여러 행이 연결 리스트 형태로 쌓이게 됩니다. 이 버킷을 탐색할 때, 원하는 데이터를 찾기 위해 연결된 모든 데이터를 순차적으로 비교해야 하므로, 탐색 시간이 O(1)에서 O(N)으로 늘어납니다.&lt;br /&gt;
* 결과: 조인 키 데이터 분포가 불균형하거나(데이터 편중), 해시 함수가 비효율적이면 해시 충돌이 자주 발생하여 성능이 크게 저하됩니다.&lt;br /&gt;
* (결론) 해시조인은 조인키로 사용되는 컬럼이 중복이 많을수록  성능이 좋지 않음&lt;br /&gt;
&lt;br /&gt;
* 파이썬으로 해시 충돌 구현 예제&lt;br /&gt;
** 고의로 해시 충돌을 유발하는 데이터를 사용하여 해시 조인의 성능 차이를 보여줍니다. &lt;br /&gt;
** products_collision 리스트의 모든 product_id는 해시 함수를 거치면 같은 값을 반환하도록 설계되었습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터 (해시 충돌이 거의 없음)&lt;br /&gt;
products_normal = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 102, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 103, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 의도적으로 해시 충돌을 유발하는 데이터&lt;br /&gt;
# 101, 201, 301 모두 100으로 나눈 나머지가 1이 되도록 설계&lt;br /&gt;
products_collision = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 201, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 301, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인 대상 데이터 (각 product_id에 대한 sales 데이터)&lt;br /&gt;
sales = [{&#039;sale_id&#039;: i, &#039;product_id&#039;: 101, &#039;amount&#039;: 100} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+1000, &#039;product_id&#039;: 201, &#039;amount&#039;: 150} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+2000, &#039;product_id&#039;: 301, &#039;amount&#039;: 200} for i in range(1000)]&lt;br /&gt;
&lt;br /&gt;
# 사용자 정의 해시 함수 (단순화를 위해)&lt;br /&gt;
def custom_hash(key):&lt;br /&gt;
    return key % 100&lt;br /&gt;
&lt;br /&gt;
def hash_join(build_table, probe_table):&lt;br /&gt;
    # 빌드 단계: 해시 버킷(딕셔너리) 생성&lt;br /&gt;
    # 파이썬 딕셔너리의 키 충돌을 구현하기 위해, 딕셔너리의 값을 리스트로 저장&lt;br /&gt;
    hash_buckets = {}&lt;br /&gt;
    for item in build_table:&lt;br /&gt;
        key = item[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        if bucket_index not in hash_buckets:&lt;br /&gt;
            hash_buckets[bucket_index] = []&lt;br /&gt;
        hash_buckets[bucket_index].append(item)&lt;br /&gt;
&lt;br /&gt;
    # 프로브 단계: 조인&lt;br /&gt;
    start_time = time.time()&lt;br /&gt;
    joined_results = []&lt;br /&gt;
    for sale in probe_table:&lt;br /&gt;
        key = sale[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        &lt;br /&gt;
        # 해시 버킷에서 일치하는 데이터를 찾기 (충돌 발생 시 순차 탐색)&lt;br /&gt;
        if bucket_index in hash_buckets:&lt;br /&gt;
            for product_info in hash_buckets[bucket_index]:&lt;br /&gt;
                if product_info[&#039;product_id&#039;] == key:&lt;br /&gt;
                    joined_results.append({**sale, **product_info}) # **은 언패킹연산자 {**A,**B} 는 A,B딕셔너리를 합쳐줌.&lt;br /&gt;
                    break&lt;br /&gt;
    end_time = time.time()&lt;br /&gt;
    return end_time - start_time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터로 조인&lt;br /&gt;
normal_time = hash_join(products_normal, sales)&lt;br /&gt;
&lt;br /&gt;
# 해시 충돌 데이터로 조인&lt;br /&gt;
collision_time = hash_join(products_collision, sales)&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;일반 데이터 조인 시간: {normal_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;해시 충돌 조인 시간: {collision_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;성능 저하 비율: {(collision_time / normal_time):.2f}배 느림&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
일반 데이터 조인 시간: 0.000971 초&lt;br /&gt;
해시 충돌 조인 시간: 0.001568 초&lt;br /&gt;
성능 저하 비율: 1.61배 느림&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 메모리 부족 및 디스크 I/O&lt;br /&gt;
* 원리: 해시 조인은 더 작은 테이블(빌드 테이블)을 메모리에 올려 해시 테이블을 만드는 것이 기본 전제입니다.&lt;br /&gt;
* 문제점: 빌드 테이블이 너무 커서 **메모리(PGA)**에 다 담을 수 없게 되면, Oracle은 해시 테이블을 여러 파티션으로 나누어 일부를 **임시 테이블스페이스(Temporary Tablespace)**에 저장합니다.&lt;br /&gt;
* 결과: 조인을 수행하기 위해 디스크에 기록된 파티션 데이터를 읽고 쓰는 디스크 I/O 작업이 반복적으로 발생합니다. 이는 메모리에서만 작업할 때보다 훨씬 느리므로 성능 저하의 결정적인 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인에 대한 의문점 ====&lt;br /&gt;
* hash join에서  해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다. &lt;br /&gt;
* 또한 조인키로 사용되는 컬럼이 중복이 많을수록 성능이 좋지 않습니다.  &lt;br /&gt;
# 그러면 조인키로 사용되는 &#039;&#039;&#039;컬럼의 값&#039;&#039;&#039; 이 중복이 심하면 성능 않좋아 진다는것인가? &lt;br /&gt;
# 아니면 해시함수를 통하여 생성된 해시값이 동일한경우에 성능이 않좋아 진다는 것인가? &lt;br /&gt;
# 둘다 해당한다는것인가?&lt;br /&gt;
&lt;br /&gt;
* 둘 다 성능에 영향을 미치지만, 서로 다른 이유이며 실무에서는 1번이 훨씬 더 큰 문제입니다.&lt;br /&gt;
&lt;br /&gt;
===== 조인 키 값의 중복 (더 흔한 문제) =====&lt;br /&gt;
* 조인 키 컬럼 자체의 중복이 많으면 성능이 나빠집니다.&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시: 성별 컬럼으로 조인 (값이 &#039;M&#039;, &#039;F&#039; 두 개뿐)&lt;br /&gt;
SELECT /*+ USE_HASH(a b) */ *&lt;br /&gt;
FROM employees a&lt;br /&gt;
JOIN customers b ON a.gender = b.gender&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- `gender = &#039;M&#039;` 인 행들이 모두 &#039;&#039;&#039;같은 해시 버킷&#039;&#039;&#039;에 저장됨&lt;br /&gt;
:- Probe 단계에서 해당 버킷의 수많은 행들을 일일이 비교해야 함&lt;br /&gt;
:- 해시 조인의 장점(O(1) 검색)이 사라짐&lt;br /&gt;
&lt;br /&gt;
===== 해시 충돌 (드문 문제) =====&lt;br /&gt;
* 서로 다른 조인 키 값이 우연히 같은 해시 값을 생성하는 경우입니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 예시 (실제는 아니지만 개념 설명)&lt;br /&gt;
hash(&#039;emp_id_12345&#039;) = 789  # 해시값 789&lt;br /&gt;
hash(&#039;emp_id_99999&#039;) = 789  # 다른 키인데 같은 해시값!&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- 서로 다른 키인데 같은 버킷에 저장됨&lt;br /&gt;
:- 역시 Probe 시 추가 비교 필요&lt;br /&gt;
&lt;br /&gt;
===== 실제 상황 비교 =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 A: 카디널리티 낮음 (중복 많음) - 실제 큰 문제&lt;br /&gt;
employees: 1,000,000명&lt;br /&gt;
성별(gender)컬럼 값: 남자(&#039;M&#039;), 여자(&#039;F&#039;) 두 개만&lt;br /&gt;
→ 각 버킷에 약 500,000개 행 저장&lt;br /&gt;
→ 성능 매우 나쁨&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 B: 카디널리티 높음 (중복 적음) - 좋음&lt;br /&gt;
employees: 1,000,000명  &lt;br /&gt;
employee_id: 1,000,000개 유니크 값&lt;br /&gt;
→ 각 버킷에 평균 1~2개 행&lt;br /&gt;
→ 해시 충돌 있어도 영향 미미&lt;br /&gt;
→ 성능 좋음&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 결론&lt;br /&gt;
&lt;br /&gt;
:- 조인 키 중복 문제 : 실무에서 흔하고 심각한 성능 저하 원인&lt;br /&gt;
:- 해시 충돌 문제 : Oracle의 해시 함수가 잘 설계되어 있어 드물게 발생&lt;br /&gt;
:- **실무 팁**: 조인 키는 **카디널리티가 높은 컬럼**(예: ID, 주문번호)을 사용해야 함&lt;br /&gt;
&lt;br /&gt;
성별, 부서코드(10개 정도) 같은 낮은 카디널리티 컬럼으로 Hash Join하면 성능이 매우 나빠집니다!&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
* 해시 조인이 효율적으로 작동하는 것은 해시 버킷이 잘 분산되어 있고, 해시 테이블이 메모리에서 모두 처리될 때입니다. 만약 데이터가 한쪽으로 치우쳐 해시 충돌이 심하거나, 메모리 부족으로 디스크 I/O가 발생하면 해시 조인의 장점이 사라져 성능이 느려지게 됩니다.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 장/단점 ===&lt;br /&gt;
==== 장점 ====&lt;br /&gt;
# 대용량 테이블간 &amp;quot;=&amp;quot; 조건의 조인에 적합(정렬 불필요,입력 순서 무관)&lt;br /&gt;
# 빠른 접근 가능(버킷을 이용한 빠른 처리)&lt;br /&gt;
# 병렬처리에 적합하면 효율성이 좋음&lt;br /&gt;
&lt;br /&gt;
==== 단점 ==== &lt;br /&gt;
# &amp;quot;=&amp;quot; 조건에만 적합, 범위 조건(between 이나 like) 조인은 사용할수 없음.&lt;br /&gt;
# 메모리에 의존적임(빌드테이블이 메모리내에 들어가지 못하면 템프 i/o가 발생)&lt;br /&gt;
# 조인키값이 한쪽에 치우치거나(skew),중복도가 높으면 특정 버킷에 연결된 버킷이 길어져 탐색하는 시간이 증가함(CPU사용량 급증, 충돌증가)&lt;br /&gt;
# 버킷수가 데이터 대비 부족하면 충돌 발생과 체인 길이가 늘어 탐색 비용이 증가함  &lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1572</id>
		<title>HASH 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1572"/>
		<updated>2025-10-21T01:53:40Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 해시 버킷의 저장구조 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해시 조인(Hash Join) ==&lt;br /&gt;
{{틀:서브&lt;br /&gt;
|제목=&#039;&#039;&#039;&amp;lt;big&amp;gt;해시조인시 사용되는 해시(hash)함수는 입력된 데이터를 &#039;잘게 다져서&#039; 고유하고 일정한 크기의 식별자로 만들어 냄.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
* 해시의 주요특징&lt;br /&gt;
** 단방향성 : 해시값을 원래의 값으로 되돌릴수 없다.&lt;br /&gt;
** 고유성 : 입력값이 바뀌면 해시값은 완전히 달라진다.&lt;br /&gt;
** 고정된길이 : 입력값의 크기와 상관없이 항상 동일한 길이의 출력값을 가진다.&lt;br /&gt;
|내용=* hash의 영문의 뜻 : &#039;&#039;&#039;잘게 썰다, 다지다의 의미(요리에서 재료를 잘게 썰어서 섞을때 쓰이는말, 예로 맥도날도 &#039;해시브라운(감자를 다져서 튀긴요리)&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
# 해시 조인(Hash Join)은 두 개의 테이블을 조인할 때, 더 작은 테이블을 메모리에 올리고 해시 테이블을 만들어 효율적으로 조인하는 방식&lt;br /&gt;
# 대용량 테이블을 조인할 때 주로 사용되며, 조인 조건에 인덱스가 없는 경우에도 빠르게 작동&lt;br /&gt;
&lt;br /&gt;
=== 해시 조인의 원리 ===&lt;br /&gt;
* 해시 조인은 크게 두 단계로 나뉩니다.&lt;br /&gt;
# 빌드 단계 (Build Phase):&lt;br /&gt;
## 옵티마이저는 두 테이블 중 더 작은 테이블(빌드 테이블)을 선택&lt;br /&gt;
## 이 테이블의 조인 키(컬럼)를 사용하여 PGA 메모리 영역에 해시 테이블을 생성. 각 조인 키 값은 해시 함수를 통하여 해시값으로 변환되어 해시 테이블의 특정 위치에 저장.&lt;br /&gt;
## 해시테이블 저장시 변환된 해시값들은 해시버킷의 갯수 만큼 쪼개서 할당되고 &amp;quot;버킷&amp;quot;에 행을 연결리스트(체인) 형태로 저장됨.&lt;br /&gt;
## 각 버킷은 &amp;quot;해당 해시 범위에 속하는 행들의 포인터(체인 헤드)&amp;quot; 역활을 하며 , 동일 해시값 이나 충돌이 발생하면 같은 버킷내에서 체인으로 연결됨.(많이 연결될수록 성능 저하,이구간이 성능저하 구간) &lt;br /&gt;
# 프로브 단계 (Probe Phase):&lt;br /&gt;
## 더 큰 테이블(프로브 테이블)을 순차적으로 읽어옮&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 테이블에서 일치하는 행을 찾음.(대상 버킷을 찾고 해당 버킷의 체인을 스캔하여 조인키를 비교)&lt;br /&gt;
## 일치하는 행을 찾으면, 두 테이블의 행을 결합하여 결과를 반환.&lt;br /&gt;
&lt;br /&gt;
* 만약 빌드 테이블이 PGA 메모리에 다 들어가지 않을 정도로 크다면, 테이블을 여러 개의 파티션으로 나누어 디스크에 임시로 저장한 뒤(성능 저하 구간), 각 파티션별로 빌드와 프로브 단계를 반복하는 &#039;그레이스풀 해시 조인(Graceful Hash Join)&#039;이 수행(성능저하 구간)&lt;br /&gt;
* 런타임 파티셔닝을 수행해 부분(배치/파티션) 단위로 템프에 내린 후(DISK I/O발생,성능저하) 다시 합쳐서 1-pass/멀티패스 방식으로 진행함.&lt;br /&gt;
&lt;br /&gt;
=== 파이썬 예제 ===&lt;br /&gt;
* 두 개의 CSV 파일 sales.csv와 products.csv가 있다고 가정하고, 이를 파이썬 딕셔너리로 해시 조인을 구현하는 예제.&lt;br /&gt;
1. 데이터 준비 (sales.csv, products.csv 파일)&lt;br /&gt;
&lt;br /&gt;
1) 대량 테이블 - 판매(sales.csv 파일)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sale_id,product_id,amount&lt;br /&gt;
1,101,100&lt;br /&gt;
2,102,150&lt;br /&gt;
3,101,200&lt;br /&gt;
4,103,50&lt;br /&gt;
....&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2) 제품 (products.csv)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
product_id,product_name&lt;br /&gt;
101,Laptop&lt;br /&gt;
102,Mouse&lt;br /&gt;
103,Keyboard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 해시 조인 구현 코드&lt;br /&gt;
&lt;br /&gt;
소량인 products 테이블을 빌드 테이블로, sales 테이블을 프로브 테이블로 사용하여 조인을 수행.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import csv&lt;br /&gt;
&lt;br /&gt;
# 1. 빌드 테이블: products.csv를 읽어 메모리 해시 테이블(딕셔너리) 생성&lt;br /&gt;
# &#039;product_id&#039;를 키로, &#039;product_name&#039;을 값으로 저장&lt;br /&gt;
products_hash_table = {}&lt;br /&gt;
with open(&#039;products.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # 해시 테이블 생성 (딕셔너리)&lt;br /&gt;
        products_hash_table[row[&#039;product_id&#039;]] = row[&#039;product_name&#039;]&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;--- 빌드 단계 (해시 테이블) ---&amp;quot;)&lt;br /&gt;
print(products_hash_table)&lt;br /&gt;
# 출력: {&#039;101&#039;: &#039;Laptop&#039;, &#039;102&#039;: &#039;Mouse&#039;, &#039;103&#039;: &#039;Keyboard&#039;}&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;\n--- 프로브 단계 (조인 결과) ---&amp;quot;)&lt;br /&gt;
# 2. 프로브 테이블: sales.csv를 읽어 해시 테이블에서 찾기&lt;br /&gt;
with open(&#039;sales.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # sale_id 3의 &#039;product_id&#039;인 &#039;101&#039;을 해시 테이블에서 찾음&lt;br /&gt;
        product_id = row[&#039;product_id&#039;]&lt;br /&gt;
        if product_id in products_hash_table:&lt;br /&gt;
            # 해시 테이블에서 &#039;product_id&#039;를 키로 검색하여 &#039;product_name&#039;을 가져옴&lt;br /&gt;
            product_name = products_hash_table[product_id]&lt;br /&gt;
            print(f&amp;quot;Sale ID: {row[&#039;sale_id&#039;]}, Product Name: {product_name}, Amount: {row[&#039;amount&#039;]}&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 예상 출력:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Sale ID: 1, Product Name: Laptop, Amount: 100&lt;br /&gt;
# Sale ID: 2, Product Name: Mouse, Amount: 150&lt;br /&gt;
# Sale ID: 3, Product Name: Laptop, Amount: 200&lt;br /&gt;
# Sale ID: 4, Product Name: Keyboard, Amount: 50&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# products_hash_table이라는 딕셔너리가 바로 해시 테이블 역할을 합니다. &lt;br /&gt;
# sales 테이블의 각 행을 읽을 때마다 product_id를 딕셔너리(해시 테이블)에서 빠르게 찾아 product_name을 가져와 조인된 결과를 출력하는 방식&lt;br /&gt;
# 이를 통해 전체 products 테이블을 다시 스캔할 필요 없이 O(1)에 가까운 시간 복잡도로 조인 조건을 만족하는 데이터를 찾을 수 있음.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 성능 향상 원리 ===&lt;br /&gt;
==== Hash 버킷의 역할과 기능 ====&lt;br /&gt;
::::* 해시 버킷은 두 테이블을 효율적으로 조인하기 위한 핵심적인 논리적 저장 공간. &lt;br /&gt;
::::* 이는 조인 키(Join Key)를 기반으로 데이터를 빠르게 찾고 저장하는 데 사용.&lt;br /&gt;
&lt;br /&gt;
===== 역할: 데이터의 논리적 분류 및 저장소 =====&lt;br /&gt;
:::::* 해시 버킷은 조인 키 값을 해시 함수에 넣어 얻은 해시 값이 같은 데이터들을 모아두는 공간.&lt;br /&gt;
:::::* 이는 거대한 데이터를 조인 키를 기준으로 더 작은 그룹으로 나누어 관리하는 역할을 합니다.&lt;br /&gt;
:::::* 이를 통해 빌드 테이블의 모든 데이터를 일일이 탐색하지 않고도, 특정 조인 키에 해당하는 데이터가 어느 버킷에 있는지 바로 알 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
===== 기능: Build 단계와 Probe 단계에서의 활용 =====&lt;br /&gt;
# Build 단계:&lt;br /&gt;
## Oracle은 먼저 두 테이블 중 더 작은 테이블을 선택하여 빌드 테이블로 지정합니다.&lt;br /&gt;
## 빌드 테이블의 각 행을 읽어 조인 키에 해시 함수를 적용하여 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값에 해당하는 버킷에 해당 행의 데이터를 저장합니다. 만약 같은 버킷에 이미 다른 데이터가 있다면, 이를 연결 리스트(Linked List) 형태로 연결하여 **해시 충돌(Hash Collision)**을 처리합니다.&lt;br /&gt;
# Probe 단계:&lt;br /&gt;
## 이제 더 큰 테이블인 프로브 테이블을 순차적으로 읽습니다.&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 버킷 위치를 즉시 찾아갑니다.&lt;br /&gt;
## 해당 버킷 안의 연결 리스트를 탐색하여 조인 키가 일치하는 행을 찾습니다.&lt;br /&gt;
## 일치하는 행이 발견되면 두 테이블의 데이터를 결합하여 최종 결과를 생성합니다.&lt;br /&gt;
&lt;br /&gt;
* 이러한 해시 버킷의 활용 덕분에, 해시 조인은 테이블의 크기와 상관없이 조인 키가 같은 데이터들을 O(1)에 가까운 시간 복잡도로 찾아낼 수 있어 대용량 데이터 조인에 매우 효과적입니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{해시 버킷의 저장구조}}&lt;br /&gt;
&lt;br /&gt;
==== 해시 버킷 개수의 결정 ====&lt;br /&gt;
* 해시 조인의 최대 해시 버킷 개수는 사전 설정된 고정값이 아니라, 조인하려는 테이블의 크기, 옵티마이저의 예측, 그리고 PGA_AGGREGATE_TARGET 파라미터에 의해 동적으로 결정&lt;br /&gt;
&lt;br /&gt;
# 빌드 테이블(Build Table)의 크기: 옵티마이저가 예측한 더 작은 테이블(빌드 테이블)의 크기에 따라 필요한 해시 버킷의 수가 결정됨&lt;br /&gt;
# 할당된 PGA 메모리: PGA_AGGREGATE_TARGET에 의해 세션에 할당된 PGA 메모리 내에서 해시 테이블이 생성됨.&lt;br /&gt;
# 메모리 부족 시: 만약 할당된 PGA 메모리만으로 해시 테이블을 모두 생성할 수 없다면, Oracle은 일부 해시 버킷을 디스크의 임시 테이블스페이스(temp tablespace)로 분할하여 저장합니다. &lt;br /&gt;
## 이를 &#039;해시 스필(Hash Spill)&#039;이라고 부르며, 이 경우 I/O가 발생하여 성능이 저하됨.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인이 느려지는 원인 ====&lt;br /&gt;
* 해시 조인 성능이 느려지는 주요 원인 중 하나는 해시 버킷 때문입니다.&lt;br /&gt;
* 정확히 말하면, 해시 충돌이 심하게 발생하거나 해시 테이블이 메모리에 맞지 않아 디스크 I/O가 발생하는 경우입니다.&lt;br /&gt;
* 해시 버킷 자체는 효율성을 위한 도구지만, 비정상적인 상황에서는 오히려 성능 저하의 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
1. 해시 충돌 (Hash Collisions)&lt;br /&gt;
* 원리: 해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다.&lt;br /&gt;
* 문제점: 해시 충돌이 발생하면, 하나의 버킷에 여러 행이 연결 리스트 형태로 쌓이게 됩니다. 이 버킷을 탐색할 때, 원하는 데이터를 찾기 위해 연결된 모든 데이터를 순차적으로 비교해야 하므로, 탐색 시간이 O(1)에서 O(N)으로 늘어납니다.&lt;br /&gt;
* 결과: 조인 키 데이터 분포가 불균형하거나(데이터 편중), 해시 함수가 비효율적이면 해시 충돌이 자주 발생하여 성능이 크게 저하됩니다.&lt;br /&gt;
* (결론) 해시조인은 조인키로 사용되는 컬럼이 중복이 많을수록  성능이 좋지 않음&lt;br /&gt;
&lt;br /&gt;
* 파이썬으로 해시 충돌 구현 예제&lt;br /&gt;
** 고의로 해시 충돌을 유발하는 데이터를 사용하여 해시 조인의 성능 차이를 보여줍니다. &lt;br /&gt;
** products_collision 리스트의 모든 product_id는 해시 함수를 거치면 같은 값을 반환하도록 설계되었습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터 (해시 충돌이 거의 없음)&lt;br /&gt;
products_normal = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 102, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 103, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 의도적으로 해시 충돌을 유발하는 데이터&lt;br /&gt;
# 101, 201, 301 모두 100으로 나눈 나머지가 1이 되도록 설계&lt;br /&gt;
products_collision = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 201, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 301, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인 대상 데이터 (각 product_id에 대한 sales 데이터)&lt;br /&gt;
sales = [{&#039;sale_id&#039;: i, &#039;product_id&#039;: 101, &#039;amount&#039;: 100} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+1000, &#039;product_id&#039;: 201, &#039;amount&#039;: 150} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+2000, &#039;product_id&#039;: 301, &#039;amount&#039;: 200} for i in range(1000)]&lt;br /&gt;
&lt;br /&gt;
# 사용자 정의 해시 함수 (단순화를 위해)&lt;br /&gt;
def custom_hash(key):&lt;br /&gt;
    return key % 100&lt;br /&gt;
&lt;br /&gt;
def hash_join(build_table, probe_table):&lt;br /&gt;
    # 빌드 단계: 해시 버킷(딕셔너리) 생성&lt;br /&gt;
    # 파이썬 딕셔너리의 키 충돌을 구현하기 위해, 딕셔너리의 값을 리스트로 저장&lt;br /&gt;
    hash_buckets = {}&lt;br /&gt;
    for item in build_table:&lt;br /&gt;
        key = item[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        if bucket_index not in hash_buckets:&lt;br /&gt;
            hash_buckets[bucket_index] = []&lt;br /&gt;
        hash_buckets[bucket_index].append(item)&lt;br /&gt;
&lt;br /&gt;
    # 프로브 단계: 조인&lt;br /&gt;
    start_time = time.time()&lt;br /&gt;
    joined_results = []&lt;br /&gt;
    for sale in probe_table:&lt;br /&gt;
        key = sale[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        &lt;br /&gt;
        # 해시 버킷에서 일치하는 데이터를 찾기 (충돌 발생 시 순차 탐색)&lt;br /&gt;
        if bucket_index in hash_buckets:&lt;br /&gt;
            for product_info in hash_buckets[bucket_index]:&lt;br /&gt;
                if product_info[&#039;product_id&#039;] == key:&lt;br /&gt;
                    joined_results.append({**sale, **product_info}) # **은 언패킹연산자 {**A,**B} 는 A,B딕셔너리를 합쳐줌.&lt;br /&gt;
                    break&lt;br /&gt;
    end_time = time.time()&lt;br /&gt;
    return end_time - start_time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터로 조인&lt;br /&gt;
normal_time = hash_join(products_normal, sales)&lt;br /&gt;
&lt;br /&gt;
# 해시 충돌 데이터로 조인&lt;br /&gt;
collision_time = hash_join(products_collision, sales)&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;일반 데이터 조인 시간: {normal_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;해시 충돌 조인 시간: {collision_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;성능 저하 비율: {(collision_time / normal_time):.2f}배 느림&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
일반 데이터 조인 시간: 0.000971 초&lt;br /&gt;
해시 충돌 조인 시간: 0.001568 초&lt;br /&gt;
성능 저하 비율: 1.61배 느림&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 메모리 부족 및 디스크 I/O&lt;br /&gt;
* 원리: 해시 조인은 더 작은 테이블(빌드 테이블)을 메모리에 올려 해시 테이블을 만드는 것이 기본 전제입니다.&lt;br /&gt;
* 문제점: 빌드 테이블이 너무 커서 **메모리(PGA)**에 다 담을 수 없게 되면, Oracle은 해시 테이블을 여러 파티션으로 나누어 일부를 **임시 테이블스페이스(Temporary Tablespace)**에 저장합니다.&lt;br /&gt;
* 결과: 조인을 수행하기 위해 디스크에 기록된 파티션 데이터를 읽고 쓰는 디스크 I/O 작업이 반복적으로 발생합니다. 이는 메모리에서만 작업할 때보다 훨씬 느리므로 성능 저하의 결정적인 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인에 대한 의문점 ====&lt;br /&gt;
* hash join에서  해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다. &lt;br /&gt;
* 또한 조인키로 사용되는 컬럼이 중복이 많을수록 성능이 좋지 않습니다.  &lt;br /&gt;
# 그러면 조인키로 사용되는 &#039;&#039;&#039;컬럼의 값&#039;&#039;&#039; 이 중복이 심하면 성능 않좋아 진다는것인가? &lt;br /&gt;
# 아니면 해시함수를 통하여 생성된 해시값이 동일한경우에 성능이 않좋아 진다는 것인가? &lt;br /&gt;
# 둘다 해당한다는것인가?&lt;br /&gt;
&lt;br /&gt;
* 둘 다 성능에 영향을 미치지만, 서로 다른 이유이며 실무에서는 1번이 훨씬 더 큰 문제입니다.&lt;br /&gt;
&lt;br /&gt;
===== 조인 키 값의 중복 (더 흔한 문제) =====&lt;br /&gt;
* 조인 키 컬럼 자체의 중복이 많으면 성능이 나빠집니다.&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시: 성별 컬럼으로 조인 (값이 &#039;M&#039;, &#039;F&#039; 두 개뿐)&lt;br /&gt;
SELECT /*+ USE_HASH(a b) */ *&lt;br /&gt;
FROM employees a&lt;br /&gt;
JOIN customers b ON a.gender = b.gender&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- `gender = &#039;M&#039;` 인 행들이 모두 &#039;&#039;&#039;같은 해시 버킷&#039;&#039;&#039;에 저장됨&lt;br /&gt;
:- Probe 단계에서 해당 버킷의 수많은 행들을 일일이 비교해야 함&lt;br /&gt;
:- 해시 조인의 장점(O(1) 검색)이 사라짐&lt;br /&gt;
&lt;br /&gt;
===== 해시 충돌 (드문 문제) =====&lt;br /&gt;
* 서로 다른 조인 키 값이 우연히 같은 해시 값을 생성하는 경우입니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 예시 (실제는 아니지만 개념 설명)&lt;br /&gt;
hash(&#039;emp_id_12345&#039;) = 789  # 해시값 789&lt;br /&gt;
hash(&#039;emp_id_99999&#039;) = 789  # 다른 키인데 같은 해시값!&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- 서로 다른 키인데 같은 버킷에 저장됨&lt;br /&gt;
:- 역시 Probe 시 추가 비교 필요&lt;br /&gt;
&lt;br /&gt;
===== 실제 상황 비교 =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 A: 카디널리티 낮음 (중복 많음) - 실제 큰 문제&lt;br /&gt;
employees: 1,000,000명&lt;br /&gt;
성별(gender)컬럼 값: 남자(&#039;M&#039;), 여자(&#039;F&#039;) 두 개만&lt;br /&gt;
→ 각 버킷에 약 500,000개 행 저장&lt;br /&gt;
→ 성능 매우 나쁨&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 B: 카디널리티 높음 (중복 적음) - 좋음&lt;br /&gt;
employees: 1,000,000명  &lt;br /&gt;
employee_id: 1,000,000개 유니크 값&lt;br /&gt;
→ 각 버킷에 평균 1~2개 행&lt;br /&gt;
→ 해시 충돌 있어도 영향 미미&lt;br /&gt;
→ 성능 좋음&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 결론&lt;br /&gt;
&lt;br /&gt;
:- 조인 키 중복 문제 : 실무에서 흔하고 심각한 성능 저하 원인&lt;br /&gt;
:- 해시 충돌 문제 : Oracle의 해시 함수가 잘 설계되어 있어 드물게 발생&lt;br /&gt;
:- **실무 팁**: 조인 키는 **카디널리티가 높은 컬럼**(예: ID, 주문번호)을 사용해야 함&lt;br /&gt;
&lt;br /&gt;
성별, 부서코드(10개 정도) 같은 낮은 카디널리티 컬럼으로 Hash Join하면 성능이 매우 나빠집니다!&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
* 해시 조인이 효율적으로 작동하는 것은 해시 버킷이 잘 분산되어 있고, 해시 테이블이 메모리에서 모두 처리될 때입니다. 만약 데이터가 한쪽으로 치우쳐 해시 충돌이 심하거나, 메모리 부족으로 디스크 I/O가 발생하면 해시 조인의 장점이 사라져 성능이 느려지게 됩니다.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 장/단점 ===&lt;br /&gt;
==== 장점 ====&lt;br /&gt;
# 대용량 테이블간 &amp;quot;=&amp;quot; 조건의 조인에 적합(정렬 불필요,입력 순서 무관)&lt;br /&gt;
# 빠른 접근 가능(버킷을 이용한 빠른 처리)&lt;br /&gt;
# 병렬처리에 적합하면 효율성이 좋음&lt;br /&gt;
&lt;br /&gt;
==== 단점 ==== &lt;br /&gt;
# &amp;quot;=&amp;quot; 조건에만 적합, 범위 조건(between 이나 like) 조인은 사용할수 없음.&lt;br /&gt;
# 메모리에 의존적임(빌드테이블이 메모리내에 들어가지 못하면 템프 i/o가 발생)&lt;br /&gt;
# 조인키값이 한쪽에 치우치거나(skew),중복도가 높으면 특정 버킷에 연결된 버킷이 길어져 탐색하는 시간이 증가함(CPU사용량 급증, 충돌증가)&lt;br /&gt;
# 버킷수가 데이터 대비 부족하면 충돌 발생과 체인 길이가 늘어 탐색 비용이 증가함  &lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1571</id>
		<title>해시 버킷의 저장구조</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1571"/>
		<updated>2025-10-21T01:51:47Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 각 버킷 엔트리에 포함되는 정보 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Oracle Hash Join의 내부 구조==&lt;br /&gt;
&lt;br /&gt;
* 해시 버킷의 저장 구조&lt;br /&gt;
&lt;br /&gt;
=== 해시 버킷에 저장되는 내용 ===&lt;br /&gt;
* 해시 버킷에는 &#039;&#039;&#039;주소 정보가 아닌 실제 데이터 행(row data)&#039;&#039;&#039; 이 저장됩니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 해시 테이블 구조:&lt;br /&gt;
#[해시 버킷 배열]&lt;br /&gt;
Bucket 0  → [emp_id=101, name=&#039;김철수&#039;, dept_id=10] → [emp_id=501, name=&#039;이영희&#039;, dept_id=10] → NULL&lt;br /&gt;
Bucket 1  → [emp_id=202, name=&#039;박민수&#039;, dept_id=20] → NULL&lt;br /&gt;
Bucket 2  → NULL&lt;br /&gt;
Bucket 3  → [emp_id=303, name=&#039;정수진&#039;, dept_id=30] → [emp_id=403, name=&#039;최지원&#039;, dept_id=30] → NULL&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 각 버킷 엔트리에 포함되는 정보 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
┌─────────────────────────────┐&lt;br /&gt;
│  해시 버킷 엔트리 (Entry)       │&lt;br /&gt;
├─────────────────────────────┤&lt;br /&gt;
│ 1. 조인 키 값                  │  ← emp_id = 101&lt;br /&gt;
│ 2. 필요한 컬럼 데이터            │  ← name, dept_id 등&lt;br /&gt;
│ 3. Next 포인터 (체이닝)         │  ← 다음 엔트리 주소&lt;br /&gt;
└─────────────────────────────┘&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 실제 메모리 구조 예시 ===&lt;br /&gt;
==== Build 단계 ====&lt;br /&gt;
* Build 단계: employees 테이블 (작은 테이블)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT * FROM employees WHERE dept_id = 10;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 단계에서 생성되는 해시 테이블:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
메모리 영역 (PGA - Work Area)&lt;br /&gt;
├─ 해시 버킷 배열 (포인터 배열)&lt;br /&gt;
│  [0] → 0x1000  (첫 번째 엔트리 주소)&lt;br /&gt;
│  [1] → NULL&lt;br /&gt;
│  [2] → 0x2000&lt;br /&gt;
│  [3] → 0x3000 → 0x3100  (충돌 발생, 체인)&lt;br /&gt;
│  ...&lt;br /&gt;
│&lt;br /&gt;
└─ 실제 데이터 영역&lt;br /&gt;
   0x1000: {emp_id: 101, name: &#039;김철수&#039;, dept_id: 10, next: NULL}&lt;br /&gt;
   0x2000: {emp_id: 202, name: &#039;박민수&#039;, dept_id: 20, next: NULL}&lt;br /&gt;
   0x3000: {emp_id: 303, name: &#039;정수진&#039;, dept_id: 30, next: 0x3100}&lt;br /&gt;
   0x3100: {emp_id: 403, name: &#039;최지원&#039;, dept_id: 30, next: NULL}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 과정 상세&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 의사 코드로 표현한 Build 단계&lt;br /&gt;
&lt;br /&gt;
hash_table = Array[BUCKET_SIZE]  # 버킷 배열 초기화&lt;br /&gt;
&lt;br /&gt;
for row in small_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 엔트리 생성 (실제 데이터 복사)&lt;br /&gt;
    entry = {&lt;br /&gt;
        &#039;join_key&#039;: row.join_key,&lt;br /&gt;
        &#039;column1&#039;: row.column1,&lt;br /&gt;
        &#039;column2&#039;: row.column2,&lt;br /&gt;
        &#039;next&#039;: NULL  # 체인 포인터&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    # 3. 버킷에 삽입 (체이닝 방식)&lt;br /&gt;
    if hash_table[bucket_index] is NULL:&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
    else:&lt;br /&gt;
        # 충돌 발생 - 체인의 맨 앞에 추가&lt;br /&gt;
        entry.next = hash_table[bucket_index]&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Probe 단계 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Probe 단계&lt;br /&gt;
&lt;br /&gt;
for probe_row in large_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(probe_row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 해당 버킷의 체인을 순회&lt;br /&gt;
    entry = hash_table[bucket_index]&lt;br /&gt;
    while entry is not NULL:&lt;br /&gt;
        # 3. 실제 조인 키 비교 (중요!)&lt;br /&gt;
        if entry.join_key == probe_row.join_key:&lt;br /&gt;
            # 조인 성공 - 결과 생성&lt;br /&gt;
            output(entry.data + probe_row.data)&lt;br /&gt;
        &lt;br /&gt;
        entry = entry.next  # 체인의 다음 엔트리로&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 왜 이렇게 설계 되었을까? ===&lt;br /&gt;
# 디스크 기반 접근 (주소만 저장한다면):&lt;br /&gt;
## 조인 매칭마다 디스크 I/O 발생&lt;br /&gt;
## 성능: 매우 느림&lt;br /&gt;
# 메모리 기반 접근 (실제 데이터 저장):&lt;br /&gt;
## 모든 매칭이 메모리에서 처리&lt;br /&gt;
## 성능: 매우 빠름 (Hash Join의 장점)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 결론 : 해시 버킷 구조의 핵심 포인트 ===&lt;br /&gt;
# 데이터 블럭의 주소만 저장 하나요? &lt;br /&gt;
#: NO → 실제 행 데이터를 메모리에 복사하여 저장&lt;br /&gt;
# 왜 데이터를 저장하나요? &lt;br /&gt;
#:Probe 단계에서 디스크 I/O 없이 빠르게 접근하기 위함&lt;br /&gt;
# DB의 어디 영역에 저장하나요? &lt;br /&gt;
#: 저장 위치: PGA의 Work Area (메모리)&lt;br /&gt;
# 해시충돌시 처리방법은?&lt;br /&gt;
#: 체이닝(Chaining) 방식 - 링크드 리스트&lt;br /&gt;
# 메모리 부족 시 에는?&lt;br /&gt;
#: 디스크 임시 테이블스페이스 사용 (성능 저하)&lt;br /&gt;
&lt;br /&gt;
* 따라서 Hash Join은 **작은 테이블 전체를 메모리에 로드**하는 것이 핵심입니다!&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1570</id>
		<title>해시 버킷의 저장구조</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1570"/>
		<updated>2025-10-21T01:51:34Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 각 버킷 엔트리에 포함되는 정보 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Oracle Hash Join의 내부 구조==&lt;br /&gt;
&lt;br /&gt;
* 해시 버킷의 저장 구조&lt;br /&gt;
&lt;br /&gt;
=== 해시 버킷에 저장되는 내용 ===&lt;br /&gt;
* 해시 버킷에는 &#039;&#039;&#039;주소 정보가 아닌 실제 데이터 행(row data)&#039;&#039;&#039; 이 저장됩니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 해시 테이블 구조:&lt;br /&gt;
#[해시 버킷 배열]&lt;br /&gt;
Bucket 0  → [emp_id=101, name=&#039;김철수&#039;, dept_id=10] → [emp_id=501, name=&#039;이영희&#039;, dept_id=10] → NULL&lt;br /&gt;
Bucket 1  → [emp_id=202, name=&#039;박민수&#039;, dept_id=20] → NULL&lt;br /&gt;
Bucket 2  → NULL&lt;br /&gt;
Bucket 3  → [emp_id=303, name=&#039;정수진&#039;, dept_id=30] → [emp_id=403, name=&#039;최지원&#039;, dept_id=30] → NULL&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 각 버킷 엔트리에 포함되는 정보 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
┌─────────────────────────────┐&lt;br /&gt;
│  해시 버킷 엔트리 (Entry)       │&lt;br /&gt;
├─────────────────────────────┤&lt;br /&gt;
│ 1. 조인 키 값                 │  ← emp_id = 101&lt;br /&gt;
│ 2. 필요한 컬럼 데이터           │  ← name, dept_id 등&lt;br /&gt;
│ 3. Next 포인터 (체이닝)        │  ← 다음 엔트리 주소&lt;br /&gt;
└─────────────────────────────┘&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 실제 메모리 구조 예시 ===&lt;br /&gt;
==== Build 단계 ====&lt;br /&gt;
* Build 단계: employees 테이블 (작은 테이블)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT * FROM employees WHERE dept_id = 10;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 단계에서 생성되는 해시 테이블:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
메모리 영역 (PGA - Work Area)&lt;br /&gt;
├─ 해시 버킷 배열 (포인터 배열)&lt;br /&gt;
│  [0] → 0x1000  (첫 번째 엔트리 주소)&lt;br /&gt;
│  [1] → NULL&lt;br /&gt;
│  [2] → 0x2000&lt;br /&gt;
│  [3] → 0x3000 → 0x3100  (충돌 발생, 체인)&lt;br /&gt;
│  ...&lt;br /&gt;
│&lt;br /&gt;
└─ 실제 데이터 영역&lt;br /&gt;
   0x1000: {emp_id: 101, name: &#039;김철수&#039;, dept_id: 10, next: NULL}&lt;br /&gt;
   0x2000: {emp_id: 202, name: &#039;박민수&#039;, dept_id: 20, next: NULL}&lt;br /&gt;
   0x3000: {emp_id: 303, name: &#039;정수진&#039;, dept_id: 30, next: 0x3100}&lt;br /&gt;
   0x3100: {emp_id: 403, name: &#039;최지원&#039;, dept_id: 30, next: NULL}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 과정 상세&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 의사 코드로 표현한 Build 단계&lt;br /&gt;
&lt;br /&gt;
hash_table = Array[BUCKET_SIZE]  # 버킷 배열 초기화&lt;br /&gt;
&lt;br /&gt;
for row in small_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 엔트리 생성 (실제 데이터 복사)&lt;br /&gt;
    entry = {&lt;br /&gt;
        &#039;join_key&#039;: row.join_key,&lt;br /&gt;
        &#039;column1&#039;: row.column1,&lt;br /&gt;
        &#039;column2&#039;: row.column2,&lt;br /&gt;
        &#039;next&#039;: NULL  # 체인 포인터&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    # 3. 버킷에 삽입 (체이닝 방식)&lt;br /&gt;
    if hash_table[bucket_index] is NULL:&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
    else:&lt;br /&gt;
        # 충돌 발생 - 체인의 맨 앞에 추가&lt;br /&gt;
        entry.next = hash_table[bucket_index]&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Probe 단계 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Probe 단계&lt;br /&gt;
&lt;br /&gt;
for probe_row in large_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(probe_row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 해당 버킷의 체인을 순회&lt;br /&gt;
    entry = hash_table[bucket_index]&lt;br /&gt;
    while entry is not NULL:&lt;br /&gt;
        # 3. 실제 조인 키 비교 (중요!)&lt;br /&gt;
        if entry.join_key == probe_row.join_key:&lt;br /&gt;
            # 조인 성공 - 결과 생성&lt;br /&gt;
            output(entry.data + probe_row.data)&lt;br /&gt;
        &lt;br /&gt;
        entry = entry.next  # 체인의 다음 엔트리로&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 왜 이렇게 설계 되었을까? ===&lt;br /&gt;
# 디스크 기반 접근 (주소만 저장한다면):&lt;br /&gt;
## 조인 매칭마다 디스크 I/O 발생&lt;br /&gt;
## 성능: 매우 느림&lt;br /&gt;
# 메모리 기반 접근 (실제 데이터 저장):&lt;br /&gt;
## 모든 매칭이 메모리에서 처리&lt;br /&gt;
## 성능: 매우 빠름 (Hash Join의 장점)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 결론 : 해시 버킷 구조의 핵심 포인트 ===&lt;br /&gt;
# 데이터 블럭의 주소만 저장 하나요? &lt;br /&gt;
#: NO → 실제 행 데이터를 메모리에 복사하여 저장&lt;br /&gt;
# 왜 데이터를 저장하나요? &lt;br /&gt;
#:Probe 단계에서 디스크 I/O 없이 빠르게 접근하기 위함&lt;br /&gt;
# DB의 어디 영역에 저장하나요? &lt;br /&gt;
#: 저장 위치: PGA의 Work Area (메모리)&lt;br /&gt;
# 해시충돌시 처리방법은?&lt;br /&gt;
#: 체이닝(Chaining) 방식 - 링크드 리스트&lt;br /&gt;
# 메모리 부족 시 에는?&lt;br /&gt;
#: 디스크 임시 테이블스페이스 사용 (성능 저하)&lt;br /&gt;
&lt;br /&gt;
* 따라서 Hash Join은 **작은 테이블 전체를 메모리에 로드**하는 것이 핵심입니다!&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1569</id>
		<title>해시 버킷의 저장구조</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%ED%95%B4%EC%8B%9C_%EB%B2%84%ED%82%B7%EC%9D%98_%EC%A0%80%EC%9E%A5%EA%B5%AC%EC%A1%B0&amp;diff=1569"/>
		<updated>2025-10-21T01:51:11Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: 새 문서: == Oracle Hash Join의 내부 구조==  * 해시 버킷의 저장 구조  === 해시 버킷에 저장되는 내용 === * 해시 버킷에는 &amp;#039;&amp;#039;&amp;#039;주소 정보가 아닌 실제 데이터 행(row data)&amp;#039;&amp;#039;&amp;#039; 이 저장됩니다.  &amp;lt;source lang=python&amp;gt;  # 해시 테이블 구조: #[해시 버킷 배열] Bucket 0  → [emp_id=101, name=&amp;#039;김철수&amp;#039;, dept_id=10] → [emp_id=501, name=&amp;#039;이영희&amp;#039;, dept_id=10] → NULL Bucket 1  → [emp_id=202, name=&amp;#039;박민수&amp;#039;, dept_id=20] → NULL Bucket...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Oracle Hash Join의 내부 구조==&lt;br /&gt;
&lt;br /&gt;
* 해시 버킷의 저장 구조&lt;br /&gt;
&lt;br /&gt;
=== 해시 버킷에 저장되는 내용 ===&lt;br /&gt;
* 해시 버킷에는 &#039;&#039;&#039;주소 정보가 아닌 실제 데이터 행(row data)&#039;&#039;&#039; 이 저장됩니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 해시 테이블 구조:&lt;br /&gt;
#[해시 버킷 배열]&lt;br /&gt;
Bucket 0  → [emp_id=101, name=&#039;김철수&#039;, dept_id=10] → [emp_id=501, name=&#039;이영희&#039;, dept_id=10] → NULL&lt;br /&gt;
Bucket 1  → [emp_id=202, name=&#039;박민수&#039;, dept_id=20] → NULL&lt;br /&gt;
Bucket 2  → NULL&lt;br /&gt;
Bucket 3  → [emp_id=303, name=&#039;정수진&#039;, dept_id=30] → [emp_id=403, name=&#039;최지원&#039;, dept_id=30] → NULL&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 각 버킷 엔트리에 포함되는 정보 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
┌─────────────────────────────┐&lt;br /&gt;
│  해시 버킷 엔트리 (Entry)     │&lt;br /&gt;
├─────────────────────────────┤&lt;br /&gt;
│ 1. 조인 키 값                │  ← emp_id = 101&lt;br /&gt;
│ 2. 필요한 컬럼 데이터         │  ← name, dept_id 등&lt;br /&gt;
│ 3. Next 포인터 (체이닝)      │  ← 다음 엔트리 주소&lt;br /&gt;
└─────────────────────────────┘&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 실제 메모리 구조 예시 ===&lt;br /&gt;
==== Build 단계 ====&lt;br /&gt;
* Build 단계: employees 테이블 (작은 테이블)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
SELECT * FROM employees WHERE dept_id = 10;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 단계에서 생성되는 해시 테이블:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
메모리 영역 (PGA - Work Area)&lt;br /&gt;
├─ 해시 버킷 배열 (포인터 배열)&lt;br /&gt;
│  [0] → 0x1000  (첫 번째 엔트리 주소)&lt;br /&gt;
│  [1] → NULL&lt;br /&gt;
│  [2] → 0x2000&lt;br /&gt;
│  [3] → 0x3000 → 0x3100  (충돌 발생, 체인)&lt;br /&gt;
│  ...&lt;br /&gt;
│&lt;br /&gt;
└─ 실제 데이터 영역&lt;br /&gt;
   0x1000: {emp_id: 101, name: &#039;김철수&#039;, dept_id: 10, next: NULL}&lt;br /&gt;
   0x2000: {emp_id: 202, name: &#039;박민수&#039;, dept_id: 20, next: NULL}&lt;br /&gt;
   0x3000: {emp_id: 303, name: &#039;정수진&#039;, dept_id: 30, next: 0x3100}&lt;br /&gt;
   0x3100: {emp_id: 403, name: &#039;최지원&#039;, dept_id: 30, next: NULL}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Build 과정 상세&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# 의사 코드로 표현한 Build 단계&lt;br /&gt;
&lt;br /&gt;
hash_table = Array[BUCKET_SIZE]  # 버킷 배열 초기화&lt;br /&gt;
&lt;br /&gt;
for row in small_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 엔트리 생성 (실제 데이터 복사)&lt;br /&gt;
    entry = {&lt;br /&gt;
        &#039;join_key&#039;: row.join_key,&lt;br /&gt;
        &#039;column1&#039;: row.column1,&lt;br /&gt;
        &#039;column2&#039;: row.column2,&lt;br /&gt;
        &#039;next&#039;: NULL  # 체인 포인터&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    # 3. 버킷에 삽입 (체이닝 방식)&lt;br /&gt;
    if hash_table[bucket_index] is NULL:&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
    else:&lt;br /&gt;
        # 충돌 발생 - 체인의 맨 앞에 추가&lt;br /&gt;
        entry.next = hash_table[bucket_index]&lt;br /&gt;
        hash_table[bucket_index] = entry&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Probe 단계 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Probe 단계&lt;br /&gt;
&lt;br /&gt;
for probe_row in large_table:&lt;br /&gt;
    # 1. 해시 값 계산&lt;br /&gt;
    hash_value = hash_function(probe_row.join_key)&lt;br /&gt;
    bucket_index = hash_value % BUCKET_SIZE&lt;br /&gt;
    &lt;br /&gt;
    # 2. 해당 버킷의 체인을 순회&lt;br /&gt;
    entry = hash_table[bucket_index]&lt;br /&gt;
    while entry is not NULL:&lt;br /&gt;
        # 3. 실제 조인 키 비교 (중요!)&lt;br /&gt;
        if entry.join_key == probe_row.join_key:&lt;br /&gt;
            # 조인 성공 - 결과 생성&lt;br /&gt;
            output(entry.data + probe_row.data)&lt;br /&gt;
        &lt;br /&gt;
        entry = entry.next  # 체인의 다음 엔트리로&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 왜 이렇게 설계 되었을까? ===&lt;br /&gt;
# 디스크 기반 접근 (주소만 저장한다면):&lt;br /&gt;
## 조인 매칭마다 디스크 I/O 발생&lt;br /&gt;
## 성능: 매우 느림&lt;br /&gt;
# 메모리 기반 접근 (실제 데이터 저장):&lt;br /&gt;
## 모든 매칭이 메모리에서 처리&lt;br /&gt;
## 성능: 매우 빠름 (Hash Join의 장점)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 결론 : 해시 버킷 구조의 핵심 포인트 ===&lt;br /&gt;
# 데이터 블럭의 주소만 저장 하나요? &lt;br /&gt;
#: NO → 실제 행 데이터를 메모리에 복사하여 저장&lt;br /&gt;
# 왜 데이터를 저장하나요? &lt;br /&gt;
#:Probe 단계에서 디스크 I/O 없이 빠르게 접근하기 위함&lt;br /&gt;
# DB의 어디 영역에 저장하나요? &lt;br /&gt;
#: 저장 위치: PGA의 Work Area (메모리)&lt;br /&gt;
# 해시충돌시 처리방법은?&lt;br /&gt;
#: 체이닝(Chaining) 방식 - 링크드 리스트&lt;br /&gt;
# 메모리 부족 시 에는?&lt;br /&gt;
#: 디스크 임시 테이블스페이스 사용 (성능 저하)&lt;br /&gt;
&lt;br /&gt;
* 따라서 Hash Join은 **작은 테이블 전체를 메모리에 로드**하는 것이 핵심입니다!&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1568</id>
		<title>HASH 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1568"/>
		<updated>2025-10-21T01:36:02Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 해시 버킷 개수의 결정 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해시 조인(Hash Join) ==&lt;br /&gt;
{{틀:서브&lt;br /&gt;
|제목=&#039;&#039;&#039;&amp;lt;big&amp;gt;해시조인시 사용되는 해시(hash)함수는 입력된 데이터를 &#039;잘게 다져서&#039; 고유하고 일정한 크기의 식별자로 만들어 냄.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
* 해시의 주요특징&lt;br /&gt;
** 단방향성 : 해시값을 원래의 값으로 되돌릴수 없다.&lt;br /&gt;
** 고유성 : 입력값이 바뀌면 해시값은 완전히 달라진다.&lt;br /&gt;
** 고정된길이 : 입력값의 크기와 상관없이 항상 동일한 길이의 출력값을 가진다.&lt;br /&gt;
|내용=* hash의 영문의 뜻 : &#039;&#039;&#039;잘게 썰다, 다지다의 의미(요리에서 재료를 잘게 썰어서 섞을때 쓰이는말, 예로 맥도날도 &#039;해시브라운(감자를 다져서 튀긴요리)&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
# 해시 조인(Hash Join)은 두 개의 테이블을 조인할 때, 더 작은 테이블을 메모리에 올리고 해시 테이블을 만들어 효율적으로 조인하는 방식&lt;br /&gt;
# 대용량 테이블을 조인할 때 주로 사용되며, 조인 조건에 인덱스가 없는 경우에도 빠르게 작동&lt;br /&gt;
&lt;br /&gt;
=== 해시 조인의 원리 ===&lt;br /&gt;
* 해시 조인은 크게 두 단계로 나뉩니다.&lt;br /&gt;
# 빌드 단계 (Build Phase):&lt;br /&gt;
## 옵티마이저는 두 테이블 중 더 작은 테이블(빌드 테이블)을 선택&lt;br /&gt;
## 이 테이블의 조인 키(컬럼)를 사용하여 PGA 메모리 영역에 해시 테이블을 생성. 각 조인 키 값은 해시 함수를 통하여 해시값으로 변환되어 해시 테이블의 특정 위치에 저장.&lt;br /&gt;
## 해시테이블 저장시 변환된 해시값들은 해시버킷의 갯수 만큼 쪼개서 할당되고 &amp;quot;버킷&amp;quot;에 행을 연결리스트(체인) 형태로 저장됨.&lt;br /&gt;
## 각 버킷은 &amp;quot;해당 해시 범위에 속하는 행들의 포인터(체인 헤드)&amp;quot; 역활을 하며 , 동일 해시값 이나 충돌이 발생하면 같은 버킷내에서 체인으로 연결됨.(많이 연결될수록 성능 저하,이구간이 성능저하 구간) &lt;br /&gt;
# 프로브 단계 (Probe Phase):&lt;br /&gt;
## 더 큰 테이블(프로브 테이블)을 순차적으로 읽어옮&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 테이블에서 일치하는 행을 찾음.(대상 버킷을 찾고 해당 버킷의 체인을 스캔하여 조인키를 비교)&lt;br /&gt;
## 일치하는 행을 찾으면, 두 테이블의 행을 결합하여 결과를 반환.&lt;br /&gt;
&lt;br /&gt;
* 만약 빌드 테이블이 PGA 메모리에 다 들어가지 않을 정도로 크다면, 테이블을 여러 개의 파티션으로 나누어 디스크에 임시로 저장한 뒤(성능 저하 구간), 각 파티션별로 빌드와 프로브 단계를 반복하는 &#039;그레이스풀 해시 조인(Graceful Hash Join)&#039;이 수행(성능저하 구간)&lt;br /&gt;
* 런타임 파티셔닝을 수행해 부분(배치/파티션) 단위로 템프에 내린 후(DISK I/O발생,성능저하) 다시 합쳐서 1-pass/멀티패스 방식으로 진행함.&lt;br /&gt;
&lt;br /&gt;
=== 파이썬 예제 ===&lt;br /&gt;
* 두 개의 CSV 파일 sales.csv와 products.csv가 있다고 가정하고, 이를 파이썬 딕셔너리로 해시 조인을 구현하는 예제.&lt;br /&gt;
1. 데이터 준비 (sales.csv, products.csv 파일)&lt;br /&gt;
&lt;br /&gt;
1) 대량 테이블 - 판매(sales.csv 파일)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sale_id,product_id,amount&lt;br /&gt;
1,101,100&lt;br /&gt;
2,102,150&lt;br /&gt;
3,101,200&lt;br /&gt;
4,103,50&lt;br /&gt;
....&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2) 제품 (products.csv)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
product_id,product_name&lt;br /&gt;
101,Laptop&lt;br /&gt;
102,Mouse&lt;br /&gt;
103,Keyboard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 해시 조인 구현 코드&lt;br /&gt;
&lt;br /&gt;
소량인 products 테이블을 빌드 테이블로, sales 테이블을 프로브 테이블로 사용하여 조인을 수행.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import csv&lt;br /&gt;
&lt;br /&gt;
# 1. 빌드 테이블: products.csv를 읽어 메모리 해시 테이블(딕셔너리) 생성&lt;br /&gt;
# &#039;product_id&#039;를 키로, &#039;product_name&#039;을 값으로 저장&lt;br /&gt;
products_hash_table = {}&lt;br /&gt;
with open(&#039;products.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # 해시 테이블 생성 (딕셔너리)&lt;br /&gt;
        products_hash_table[row[&#039;product_id&#039;]] = row[&#039;product_name&#039;]&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;--- 빌드 단계 (해시 테이블) ---&amp;quot;)&lt;br /&gt;
print(products_hash_table)&lt;br /&gt;
# 출력: {&#039;101&#039;: &#039;Laptop&#039;, &#039;102&#039;: &#039;Mouse&#039;, &#039;103&#039;: &#039;Keyboard&#039;}&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;\n--- 프로브 단계 (조인 결과) ---&amp;quot;)&lt;br /&gt;
# 2. 프로브 테이블: sales.csv를 읽어 해시 테이블에서 찾기&lt;br /&gt;
with open(&#039;sales.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # sale_id 3의 &#039;product_id&#039;인 &#039;101&#039;을 해시 테이블에서 찾음&lt;br /&gt;
        product_id = row[&#039;product_id&#039;]&lt;br /&gt;
        if product_id in products_hash_table:&lt;br /&gt;
            # 해시 테이블에서 &#039;product_id&#039;를 키로 검색하여 &#039;product_name&#039;을 가져옴&lt;br /&gt;
            product_name = products_hash_table[product_id]&lt;br /&gt;
            print(f&amp;quot;Sale ID: {row[&#039;sale_id&#039;]}, Product Name: {product_name}, Amount: {row[&#039;amount&#039;]}&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 예상 출력:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Sale ID: 1, Product Name: Laptop, Amount: 100&lt;br /&gt;
# Sale ID: 2, Product Name: Mouse, Amount: 150&lt;br /&gt;
# Sale ID: 3, Product Name: Laptop, Amount: 200&lt;br /&gt;
# Sale ID: 4, Product Name: Keyboard, Amount: 50&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# products_hash_table이라는 딕셔너리가 바로 해시 테이블 역할을 합니다. &lt;br /&gt;
# sales 테이블의 각 행을 읽을 때마다 product_id를 딕셔너리(해시 테이블)에서 빠르게 찾아 product_name을 가져와 조인된 결과를 출력하는 방식&lt;br /&gt;
# 이를 통해 전체 products 테이블을 다시 스캔할 필요 없이 O(1)에 가까운 시간 복잡도로 조인 조건을 만족하는 데이터를 찾을 수 있음.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 성능 향상 원리 ===&lt;br /&gt;
==== Hash 버킷의 역할과 기능 ====&lt;br /&gt;
::::* 해시 버킷은 두 테이블을 효율적으로 조인하기 위한 핵심적인 논리적 저장 공간. &lt;br /&gt;
::::* 이는 조인 키(Join Key)를 기반으로 데이터를 빠르게 찾고 저장하는 데 사용.&lt;br /&gt;
&lt;br /&gt;
===== 역할: 데이터의 논리적 분류 및 저장소 =====&lt;br /&gt;
:::::* 해시 버킷은 조인 키 값을 해시 함수에 넣어 얻은 해시 값이 같은 데이터들을 모아두는 공간.&lt;br /&gt;
:::::* 이는 거대한 데이터를 조인 키를 기준으로 더 작은 그룹으로 나누어 관리하는 역할을 합니다.&lt;br /&gt;
:::::* 이를 통해 빌드 테이블의 모든 데이터를 일일이 탐색하지 않고도, 특정 조인 키에 해당하는 데이터가 어느 버킷에 있는지 바로 알 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
===== 기능: Build 단계와 Probe 단계에서의 활용 =====&lt;br /&gt;
# Build 단계:&lt;br /&gt;
## Oracle은 먼저 두 테이블 중 더 작은 테이블을 선택하여 빌드 테이블로 지정합니다.&lt;br /&gt;
## 빌드 테이블의 각 행을 읽어 조인 키에 해시 함수를 적용하여 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값에 해당하는 버킷에 해당 행의 데이터를 저장합니다. 만약 같은 버킷에 이미 다른 데이터가 있다면, 이를 연결 리스트(Linked List) 형태로 연결하여 **해시 충돌(Hash Collision)**을 처리합니다.&lt;br /&gt;
# Probe 단계:&lt;br /&gt;
## 이제 더 큰 테이블인 프로브 테이블을 순차적으로 읽습니다.&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 버킷 위치를 즉시 찾아갑니다.&lt;br /&gt;
## 해당 버킷 안의 연결 리스트를 탐색하여 조인 키가 일치하는 행을 찾습니다.&lt;br /&gt;
## 일치하는 행이 발견되면 두 테이블의 데이터를 결합하여 최종 결과를 생성합니다.&lt;br /&gt;
&lt;br /&gt;
* 이러한 해시 버킷의 활용 덕분에, 해시 조인은 테이블의 크기와 상관없이 조인 키가 같은 데이터들을 O(1)에 가까운 시간 복잡도로 찾아낼 수 있어 대용량 데이터 조인에 매우 효과적입니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== [[해시 버킷의 저장구조]] ====&lt;br /&gt;
==== 해시 버킷 개수의 결정 ====&lt;br /&gt;
* 해시 조인의 최대 해시 버킷 개수는 사전 설정된 고정값이 아니라, 조인하려는 테이블의 크기, 옵티마이저의 예측, 그리고 PGA_AGGREGATE_TARGET 파라미터에 의해 동적으로 결정&lt;br /&gt;
&lt;br /&gt;
# 빌드 테이블(Build Table)의 크기: 옵티마이저가 예측한 더 작은 테이블(빌드 테이블)의 크기에 따라 필요한 해시 버킷의 수가 결정됨&lt;br /&gt;
# 할당된 PGA 메모리: PGA_AGGREGATE_TARGET에 의해 세션에 할당된 PGA 메모리 내에서 해시 테이블이 생성됨.&lt;br /&gt;
# 메모리 부족 시: 만약 할당된 PGA 메모리만으로 해시 테이블을 모두 생성할 수 없다면, Oracle은 일부 해시 버킷을 디스크의 임시 테이블스페이스(temp tablespace)로 분할하여 저장합니다. &lt;br /&gt;
## 이를 &#039;해시 스필(Hash Spill)&#039;이라고 부르며, 이 경우 I/O가 발생하여 성능이 저하됨.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인이 느려지는 원인 ====&lt;br /&gt;
* 해시 조인 성능이 느려지는 주요 원인 중 하나는 해시 버킷 때문입니다.&lt;br /&gt;
* 정확히 말하면, 해시 충돌이 심하게 발생하거나 해시 테이블이 메모리에 맞지 않아 디스크 I/O가 발생하는 경우입니다.&lt;br /&gt;
* 해시 버킷 자체는 효율성을 위한 도구지만, 비정상적인 상황에서는 오히려 성능 저하의 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
1. 해시 충돌 (Hash Collisions)&lt;br /&gt;
* 원리: 해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다.&lt;br /&gt;
* 문제점: 해시 충돌이 발생하면, 하나의 버킷에 여러 행이 연결 리스트 형태로 쌓이게 됩니다. 이 버킷을 탐색할 때, 원하는 데이터를 찾기 위해 연결된 모든 데이터를 순차적으로 비교해야 하므로, 탐색 시간이 O(1)에서 O(N)으로 늘어납니다.&lt;br /&gt;
* 결과: 조인 키 데이터 분포가 불균형하거나(데이터 편중), 해시 함수가 비효율적이면 해시 충돌이 자주 발생하여 성능이 크게 저하됩니다.&lt;br /&gt;
* (결론) 해시조인은 조인키로 사용되는 컬럼이 중복이 많을수록  성능이 좋지 않음&lt;br /&gt;
&lt;br /&gt;
* 파이썬으로 해시 충돌 구현 예제&lt;br /&gt;
** 고의로 해시 충돌을 유발하는 데이터를 사용하여 해시 조인의 성능 차이를 보여줍니다. &lt;br /&gt;
** products_collision 리스트의 모든 product_id는 해시 함수를 거치면 같은 값을 반환하도록 설계되었습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터 (해시 충돌이 거의 없음)&lt;br /&gt;
products_normal = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 102, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 103, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 의도적으로 해시 충돌을 유발하는 데이터&lt;br /&gt;
# 101, 201, 301 모두 100으로 나눈 나머지가 1이 되도록 설계&lt;br /&gt;
products_collision = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 201, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 301, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인 대상 데이터 (각 product_id에 대한 sales 데이터)&lt;br /&gt;
sales = [{&#039;sale_id&#039;: i, &#039;product_id&#039;: 101, &#039;amount&#039;: 100} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+1000, &#039;product_id&#039;: 201, &#039;amount&#039;: 150} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+2000, &#039;product_id&#039;: 301, &#039;amount&#039;: 200} for i in range(1000)]&lt;br /&gt;
&lt;br /&gt;
# 사용자 정의 해시 함수 (단순화를 위해)&lt;br /&gt;
def custom_hash(key):&lt;br /&gt;
    return key % 100&lt;br /&gt;
&lt;br /&gt;
def hash_join(build_table, probe_table):&lt;br /&gt;
    # 빌드 단계: 해시 버킷(딕셔너리) 생성&lt;br /&gt;
    # 파이썬 딕셔너리의 키 충돌을 구현하기 위해, 딕셔너리의 값을 리스트로 저장&lt;br /&gt;
    hash_buckets = {}&lt;br /&gt;
    for item in build_table:&lt;br /&gt;
        key = item[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        if bucket_index not in hash_buckets:&lt;br /&gt;
            hash_buckets[bucket_index] = []&lt;br /&gt;
        hash_buckets[bucket_index].append(item)&lt;br /&gt;
&lt;br /&gt;
    # 프로브 단계: 조인&lt;br /&gt;
    start_time = time.time()&lt;br /&gt;
    joined_results = []&lt;br /&gt;
    for sale in probe_table:&lt;br /&gt;
        key = sale[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        &lt;br /&gt;
        # 해시 버킷에서 일치하는 데이터를 찾기 (충돌 발생 시 순차 탐색)&lt;br /&gt;
        if bucket_index in hash_buckets:&lt;br /&gt;
            for product_info in hash_buckets[bucket_index]:&lt;br /&gt;
                if product_info[&#039;product_id&#039;] == key:&lt;br /&gt;
                    joined_results.append({**sale, **product_info}) # **은 언패킹연산자 {**A,**B} 는 A,B딕셔너리를 합쳐줌.&lt;br /&gt;
                    break&lt;br /&gt;
    end_time = time.time()&lt;br /&gt;
    return end_time - start_time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터로 조인&lt;br /&gt;
normal_time = hash_join(products_normal, sales)&lt;br /&gt;
&lt;br /&gt;
# 해시 충돌 데이터로 조인&lt;br /&gt;
collision_time = hash_join(products_collision, sales)&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;일반 데이터 조인 시간: {normal_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;해시 충돌 조인 시간: {collision_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;성능 저하 비율: {(collision_time / normal_time):.2f}배 느림&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
일반 데이터 조인 시간: 0.000971 초&lt;br /&gt;
해시 충돌 조인 시간: 0.001568 초&lt;br /&gt;
성능 저하 비율: 1.61배 느림&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 메모리 부족 및 디스크 I/O&lt;br /&gt;
* 원리: 해시 조인은 더 작은 테이블(빌드 테이블)을 메모리에 올려 해시 테이블을 만드는 것이 기본 전제입니다.&lt;br /&gt;
* 문제점: 빌드 테이블이 너무 커서 **메모리(PGA)**에 다 담을 수 없게 되면, Oracle은 해시 테이블을 여러 파티션으로 나누어 일부를 **임시 테이블스페이스(Temporary Tablespace)**에 저장합니다.&lt;br /&gt;
* 결과: 조인을 수행하기 위해 디스크에 기록된 파티션 데이터를 읽고 쓰는 디스크 I/O 작업이 반복적으로 발생합니다. 이는 메모리에서만 작업할 때보다 훨씬 느리므로 성능 저하의 결정적인 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인에 대한 의문점 ====&lt;br /&gt;
* hash join에서  해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다. &lt;br /&gt;
* 또한 조인키로 사용되는 컬럼이 중복이 많을수록 성능이 좋지 않습니다.  &lt;br /&gt;
# 그러면 조인키로 사용되는 &#039;&#039;&#039;컬럼의 값&#039;&#039;&#039; 이 중복이 심하면 성능 않좋아 진다는것인가? &lt;br /&gt;
# 아니면 해시함수를 통하여 생성된 해시값이 동일한경우에 성능이 않좋아 진다는 것인가? &lt;br /&gt;
# 둘다 해당한다는것인가?&lt;br /&gt;
&lt;br /&gt;
* 둘 다 성능에 영향을 미치지만, 서로 다른 이유이며 실무에서는 1번이 훨씬 더 큰 문제입니다.&lt;br /&gt;
&lt;br /&gt;
===== 조인 키 값의 중복 (더 흔한 문제) =====&lt;br /&gt;
* 조인 키 컬럼 자체의 중복이 많으면 성능이 나빠집니다.&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시: 성별 컬럼으로 조인 (값이 &#039;M&#039;, &#039;F&#039; 두 개뿐)&lt;br /&gt;
SELECT /*+ USE_HASH(a b) */ *&lt;br /&gt;
FROM employees a&lt;br /&gt;
JOIN customers b ON a.gender = b.gender&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- `gender = &#039;M&#039;` 인 행들이 모두 &#039;&#039;&#039;같은 해시 버킷&#039;&#039;&#039;에 저장됨&lt;br /&gt;
:- Probe 단계에서 해당 버킷의 수많은 행들을 일일이 비교해야 함&lt;br /&gt;
:- 해시 조인의 장점(O(1) 검색)이 사라짐&lt;br /&gt;
&lt;br /&gt;
===== 해시 충돌 (드문 문제) =====&lt;br /&gt;
* 서로 다른 조인 키 값이 우연히 같은 해시 값을 생성하는 경우입니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 예시 (실제는 아니지만 개념 설명)&lt;br /&gt;
hash(&#039;emp_id_12345&#039;) = 789  # 해시값 789&lt;br /&gt;
hash(&#039;emp_id_99999&#039;) = 789  # 다른 키인데 같은 해시값!&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- 서로 다른 키인데 같은 버킷에 저장됨&lt;br /&gt;
:- 역시 Probe 시 추가 비교 필요&lt;br /&gt;
&lt;br /&gt;
===== 실제 상황 비교 =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 A: 카디널리티 낮음 (중복 많음) - 실제 큰 문제&lt;br /&gt;
employees: 1,000,000명&lt;br /&gt;
성별(gender)컬럼 값: 남자(&#039;M&#039;), 여자(&#039;F&#039;) 두 개만&lt;br /&gt;
→ 각 버킷에 약 500,000개 행 저장&lt;br /&gt;
→ 성능 매우 나쁨&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 B: 카디널리티 높음 (중복 적음) - 좋음&lt;br /&gt;
employees: 1,000,000명  &lt;br /&gt;
employee_id: 1,000,000개 유니크 값&lt;br /&gt;
→ 각 버킷에 평균 1~2개 행&lt;br /&gt;
→ 해시 충돌 있어도 영향 미미&lt;br /&gt;
→ 성능 좋음&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 결론&lt;br /&gt;
&lt;br /&gt;
:- 조인 키 중복 문제 : 실무에서 흔하고 심각한 성능 저하 원인&lt;br /&gt;
:- 해시 충돌 문제 : Oracle의 해시 함수가 잘 설계되어 있어 드물게 발생&lt;br /&gt;
:- **실무 팁**: 조인 키는 **카디널리티가 높은 컬럼**(예: ID, 주문번호)을 사용해야 함&lt;br /&gt;
&lt;br /&gt;
성별, 부서코드(10개 정도) 같은 낮은 카디널리티 컬럼으로 Hash Join하면 성능이 매우 나빠집니다!&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
* 해시 조인이 효율적으로 작동하는 것은 해시 버킷이 잘 분산되어 있고, 해시 테이블이 메모리에서 모두 처리될 때입니다. 만약 데이터가 한쪽으로 치우쳐 해시 충돌이 심하거나, 메모리 부족으로 디스크 I/O가 발생하면 해시 조인의 장점이 사라져 성능이 느려지게 됩니다.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 장/단점 ===&lt;br /&gt;
==== 장점 ====&lt;br /&gt;
# 대용량 테이블간 &amp;quot;=&amp;quot; 조건의 조인에 적합(정렬 불필요,입력 순서 무관)&lt;br /&gt;
# 빠른 접근 가능(버킷을 이용한 빠른 처리)&lt;br /&gt;
# 병렬처리에 적합하면 효율성이 좋음&lt;br /&gt;
&lt;br /&gt;
==== 단점 ==== &lt;br /&gt;
# &amp;quot;=&amp;quot; 조건에만 적합, 범위 조건(between 이나 like) 조인은 사용할수 없음.&lt;br /&gt;
# 메모리에 의존적임(빌드테이블이 메모리내에 들어가지 못하면 템프 i/o가 발생)&lt;br /&gt;
# 조인키값이 한쪽에 치우치거나(skew),중복도가 높으면 특정 버킷에 연결된 버킷이 길어져 탐색하는 시간이 증가함(CPU사용량 급증, 충돌증가)&lt;br /&gt;
# 버킷수가 데이터 대비 부족하면 충돌 발생과 체인 길이가 늘어 탐색 비용이 증가함  &lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1567</id>
		<title>HASH 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1567"/>
		<updated>2025-10-21T01:32:11Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* HASH 조인에 대한 질문 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해시 조인(Hash Join) ==&lt;br /&gt;
{{틀:서브&lt;br /&gt;
|제목=&#039;&#039;&#039;&amp;lt;big&amp;gt;해시조인시 사용되는 해시(hash)함수는 입력된 데이터를 &#039;잘게 다져서&#039; 고유하고 일정한 크기의 식별자로 만들어 냄.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
* 해시의 주요특징&lt;br /&gt;
** 단방향성 : 해시값을 원래의 값으로 되돌릴수 없다.&lt;br /&gt;
** 고유성 : 입력값이 바뀌면 해시값은 완전히 달라진다.&lt;br /&gt;
** 고정된길이 : 입력값의 크기와 상관없이 항상 동일한 길이의 출력값을 가진다.&lt;br /&gt;
|내용=* hash의 영문의 뜻 : &#039;&#039;&#039;잘게 썰다, 다지다의 의미(요리에서 재료를 잘게 썰어서 섞을때 쓰이는말, 예로 맥도날도 &#039;해시브라운(감자를 다져서 튀긴요리)&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
# 해시 조인(Hash Join)은 두 개의 테이블을 조인할 때, 더 작은 테이블을 메모리에 올리고 해시 테이블을 만들어 효율적으로 조인하는 방식&lt;br /&gt;
# 대용량 테이블을 조인할 때 주로 사용되며, 조인 조건에 인덱스가 없는 경우에도 빠르게 작동&lt;br /&gt;
&lt;br /&gt;
=== 해시 조인의 원리 ===&lt;br /&gt;
* 해시 조인은 크게 두 단계로 나뉩니다.&lt;br /&gt;
# 빌드 단계 (Build Phase):&lt;br /&gt;
## 옵티마이저는 두 테이블 중 더 작은 테이블(빌드 테이블)을 선택&lt;br /&gt;
## 이 테이블의 조인 키(컬럼)를 사용하여 PGA 메모리 영역에 해시 테이블을 생성. 각 조인 키 값은 해시 함수를 통하여 해시값으로 변환되어 해시 테이블의 특정 위치에 저장.&lt;br /&gt;
## 해시테이블 저장시 변환된 해시값들은 해시버킷의 갯수 만큼 쪼개서 할당되고 &amp;quot;버킷&amp;quot;에 행을 연결리스트(체인) 형태로 저장됨.&lt;br /&gt;
## 각 버킷은 &amp;quot;해당 해시 범위에 속하는 행들의 포인터(체인 헤드)&amp;quot; 역활을 하며 , 동일 해시값 이나 충돌이 발생하면 같은 버킷내에서 체인으로 연결됨.(많이 연결될수록 성능 저하,이구간이 성능저하 구간) &lt;br /&gt;
# 프로브 단계 (Probe Phase):&lt;br /&gt;
## 더 큰 테이블(프로브 테이블)을 순차적으로 읽어옮&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 테이블에서 일치하는 행을 찾음.(대상 버킷을 찾고 해당 버킷의 체인을 스캔하여 조인키를 비교)&lt;br /&gt;
## 일치하는 행을 찾으면, 두 테이블의 행을 결합하여 결과를 반환.&lt;br /&gt;
&lt;br /&gt;
* 만약 빌드 테이블이 PGA 메모리에 다 들어가지 않을 정도로 크다면, 테이블을 여러 개의 파티션으로 나누어 디스크에 임시로 저장한 뒤(성능 저하 구간), 각 파티션별로 빌드와 프로브 단계를 반복하는 &#039;그레이스풀 해시 조인(Graceful Hash Join)&#039;이 수행(성능저하 구간)&lt;br /&gt;
* 런타임 파티셔닝을 수행해 부분(배치/파티션) 단위로 템프에 내린 후(DISK I/O발생,성능저하) 다시 합쳐서 1-pass/멀티패스 방식으로 진행함.&lt;br /&gt;
&lt;br /&gt;
=== 파이썬 예제 ===&lt;br /&gt;
* 두 개의 CSV 파일 sales.csv와 products.csv가 있다고 가정하고, 이를 파이썬 딕셔너리로 해시 조인을 구현하는 예제.&lt;br /&gt;
1. 데이터 준비 (sales.csv, products.csv 파일)&lt;br /&gt;
&lt;br /&gt;
1) 대량 테이블 - 판매(sales.csv 파일)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sale_id,product_id,amount&lt;br /&gt;
1,101,100&lt;br /&gt;
2,102,150&lt;br /&gt;
3,101,200&lt;br /&gt;
4,103,50&lt;br /&gt;
....&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2) 제품 (products.csv)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
product_id,product_name&lt;br /&gt;
101,Laptop&lt;br /&gt;
102,Mouse&lt;br /&gt;
103,Keyboard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 해시 조인 구현 코드&lt;br /&gt;
&lt;br /&gt;
소량인 products 테이블을 빌드 테이블로, sales 테이블을 프로브 테이블로 사용하여 조인을 수행.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import csv&lt;br /&gt;
&lt;br /&gt;
# 1. 빌드 테이블: products.csv를 읽어 메모리 해시 테이블(딕셔너리) 생성&lt;br /&gt;
# &#039;product_id&#039;를 키로, &#039;product_name&#039;을 값으로 저장&lt;br /&gt;
products_hash_table = {}&lt;br /&gt;
with open(&#039;products.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # 해시 테이블 생성 (딕셔너리)&lt;br /&gt;
        products_hash_table[row[&#039;product_id&#039;]] = row[&#039;product_name&#039;]&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;--- 빌드 단계 (해시 테이블) ---&amp;quot;)&lt;br /&gt;
print(products_hash_table)&lt;br /&gt;
# 출력: {&#039;101&#039;: &#039;Laptop&#039;, &#039;102&#039;: &#039;Mouse&#039;, &#039;103&#039;: &#039;Keyboard&#039;}&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;\n--- 프로브 단계 (조인 결과) ---&amp;quot;)&lt;br /&gt;
# 2. 프로브 테이블: sales.csv를 읽어 해시 테이블에서 찾기&lt;br /&gt;
with open(&#039;sales.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # sale_id 3의 &#039;product_id&#039;인 &#039;101&#039;을 해시 테이블에서 찾음&lt;br /&gt;
        product_id = row[&#039;product_id&#039;]&lt;br /&gt;
        if product_id in products_hash_table:&lt;br /&gt;
            # 해시 테이블에서 &#039;product_id&#039;를 키로 검색하여 &#039;product_name&#039;을 가져옴&lt;br /&gt;
            product_name = products_hash_table[product_id]&lt;br /&gt;
            print(f&amp;quot;Sale ID: {row[&#039;sale_id&#039;]}, Product Name: {product_name}, Amount: {row[&#039;amount&#039;]}&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 예상 출력:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Sale ID: 1, Product Name: Laptop, Amount: 100&lt;br /&gt;
# Sale ID: 2, Product Name: Mouse, Amount: 150&lt;br /&gt;
# Sale ID: 3, Product Name: Laptop, Amount: 200&lt;br /&gt;
# Sale ID: 4, Product Name: Keyboard, Amount: 50&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# products_hash_table이라는 딕셔너리가 바로 해시 테이블 역할을 합니다. &lt;br /&gt;
# sales 테이블의 각 행을 읽을 때마다 product_id를 딕셔너리(해시 테이블)에서 빠르게 찾아 product_name을 가져와 조인된 결과를 출력하는 방식&lt;br /&gt;
# 이를 통해 전체 products 테이블을 다시 스캔할 필요 없이 O(1)에 가까운 시간 복잡도로 조인 조건을 만족하는 데이터를 찾을 수 있음.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 성능 향상 원리 ===&lt;br /&gt;
==== Hash 버킷의 역할과 기능 ====&lt;br /&gt;
::::* 해시 버킷은 두 테이블을 효율적으로 조인하기 위한 핵심적인 논리적 저장 공간. &lt;br /&gt;
::::* 이는 조인 키(Join Key)를 기반으로 데이터를 빠르게 찾고 저장하는 데 사용.&lt;br /&gt;
&lt;br /&gt;
===== 역할: 데이터의 논리적 분류 및 저장소 =====&lt;br /&gt;
:::::* 해시 버킷은 조인 키 값을 해시 함수에 넣어 얻은 해시 값이 같은 데이터들을 모아두는 공간.&lt;br /&gt;
:::::* 이는 거대한 데이터를 조인 키를 기준으로 더 작은 그룹으로 나누어 관리하는 역할을 합니다.&lt;br /&gt;
:::::* 이를 통해 빌드 테이블의 모든 데이터를 일일이 탐색하지 않고도, 특정 조인 키에 해당하는 데이터가 어느 버킷에 있는지 바로 알 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
===== 기능: Build 단계와 Probe 단계에서의 활용 =====&lt;br /&gt;
# Build 단계:&lt;br /&gt;
## Oracle은 먼저 두 테이블 중 더 작은 테이블을 선택하여 빌드 테이블로 지정합니다.&lt;br /&gt;
## 빌드 테이블의 각 행을 읽어 조인 키에 해시 함수를 적용하여 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값에 해당하는 버킷에 해당 행의 데이터를 저장합니다. 만약 같은 버킷에 이미 다른 데이터가 있다면, 이를 연결 리스트(Linked List) 형태로 연결하여 **해시 충돌(Hash Collision)**을 처리합니다.&lt;br /&gt;
# Probe 단계:&lt;br /&gt;
## 이제 더 큰 테이블인 프로브 테이블을 순차적으로 읽습니다.&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 버킷 위치를 즉시 찾아갑니다.&lt;br /&gt;
## 해당 버킷 안의 연결 리스트를 탐색하여 조인 키가 일치하는 행을 찾습니다.&lt;br /&gt;
## 일치하는 행이 발견되면 두 테이블의 데이터를 결합하여 최종 결과를 생성합니다.&lt;br /&gt;
&lt;br /&gt;
* 이러한 해시 버킷의 활용 덕분에, 해시 조인은 테이블의 크기와 상관없이 조인 키가 같은 데이터들을 O(1)에 가까운 시간 복잡도로 찾아낼 수 있어 대용량 데이터 조인에 매우 효과적입니다.&lt;br /&gt;
&lt;br /&gt;
==== 해시 버킷 개수의 결정 ====&lt;br /&gt;
* 해시 조인의 최대 해시 버킷 개수는 사전 설정된 고정값이 아니라, 조인하려는 테이블의 크기, 옵티마이저의 예측, 그리고 PGA_AGGREGATE_TARGET 파라미터에 의해 동적으로 결정&lt;br /&gt;
&lt;br /&gt;
# 빌드 테이블(Build Table)의 크기: 옵티마이저가 예측한 더 작은 테이블(빌드 테이블)의 크기에 따라 필요한 해시 버킷의 수가 결정됨&lt;br /&gt;
# 할당된 PGA 메모리: PGA_AGGREGATE_TARGET에 의해 세션에 할당된 PGA 메모리 내에서 해시 테이블이 생성됨.&lt;br /&gt;
# 메모리 부족 시: 만약 할당된 PGA 메모리만으로 해시 테이블을 모두 생성할 수 없다면, Oracle은 일부 해시 버킷을 디스크의 임시 테이블스페이스(temp tablespace)로 분할하여 저장합니다. &lt;br /&gt;
## 이를 &#039;해시 스필(Hash Spill)&#039;이라고 부르며, 이 경우 I/O가 발생하여 성능이 저하됨.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인이 느려지는 원인 ====&lt;br /&gt;
* 해시 조인 성능이 느려지는 주요 원인 중 하나는 해시 버킷 때문입니다.&lt;br /&gt;
* 정확히 말하면, 해시 충돌이 심하게 발생하거나 해시 테이블이 메모리에 맞지 않아 디스크 I/O가 발생하는 경우입니다.&lt;br /&gt;
* 해시 버킷 자체는 효율성을 위한 도구지만, 비정상적인 상황에서는 오히려 성능 저하의 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
1. 해시 충돌 (Hash Collisions)&lt;br /&gt;
* 원리: 해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다.&lt;br /&gt;
* 문제점: 해시 충돌이 발생하면, 하나의 버킷에 여러 행이 연결 리스트 형태로 쌓이게 됩니다. 이 버킷을 탐색할 때, 원하는 데이터를 찾기 위해 연결된 모든 데이터를 순차적으로 비교해야 하므로, 탐색 시간이 O(1)에서 O(N)으로 늘어납니다.&lt;br /&gt;
* 결과: 조인 키 데이터 분포가 불균형하거나(데이터 편중), 해시 함수가 비효율적이면 해시 충돌이 자주 발생하여 성능이 크게 저하됩니다.&lt;br /&gt;
* (결론) 해시조인은 조인키로 사용되는 컬럼이 중복이 많을수록  성능이 좋지 않음&lt;br /&gt;
&lt;br /&gt;
* 파이썬으로 해시 충돌 구현 예제&lt;br /&gt;
** 고의로 해시 충돌을 유발하는 데이터를 사용하여 해시 조인의 성능 차이를 보여줍니다. &lt;br /&gt;
** products_collision 리스트의 모든 product_id는 해시 함수를 거치면 같은 값을 반환하도록 설계되었습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터 (해시 충돌이 거의 없음)&lt;br /&gt;
products_normal = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 102, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 103, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 의도적으로 해시 충돌을 유발하는 데이터&lt;br /&gt;
# 101, 201, 301 모두 100으로 나눈 나머지가 1이 되도록 설계&lt;br /&gt;
products_collision = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 201, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 301, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인 대상 데이터 (각 product_id에 대한 sales 데이터)&lt;br /&gt;
sales = [{&#039;sale_id&#039;: i, &#039;product_id&#039;: 101, &#039;amount&#039;: 100} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+1000, &#039;product_id&#039;: 201, &#039;amount&#039;: 150} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+2000, &#039;product_id&#039;: 301, &#039;amount&#039;: 200} for i in range(1000)]&lt;br /&gt;
&lt;br /&gt;
# 사용자 정의 해시 함수 (단순화를 위해)&lt;br /&gt;
def custom_hash(key):&lt;br /&gt;
    return key % 100&lt;br /&gt;
&lt;br /&gt;
def hash_join(build_table, probe_table):&lt;br /&gt;
    # 빌드 단계: 해시 버킷(딕셔너리) 생성&lt;br /&gt;
    # 파이썬 딕셔너리의 키 충돌을 구현하기 위해, 딕셔너리의 값을 리스트로 저장&lt;br /&gt;
    hash_buckets = {}&lt;br /&gt;
    for item in build_table:&lt;br /&gt;
        key = item[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        if bucket_index not in hash_buckets:&lt;br /&gt;
            hash_buckets[bucket_index] = []&lt;br /&gt;
        hash_buckets[bucket_index].append(item)&lt;br /&gt;
&lt;br /&gt;
    # 프로브 단계: 조인&lt;br /&gt;
    start_time = time.time()&lt;br /&gt;
    joined_results = []&lt;br /&gt;
    for sale in probe_table:&lt;br /&gt;
        key = sale[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        &lt;br /&gt;
        # 해시 버킷에서 일치하는 데이터를 찾기 (충돌 발생 시 순차 탐색)&lt;br /&gt;
        if bucket_index in hash_buckets:&lt;br /&gt;
            for product_info in hash_buckets[bucket_index]:&lt;br /&gt;
                if product_info[&#039;product_id&#039;] == key:&lt;br /&gt;
                    joined_results.append({**sale, **product_info}) # **은 언패킹연산자 {**A,**B} 는 A,B딕셔너리를 합쳐줌.&lt;br /&gt;
                    break&lt;br /&gt;
    end_time = time.time()&lt;br /&gt;
    return end_time - start_time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터로 조인&lt;br /&gt;
normal_time = hash_join(products_normal, sales)&lt;br /&gt;
&lt;br /&gt;
# 해시 충돌 데이터로 조인&lt;br /&gt;
collision_time = hash_join(products_collision, sales)&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;일반 데이터 조인 시간: {normal_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;해시 충돌 조인 시간: {collision_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;성능 저하 비율: {(collision_time / normal_time):.2f}배 느림&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
일반 데이터 조인 시간: 0.000971 초&lt;br /&gt;
해시 충돌 조인 시간: 0.001568 초&lt;br /&gt;
성능 저하 비율: 1.61배 느림&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 메모리 부족 및 디스크 I/O&lt;br /&gt;
* 원리: 해시 조인은 더 작은 테이블(빌드 테이블)을 메모리에 올려 해시 테이블을 만드는 것이 기본 전제입니다.&lt;br /&gt;
* 문제점: 빌드 테이블이 너무 커서 **메모리(PGA)**에 다 담을 수 없게 되면, Oracle은 해시 테이블을 여러 파티션으로 나누어 일부를 **임시 테이블스페이스(Temporary Tablespace)**에 저장합니다.&lt;br /&gt;
* 결과: 조인을 수행하기 위해 디스크에 기록된 파티션 데이터를 읽고 쓰는 디스크 I/O 작업이 반복적으로 발생합니다. 이는 메모리에서만 작업할 때보다 훨씬 느리므로 성능 저하의 결정적인 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인에 대한 의문점 ====&lt;br /&gt;
* hash join에서  해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다. &lt;br /&gt;
* 또한 조인키로 사용되는 컬럼이 중복이 많을수록 성능이 좋지 않습니다.  &lt;br /&gt;
# 그러면 조인키로 사용되는 &#039;&#039;&#039;컬럼의 값&#039;&#039;&#039; 이 중복이 심하면 성능 않좋아 진다는것인가? &lt;br /&gt;
# 아니면 해시함수를 통하여 생성된 해시값이 동일한경우에 성능이 않좋아 진다는 것인가? &lt;br /&gt;
# 둘다 해당한다는것인가?&lt;br /&gt;
&lt;br /&gt;
* 둘 다 성능에 영향을 미치지만, 서로 다른 이유이며 실무에서는 1번이 훨씬 더 큰 문제입니다.&lt;br /&gt;
&lt;br /&gt;
===== 조인 키 값의 중복 (더 흔한 문제) =====&lt;br /&gt;
* 조인 키 컬럼 자체의 중복이 많으면 성능이 나빠집니다.&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시: 성별 컬럼으로 조인 (값이 &#039;M&#039;, &#039;F&#039; 두 개뿐)&lt;br /&gt;
SELECT /*+ USE_HASH(a b) */ *&lt;br /&gt;
FROM employees a&lt;br /&gt;
JOIN customers b ON a.gender = b.gender&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- `gender = &#039;M&#039;` 인 행들이 모두 &#039;&#039;&#039;같은 해시 버킷&#039;&#039;&#039;에 저장됨&lt;br /&gt;
:- Probe 단계에서 해당 버킷의 수많은 행들을 일일이 비교해야 함&lt;br /&gt;
:- 해시 조인의 장점(O(1) 검색)이 사라짐&lt;br /&gt;
&lt;br /&gt;
===== 해시 충돌 (드문 문제) =====&lt;br /&gt;
* 서로 다른 조인 키 값이 우연히 같은 해시 값을 생성하는 경우입니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 예시 (실제는 아니지만 개념 설명)&lt;br /&gt;
hash(&#039;emp_id_12345&#039;) = 789  # 해시값 789&lt;br /&gt;
hash(&#039;emp_id_99999&#039;) = 789  # 다른 키인데 같은 해시값!&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- 서로 다른 키인데 같은 버킷에 저장됨&lt;br /&gt;
:- 역시 Probe 시 추가 비교 필요&lt;br /&gt;
&lt;br /&gt;
===== 실제 상황 비교 =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 A: 카디널리티 낮음 (중복 많음) - 실제 큰 문제&lt;br /&gt;
employees: 1,000,000명&lt;br /&gt;
성별(gender)컬럼 값: 남자(&#039;M&#039;), 여자(&#039;F&#039;) 두 개만&lt;br /&gt;
→ 각 버킷에 약 500,000개 행 저장&lt;br /&gt;
→ 성능 매우 나쁨&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 B: 카디널리티 높음 (중복 적음) - 좋음&lt;br /&gt;
employees: 1,000,000명  &lt;br /&gt;
employee_id: 1,000,000개 유니크 값&lt;br /&gt;
→ 각 버킷에 평균 1~2개 행&lt;br /&gt;
→ 해시 충돌 있어도 영향 미미&lt;br /&gt;
→ 성능 좋음&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 결론&lt;br /&gt;
&lt;br /&gt;
:- 조인 키 중복 문제 : 실무에서 흔하고 심각한 성능 저하 원인&lt;br /&gt;
:- 해시 충돌 문제 : Oracle의 해시 함수가 잘 설계되어 있어 드물게 발생&lt;br /&gt;
:- **실무 팁**: 조인 키는 **카디널리티가 높은 컬럼**(예: ID, 주문번호)을 사용해야 함&lt;br /&gt;
&lt;br /&gt;
성별, 부서코드(10개 정도) 같은 낮은 카디널리티 컬럼으로 Hash Join하면 성능이 매우 나빠집니다!&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
* 해시 조인이 효율적으로 작동하는 것은 해시 버킷이 잘 분산되어 있고, 해시 테이블이 메모리에서 모두 처리될 때입니다. 만약 데이터가 한쪽으로 치우쳐 해시 충돌이 심하거나, 메모리 부족으로 디스크 I/O가 발생하면 해시 조인의 장점이 사라져 성능이 느려지게 됩니다.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 장/단점 ===&lt;br /&gt;
==== 장점 ====&lt;br /&gt;
# 대용량 테이블간 &amp;quot;=&amp;quot; 조건의 조인에 적합(정렬 불필요,입력 순서 무관)&lt;br /&gt;
# 빠른 접근 가능(버킷을 이용한 빠른 처리)&lt;br /&gt;
# 병렬처리에 적합하면 효율성이 좋음&lt;br /&gt;
&lt;br /&gt;
==== 단점 ==== &lt;br /&gt;
# &amp;quot;=&amp;quot; 조건에만 적합, 범위 조건(between 이나 like) 조인은 사용할수 없음.&lt;br /&gt;
# 메모리에 의존적임(빌드테이블이 메모리내에 들어가지 못하면 템프 i/o가 발생)&lt;br /&gt;
# 조인키값이 한쪽에 치우치거나(skew),중복도가 높으면 특정 버킷에 연결된 버킷이 길어져 탐색하는 시간이 증가함(CPU사용량 급증, 충돌증가)&lt;br /&gt;
# 버킷수가 데이터 대비 부족하면 충돌 발생과 체인 길이가 늘어 탐색 비용이 증가함  &lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1566</id>
		<title>HASH 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1566"/>
		<updated>2025-10-21T01:31:24Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해시 조인(Hash Join) ==&lt;br /&gt;
{{틀:서브&lt;br /&gt;
|제목=&#039;&#039;&#039;&amp;lt;big&amp;gt;해시조인시 사용되는 해시(hash)함수는 입력된 데이터를 &#039;잘게 다져서&#039; 고유하고 일정한 크기의 식별자로 만들어 냄.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
* 해시의 주요특징&lt;br /&gt;
** 단방향성 : 해시값을 원래의 값으로 되돌릴수 없다.&lt;br /&gt;
** 고유성 : 입력값이 바뀌면 해시값은 완전히 달라진다.&lt;br /&gt;
** 고정된길이 : 입력값의 크기와 상관없이 항상 동일한 길이의 출력값을 가진다.&lt;br /&gt;
|내용=* hash의 영문의 뜻 : &#039;&#039;&#039;잘게 썰다, 다지다의 의미(요리에서 재료를 잘게 썰어서 섞을때 쓰이는말, 예로 맥도날도 &#039;해시브라운(감자를 다져서 튀긴요리)&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
# 해시 조인(Hash Join)은 두 개의 테이블을 조인할 때, 더 작은 테이블을 메모리에 올리고 해시 테이블을 만들어 효율적으로 조인하는 방식&lt;br /&gt;
# 대용량 테이블을 조인할 때 주로 사용되며, 조인 조건에 인덱스가 없는 경우에도 빠르게 작동&lt;br /&gt;
&lt;br /&gt;
=== 해시 조인의 원리 ===&lt;br /&gt;
* 해시 조인은 크게 두 단계로 나뉩니다.&lt;br /&gt;
# 빌드 단계 (Build Phase):&lt;br /&gt;
## 옵티마이저는 두 테이블 중 더 작은 테이블(빌드 테이블)을 선택&lt;br /&gt;
## 이 테이블의 조인 키(컬럼)를 사용하여 PGA 메모리 영역에 해시 테이블을 생성. 각 조인 키 값은 해시 함수를 통하여 해시값으로 변환되어 해시 테이블의 특정 위치에 저장.&lt;br /&gt;
## 해시테이블 저장시 변환된 해시값들은 해시버킷의 갯수 만큼 쪼개서 할당되고 &amp;quot;버킷&amp;quot;에 행을 연결리스트(체인) 형태로 저장됨.&lt;br /&gt;
## 각 버킷은 &amp;quot;해당 해시 범위에 속하는 행들의 포인터(체인 헤드)&amp;quot; 역활을 하며 , 동일 해시값 이나 충돌이 발생하면 같은 버킷내에서 체인으로 연결됨.(많이 연결될수록 성능 저하,이구간이 성능저하 구간) &lt;br /&gt;
# 프로브 단계 (Probe Phase):&lt;br /&gt;
## 더 큰 테이블(프로브 테이블)을 순차적으로 읽어옮&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 테이블에서 일치하는 행을 찾음.(대상 버킷을 찾고 해당 버킷의 체인을 스캔하여 조인키를 비교)&lt;br /&gt;
## 일치하는 행을 찾으면, 두 테이블의 행을 결합하여 결과를 반환.&lt;br /&gt;
&lt;br /&gt;
* 만약 빌드 테이블이 PGA 메모리에 다 들어가지 않을 정도로 크다면, 테이블을 여러 개의 파티션으로 나누어 디스크에 임시로 저장한 뒤(성능 저하 구간), 각 파티션별로 빌드와 프로브 단계를 반복하는 &#039;그레이스풀 해시 조인(Graceful Hash Join)&#039;이 수행(성능저하 구간)&lt;br /&gt;
* 런타임 파티셔닝을 수행해 부분(배치/파티션) 단위로 템프에 내린 후(DISK I/O발생,성능저하) 다시 합쳐서 1-pass/멀티패스 방식으로 진행함.&lt;br /&gt;
&lt;br /&gt;
=== 파이썬 예제 ===&lt;br /&gt;
* 두 개의 CSV 파일 sales.csv와 products.csv가 있다고 가정하고, 이를 파이썬 딕셔너리로 해시 조인을 구현하는 예제.&lt;br /&gt;
1. 데이터 준비 (sales.csv, products.csv 파일)&lt;br /&gt;
&lt;br /&gt;
1) 대량 테이블 - 판매(sales.csv 파일)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sale_id,product_id,amount&lt;br /&gt;
1,101,100&lt;br /&gt;
2,102,150&lt;br /&gt;
3,101,200&lt;br /&gt;
4,103,50&lt;br /&gt;
....&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2) 제품 (products.csv)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
product_id,product_name&lt;br /&gt;
101,Laptop&lt;br /&gt;
102,Mouse&lt;br /&gt;
103,Keyboard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 해시 조인 구현 코드&lt;br /&gt;
&lt;br /&gt;
소량인 products 테이블을 빌드 테이블로, sales 테이블을 프로브 테이블로 사용하여 조인을 수행.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import csv&lt;br /&gt;
&lt;br /&gt;
# 1. 빌드 테이블: products.csv를 읽어 메모리 해시 테이블(딕셔너리) 생성&lt;br /&gt;
# &#039;product_id&#039;를 키로, &#039;product_name&#039;을 값으로 저장&lt;br /&gt;
products_hash_table = {}&lt;br /&gt;
with open(&#039;products.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # 해시 테이블 생성 (딕셔너리)&lt;br /&gt;
        products_hash_table[row[&#039;product_id&#039;]] = row[&#039;product_name&#039;]&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;--- 빌드 단계 (해시 테이블) ---&amp;quot;)&lt;br /&gt;
print(products_hash_table)&lt;br /&gt;
# 출력: {&#039;101&#039;: &#039;Laptop&#039;, &#039;102&#039;: &#039;Mouse&#039;, &#039;103&#039;: &#039;Keyboard&#039;}&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;\n--- 프로브 단계 (조인 결과) ---&amp;quot;)&lt;br /&gt;
# 2. 프로브 테이블: sales.csv를 읽어 해시 테이블에서 찾기&lt;br /&gt;
with open(&#039;sales.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # sale_id 3의 &#039;product_id&#039;인 &#039;101&#039;을 해시 테이블에서 찾음&lt;br /&gt;
        product_id = row[&#039;product_id&#039;]&lt;br /&gt;
        if product_id in products_hash_table:&lt;br /&gt;
            # 해시 테이블에서 &#039;product_id&#039;를 키로 검색하여 &#039;product_name&#039;을 가져옴&lt;br /&gt;
            product_name = products_hash_table[product_id]&lt;br /&gt;
            print(f&amp;quot;Sale ID: {row[&#039;sale_id&#039;]}, Product Name: {product_name}, Amount: {row[&#039;amount&#039;]}&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 예상 출력:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Sale ID: 1, Product Name: Laptop, Amount: 100&lt;br /&gt;
# Sale ID: 2, Product Name: Mouse, Amount: 150&lt;br /&gt;
# Sale ID: 3, Product Name: Laptop, Amount: 200&lt;br /&gt;
# Sale ID: 4, Product Name: Keyboard, Amount: 50&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# products_hash_table이라는 딕셔너리가 바로 해시 테이블 역할을 합니다. &lt;br /&gt;
# sales 테이블의 각 행을 읽을 때마다 product_id를 딕셔너리(해시 테이블)에서 빠르게 찾아 product_name을 가져와 조인된 결과를 출력하는 방식&lt;br /&gt;
# 이를 통해 전체 products 테이블을 다시 스캔할 필요 없이 O(1)에 가까운 시간 복잡도로 조인 조건을 만족하는 데이터를 찾을 수 있음.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 성능 향상 원리 ===&lt;br /&gt;
==== Hash 버킷의 역할과 기능 ====&lt;br /&gt;
::::* 해시 버킷은 두 테이블을 효율적으로 조인하기 위한 핵심적인 논리적 저장 공간. &lt;br /&gt;
::::* 이는 조인 키(Join Key)를 기반으로 데이터를 빠르게 찾고 저장하는 데 사용.&lt;br /&gt;
&lt;br /&gt;
===== 역할: 데이터의 논리적 분류 및 저장소 =====&lt;br /&gt;
:::::* 해시 버킷은 조인 키 값을 해시 함수에 넣어 얻은 해시 값이 같은 데이터들을 모아두는 공간.&lt;br /&gt;
:::::* 이는 거대한 데이터를 조인 키를 기준으로 더 작은 그룹으로 나누어 관리하는 역할을 합니다.&lt;br /&gt;
:::::* 이를 통해 빌드 테이블의 모든 데이터를 일일이 탐색하지 않고도, 특정 조인 키에 해당하는 데이터가 어느 버킷에 있는지 바로 알 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
===== 기능: Build 단계와 Probe 단계에서의 활용 =====&lt;br /&gt;
# Build 단계:&lt;br /&gt;
## Oracle은 먼저 두 테이블 중 더 작은 테이블을 선택하여 빌드 테이블로 지정합니다.&lt;br /&gt;
## 빌드 테이블의 각 행을 읽어 조인 키에 해시 함수를 적용하여 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값에 해당하는 버킷에 해당 행의 데이터를 저장합니다. 만약 같은 버킷에 이미 다른 데이터가 있다면, 이를 연결 리스트(Linked List) 형태로 연결하여 **해시 충돌(Hash Collision)**을 처리합니다.&lt;br /&gt;
# Probe 단계:&lt;br /&gt;
## 이제 더 큰 테이블인 프로브 테이블을 순차적으로 읽습니다.&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 버킷 위치를 즉시 찾아갑니다.&lt;br /&gt;
## 해당 버킷 안의 연결 리스트를 탐색하여 조인 키가 일치하는 행을 찾습니다.&lt;br /&gt;
## 일치하는 행이 발견되면 두 테이블의 데이터를 결합하여 최종 결과를 생성합니다.&lt;br /&gt;
&lt;br /&gt;
* 이러한 해시 버킷의 활용 덕분에, 해시 조인은 테이블의 크기와 상관없이 조인 키가 같은 데이터들을 O(1)에 가까운 시간 복잡도로 찾아낼 수 있어 대용량 데이터 조인에 매우 효과적입니다.&lt;br /&gt;
&lt;br /&gt;
==== 해시 버킷 개수의 결정 ====&lt;br /&gt;
* 해시 조인의 최대 해시 버킷 개수는 사전 설정된 고정값이 아니라, 조인하려는 테이블의 크기, 옵티마이저의 예측, 그리고 PGA_AGGREGATE_TARGET 파라미터에 의해 동적으로 결정&lt;br /&gt;
&lt;br /&gt;
# 빌드 테이블(Build Table)의 크기: 옵티마이저가 예측한 더 작은 테이블(빌드 테이블)의 크기에 따라 필요한 해시 버킷의 수가 결정됨&lt;br /&gt;
# 할당된 PGA 메모리: PGA_AGGREGATE_TARGET에 의해 세션에 할당된 PGA 메모리 내에서 해시 테이블이 생성됨.&lt;br /&gt;
# 메모리 부족 시: 만약 할당된 PGA 메모리만으로 해시 테이블을 모두 생성할 수 없다면, Oracle은 일부 해시 버킷을 디스크의 임시 테이블스페이스(temp tablespace)로 분할하여 저장합니다. &lt;br /&gt;
## 이를 &#039;해시 스필(Hash Spill)&#039;이라고 부르며, 이 경우 I/O가 발생하여 성능이 저하됨.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인이 느려지는 원인 ====&lt;br /&gt;
* 해시 조인 성능이 느려지는 주요 원인 중 하나는 해시 버킷 때문입니다.&lt;br /&gt;
* 정확히 말하면, 해시 충돌이 심하게 발생하거나 해시 테이블이 메모리에 맞지 않아 디스크 I/O가 발생하는 경우입니다.&lt;br /&gt;
* 해시 버킷 자체는 효율성을 위한 도구지만, 비정상적인 상황에서는 오히려 성능 저하의 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
1. 해시 충돌 (Hash Collisions)&lt;br /&gt;
* 원리: 해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다.&lt;br /&gt;
* 문제점: 해시 충돌이 발생하면, 하나의 버킷에 여러 행이 연결 리스트 형태로 쌓이게 됩니다. 이 버킷을 탐색할 때, 원하는 데이터를 찾기 위해 연결된 모든 데이터를 순차적으로 비교해야 하므로, 탐색 시간이 O(1)에서 O(N)으로 늘어납니다.&lt;br /&gt;
* 결과: 조인 키 데이터 분포가 불균형하거나(데이터 편중), 해시 함수가 비효율적이면 해시 충돌이 자주 발생하여 성능이 크게 저하됩니다.&lt;br /&gt;
* (결론) 해시조인은 조인키로 사용되는 컬럼이 중복이 많을수록  성능이 좋지 않음&lt;br /&gt;
&lt;br /&gt;
* 파이썬으로 해시 충돌 구현 예제&lt;br /&gt;
** 고의로 해시 충돌을 유발하는 데이터를 사용하여 해시 조인의 성능 차이를 보여줍니다. &lt;br /&gt;
** products_collision 리스트의 모든 product_id는 해시 함수를 거치면 같은 값을 반환하도록 설계되었습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터 (해시 충돌이 거의 없음)&lt;br /&gt;
products_normal = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 102, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 103, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 의도적으로 해시 충돌을 유발하는 데이터&lt;br /&gt;
# 101, 201, 301 모두 100으로 나눈 나머지가 1이 되도록 설계&lt;br /&gt;
products_collision = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 201, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 301, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인 대상 데이터 (각 product_id에 대한 sales 데이터)&lt;br /&gt;
sales = [{&#039;sale_id&#039;: i, &#039;product_id&#039;: 101, &#039;amount&#039;: 100} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+1000, &#039;product_id&#039;: 201, &#039;amount&#039;: 150} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+2000, &#039;product_id&#039;: 301, &#039;amount&#039;: 200} for i in range(1000)]&lt;br /&gt;
&lt;br /&gt;
# 사용자 정의 해시 함수 (단순화를 위해)&lt;br /&gt;
def custom_hash(key):&lt;br /&gt;
    return key % 100&lt;br /&gt;
&lt;br /&gt;
def hash_join(build_table, probe_table):&lt;br /&gt;
    # 빌드 단계: 해시 버킷(딕셔너리) 생성&lt;br /&gt;
    # 파이썬 딕셔너리의 키 충돌을 구현하기 위해, 딕셔너리의 값을 리스트로 저장&lt;br /&gt;
    hash_buckets = {}&lt;br /&gt;
    for item in build_table:&lt;br /&gt;
        key = item[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        if bucket_index not in hash_buckets:&lt;br /&gt;
            hash_buckets[bucket_index] = []&lt;br /&gt;
        hash_buckets[bucket_index].append(item)&lt;br /&gt;
&lt;br /&gt;
    # 프로브 단계: 조인&lt;br /&gt;
    start_time = time.time()&lt;br /&gt;
    joined_results = []&lt;br /&gt;
    for sale in probe_table:&lt;br /&gt;
        key = sale[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        &lt;br /&gt;
        # 해시 버킷에서 일치하는 데이터를 찾기 (충돌 발생 시 순차 탐색)&lt;br /&gt;
        if bucket_index in hash_buckets:&lt;br /&gt;
            for product_info in hash_buckets[bucket_index]:&lt;br /&gt;
                if product_info[&#039;product_id&#039;] == key:&lt;br /&gt;
                    joined_results.append({**sale, **product_info}) # **은 언패킹연산자 {**A,**B} 는 A,B딕셔너리를 합쳐줌.&lt;br /&gt;
                    break&lt;br /&gt;
    end_time = time.time()&lt;br /&gt;
    return end_time - start_time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터로 조인&lt;br /&gt;
normal_time = hash_join(products_normal, sales)&lt;br /&gt;
&lt;br /&gt;
# 해시 충돌 데이터로 조인&lt;br /&gt;
collision_time = hash_join(products_collision, sales)&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;일반 데이터 조인 시간: {normal_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;해시 충돌 조인 시간: {collision_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;성능 저하 비율: {(collision_time / normal_time):.2f}배 느림&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
일반 데이터 조인 시간: 0.000971 초&lt;br /&gt;
해시 충돌 조인 시간: 0.001568 초&lt;br /&gt;
성능 저하 비율: 1.61배 느림&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 메모리 부족 및 디스크 I/O&lt;br /&gt;
* 원리: 해시 조인은 더 작은 테이블(빌드 테이블)을 메모리에 올려 해시 테이블을 만드는 것이 기본 전제입니다.&lt;br /&gt;
* 문제점: 빌드 테이블이 너무 커서 **메모리(PGA)**에 다 담을 수 없게 되면, Oracle은 해시 테이블을 여러 파티션으로 나누어 일부를 **임시 테이블스페이스(Temporary Tablespace)**에 저장합니다.&lt;br /&gt;
* 결과: 조인을 수행하기 위해 디스크에 기록된 파티션 데이터를 읽고 쓰는 디스크 I/O 작업이 반복적으로 발생합니다. 이는 메모리에서만 작업할 때보다 훨씬 느리므로 성능 저하의 결정적인 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인에 대한 질문 ====&lt;br /&gt;
* hash join에서  해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다. &lt;br /&gt;
* 또한 조인키로 사용되는 컬럼이 중복이 많을수록 성능이 좋지 않습니다.  &lt;br /&gt;
# 그러면 조인키로 사용되는 &#039;&#039;&#039;컬럼의 값&#039;&#039;&#039; 이 중복이 심하면 성능 않좋아 진다는것인가? &lt;br /&gt;
# 아니면 해시함수를 통하여 생성된 해시값이 동일한경우에 성능이 않좋아 진다는 것인가? &lt;br /&gt;
# 둘다 해당한다는것인가?&lt;br /&gt;
&lt;br /&gt;
* 둘 다 성능에 영향을 미치지만, 서로 다른 이유이며 실무에서는 1번이 훨씬 더 큰 문제입니다.&lt;br /&gt;
&lt;br /&gt;
===== 조인 키 값의 중복 (더 흔한 문제) =====&lt;br /&gt;
* 조인 키 컬럼 자체의 중복이 많으면 성능이 나빠집니다.&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 예시: 성별 컬럼으로 조인 (값이 &#039;M&#039;, &#039;F&#039; 두 개뿐)&lt;br /&gt;
SELECT /*+ USE_HASH(a b) */ *&lt;br /&gt;
FROM employees a&lt;br /&gt;
JOIN customers b ON a.gender = b.gender&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- `gender = &#039;M&#039;` 인 행들이 모두 &#039;&#039;&#039;같은 해시 버킷&#039;&#039;&#039;에 저장됨&lt;br /&gt;
:- Probe 단계에서 해당 버킷의 수많은 행들을 일일이 비교해야 함&lt;br /&gt;
:- 해시 조인의 장점(O(1) 검색)이 사라짐&lt;br /&gt;
&lt;br /&gt;
===== 해시 충돌 (드문 문제) =====&lt;br /&gt;
* 서로 다른 조인 키 값이 우연히 같은 해시 값을 생성하는 경우입니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 예시 (실제는 아니지만 개념 설명)&lt;br /&gt;
hash(&#039;emp_id_12345&#039;) = 789  # 해시값 789&lt;br /&gt;
hash(&#039;emp_id_99999&#039;) = 789  # 다른 키인데 같은 해시값!&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 문제점:&lt;br /&gt;
:- 서로 다른 키인데 같은 버킷에 저장됨&lt;br /&gt;
:- 역시 Probe 시 추가 비교 필요&lt;br /&gt;
&lt;br /&gt;
===== 실제 상황 비교 =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 A: 카디널리티 낮음 (중복 많음) - 실제 큰 문제&lt;br /&gt;
employees: 1,000,000명&lt;br /&gt;
성별(gender)컬럼 값: 남자(&#039;M&#039;), 여자(&#039;F&#039;) 두 개만&lt;br /&gt;
→ 각 버킷에 약 500,000개 행 저장&lt;br /&gt;
→ 성능 매우 나쁨&lt;br /&gt;
&lt;br /&gt;
-- 시나리오 B: 카디널리티 높음 (중복 적음) - 좋음&lt;br /&gt;
employees: 1,000,000명  &lt;br /&gt;
employee_id: 1,000,000개 유니크 값&lt;br /&gt;
→ 각 버킷에 평균 1~2개 행&lt;br /&gt;
→ 해시 충돌 있어도 영향 미미&lt;br /&gt;
→ 성능 좋음&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 결론&lt;br /&gt;
&lt;br /&gt;
:- 조인 키 중복 문제 : 실무에서 흔하고 심각한 성능 저하 원인&lt;br /&gt;
:- 해시 충돌 문제 : Oracle의 해시 함수가 잘 설계되어 있어 드물게 발생&lt;br /&gt;
:- **실무 팁**: 조인 키는 **카디널리티가 높은 컬럼**(예: ID, 주문번호)을 사용해야 함&lt;br /&gt;
&lt;br /&gt;
성별, 부서코드(10개 정도) 같은 낮은 카디널리티 컬럼으로 Hash Join하면 성능이 매우 나빠집니다!&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
* 해시 조인이 효율적으로 작동하는 것은 해시 버킷이 잘 분산되어 있고, 해시 테이블이 메모리에서 모두 처리될 때입니다. 만약 데이터가 한쪽으로 치우쳐 해시 충돌이 심하거나, 메모리 부족으로 디스크 I/O가 발생하면 해시 조인의 장점이 사라져 성능이 느려지게 됩니다.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 장/단점 ===&lt;br /&gt;
==== 장점 ====&lt;br /&gt;
# 대용량 테이블간 &amp;quot;=&amp;quot; 조건의 조인에 적합(정렬 불필요,입력 순서 무관)&lt;br /&gt;
# 빠른 접근 가능(버킷을 이용한 빠른 처리)&lt;br /&gt;
# 병렬처리에 적합하면 효율성이 좋음&lt;br /&gt;
&lt;br /&gt;
==== 단점 ==== &lt;br /&gt;
# &amp;quot;=&amp;quot; 조건에만 적합, 범위 조건(between 이나 like) 조인은 사용할수 없음.&lt;br /&gt;
# 메모리에 의존적임(빌드테이블이 메모리내에 들어가지 못하면 템프 i/o가 발생)&lt;br /&gt;
# 조인키값이 한쪽에 치우치거나(skew),중복도가 높으면 특정 버킷에 연결된 버킷이 길어져 탐색하는 시간이 증가함(CPU사용량 급증, 충돌증가)&lt;br /&gt;
# 버킷수가 데이터 대비 부족하면 충돌 발생과 체인 길이가 늘어 탐색 비용이 증가함  &lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1565</id>
		<title>HASH 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1565"/>
		<updated>2025-10-21T01:10:28Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* HASH 조인이 느려지는 원인 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해시 조인(Hash Join) ==&lt;br /&gt;
{{틀:서브&lt;br /&gt;
|제목=&#039;&#039;&#039;&amp;lt;big&amp;gt;해시조인시 사용되는 해시(hash)함수는 입력된 데이터를 &#039;잘게 다져서&#039; 고유하고 일정한 크기의 식별자로 만들어 냄.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
* 해시의 주요특징&lt;br /&gt;
** 단방향성 : 해시값을 원래의 값으로 되돌릴수 없다.&lt;br /&gt;
** 고유성 : 입력값이 바뀌면 해시값은 완전히 달라진다.&lt;br /&gt;
** 고정된길이 : 입력값의 크기와 상관없이 항상 동일한 길이의 출력값을 가진다.&lt;br /&gt;
|내용=* hash의 영문의 뜻 : &#039;&#039;&#039;잘게 썰다, 다지다의 의미(요리에서 재료를 잘게 썰어서 섞을때 쓰이는말, 예로 맥도날도 &#039;해시브라운(감자를 다져서 튀긴요리)&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
# 해시 조인(Hash Join)은 두 개의 테이블을 조인할 때, 더 작은 테이블을 메모리에 올리고 해시 테이블을 만들어 효율적으로 조인하는 방식&lt;br /&gt;
# 대용량 테이블을 조인할 때 주로 사용되며, 조인 조건에 인덱스가 없는 경우에도 빠르게 작동&lt;br /&gt;
&lt;br /&gt;
=== 해시 조인의 원리 ===&lt;br /&gt;
* 해시 조인은 크게 두 단계로 나뉩니다.&lt;br /&gt;
# 빌드 단계 (Build Phase):&lt;br /&gt;
## 옵티마이저는 두 테이블 중 더 작은 테이블(빌드 테이블)을 선택&lt;br /&gt;
## 이 테이블의 조인 키(컬럼)를 사용하여 PGA 메모리 영역에 해시 테이블을 생성. 각 조인 키 값은 해시 함수를 통하여 해시값으로 변환되어 해시 테이블의 특정 위치에 저장.&lt;br /&gt;
## 해시테이블 저장시 변환된 해시값들은 해시버킷의 갯수 만큼 쪼개서 할당되고 &amp;quot;버킷&amp;quot;에 행을 연결리스트(체인) 형태로 저장됨.&lt;br /&gt;
## 각 버킷은 &amp;quot;해당 해시 범위에 속하는 행들의 포인터(체인 헤드)&amp;quot; 역활을 하며 , 동일 해시값 이나 충돌이 발생하면 같은 버킷내에서 체인으로 연결됨.(많이 연결될수록 성능 저하,이구간이 성능저하 구간) &lt;br /&gt;
# 프로브 단계 (Probe Phase):&lt;br /&gt;
## 더 큰 테이블(프로브 테이블)을 순차적으로 읽어옮&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 테이블에서 일치하는 행을 찾음.(대상 버킷을 찾고 해당 버킷의 체인을 스캔하여 조인키를 비교)&lt;br /&gt;
## 일치하는 행을 찾으면, 두 테이블의 행을 결합하여 결과를 반환.&lt;br /&gt;
&lt;br /&gt;
* 만약 빌드 테이블이 PGA 메모리에 다 들어가지 않을 정도로 크다면, 테이블을 여러 개의 파티션으로 나누어 디스크에 임시로 저장한 뒤(성능 저하 구간), 각 파티션별로 빌드와 프로브 단계를 반복하는 &#039;그레이스풀 해시 조인(Graceful Hash Join)&#039;이 수행(성능저하 구간)&lt;br /&gt;
* 런타임 파티셔닝을 수행해 부분(배치/파티션) 단위로 템프에 내린 후(DISK I/O발생,성능저하) 다시 합쳐서 1-pass/멀티패스 방식으로 진행함.&lt;br /&gt;
&lt;br /&gt;
=== 파이썬 예제 ===&lt;br /&gt;
* 두 개의 CSV 파일 sales.csv와 products.csv가 있다고 가정하고, 이를 파이썬 딕셔너리로 해시 조인을 구현하는 예제.&lt;br /&gt;
1. 데이터 준비 (sales.csv, products.csv 파일)&lt;br /&gt;
&lt;br /&gt;
1) 대량 테이블 - 판매(sales.csv 파일)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sale_id,product_id,amount&lt;br /&gt;
1,101,100&lt;br /&gt;
2,102,150&lt;br /&gt;
3,101,200&lt;br /&gt;
4,103,50&lt;br /&gt;
....&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2) 제품 (products.csv)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
product_id,product_name&lt;br /&gt;
101,Laptop&lt;br /&gt;
102,Mouse&lt;br /&gt;
103,Keyboard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 해시 조인 구현 코드&lt;br /&gt;
&lt;br /&gt;
소량인 products 테이블을 빌드 테이블로, sales 테이블을 프로브 테이블로 사용하여 조인을 수행.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import csv&lt;br /&gt;
&lt;br /&gt;
# 1. 빌드 테이블: products.csv를 읽어 메모리 해시 테이블(딕셔너리) 생성&lt;br /&gt;
# &#039;product_id&#039;를 키로, &#039;product_name&#039;을 값으로 저장&lt;br /&gt;
products_hash_table = {}&lt;br /&gt;
with open(&#039;products.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # 해시 테이블 생성 (딕셔너리)&lt;br /&gt;
        products_hash_table[row[&#039;product_id&#039;]] = row[&#039;product_name&#039;]&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;--- 빌드 단계 (해시 테이블) ---&amp;quot;)&lt;br /&gt;
print(products_hash_table)&lt;br /&gt;
# 출력: {&#039;101&#039;: &#039;Laptop&#039;, &#039;102&#039;: &#039;Mouse&#039;, &#039;103&#039;: &#039;Keyboard&#039;}&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;\n--- 프로브 단계 (조인 결과) ---&amp;quot;)&lt;br /&gt;
# 2. 프로브 테이블: sales.csv를 읽어 해시 테이블에서 찾기&lt;br /&gt;
with open(&#039;sales.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # sale_id 3의 &#039;product_id&#039;인 &#039;101&#039;을 해시 테이블에서 찾음&lt;br /&gt;
        product_id = row[&#039;product_id&#039;]&lt;br /&gt;
        if product_id in products_hash_table:&lt;br /&gt;
            # 해시 테이블에서 &#039;product_id&#039;를 키로 검색하여 &#039;product_name&#039;을 가져옴&lt;br /&gt;
            product_name = products_hash_table[product_id]&lt;br /&gt;
            print(f&amp;quot;Sale ID: {row[&#039;sale_id&#039;]}, Product Name: {product_name}, Amount: {row[&#039;amount&#039;]}&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 예상 출력:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Sale ID: 1, Product Name: Laptop, Amount: 100&lt;br /&gt;
# Sale ID: 2, Product Name: Mouse, Amount: 150&lt;br /&gt;
# Sale ID: 3, Product Name: Laptop, Amount: 200&lt;br /&gt;
# Sale ID: 4, Product Name: Keyboard, Amount: 50&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# products_hash_table이라는 딕셔너리가 바로 해시 테이블 역할을 합니다. &lt;br /&gt;
# sales 테이블의 각 행을 읽을 때마다 product_id를 딕셔너리(해시 테이블)에서 빠르게 찾아 product_name을 가져와 조인된 결과를 출력하는 방식&lt;br /&gt;
# 이를 통해 전체 products 테이블을 다시 스캔할 필요 없이 O(1)에 가까운 시간 복잡도로 조인 조건을 만족하는 데이터를 찾을 수 있음.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 성능 향상 원리 ===&lt;br /&gt;
==== Hash 버킷의 역할과 기능 ====&lt;br /&gt;
::::* 해시 버킷은 두 테이블을 효율적으로 조인하기 위한 핵심적인 논리적 저장 공간. &lt;br /&gt;
::::* 이는 조인 키(Join Key)를 기반으로 데이터를 빠르게 찾고 저장하는 데 사용.&lt;br /&gt;
&lt;br /&gt;
===== 역할: 데이터의 논리적 분류 및 저장소 =====&lt;br /&gt;
:::::* 해시 버킷은 조인 키 값을 해시 함수에 넣어 얻은 해시 값이 같은 데이터들을 모아두는 공간.&lt;br /&gt;
:::::* 이는 거대한 데이터를 조인 키를 기준으로 더 작은 그룹으로 나누어 관리하는 역할을 합니다.&lt;br /&gt;
:::::* 이를 통해 빌드 테이블의 모든 데이터를 일일이 탐색하지 않고도, 특정 조인 키에 해당하는 데이터가 어느 버킷에 있는지 바로 알 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
===== 기능: Build 단계와 Probe 단계에서의 활용 =====&lt;br /&gt;
# Build 단계:&lt;br /&gt;
## Oracle은 먼저 두 테이블 중 더 작은 테이블을 선택하여 빌드 테이블로 지정합니다.&lt;br /&gt;
## 빌드 테이블의 각 행을 읽어 조인 키에 해시 함수를 적용하여 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값에 해당하는 버킷에 해당 행의 데이터를 저장합니다. 만약 같은 버킷에 이미 다른 데이터가 있다면, 이를 연결 리스트(Linked List) 형태로 연결하여 **해시 충돌(Hash Collision)**을 처리합니다.&lt;br /&gt;
# Probe 단계:&lt;br /&gt;
## 이제 더 큰 테이블인 프로브 테이블을 순차적으로 읽습니다.&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 버킷 위치를 즉시 찾아갑니다.&lt;br /&gt;
## 해당 버킷 안의 연결 리스트를 탐색하여 조인 키가 일치하는 행을 찾습니다.&lt;br /&gt;
## 일치하는 행이 발견되면 두 테이블의 데이터를 결합하여 최종 결과를 생성합니다.&lt;br /&gt;
&lt;br /&gt;
* 이러한 해시 버킷의 활용 덕분에, 해시 조인은 테이블의 크기와 상관없이 조인 키가 같은 데이터들을 O(1)에 가까운 시간 복잡도로 찾아낼 수 있어 대용량 데이터 조인에 매우 효과적입니다.&lt;br /&gt;
&lt;br /&gt;
==== 해시 버킷 개수의 결정 ====&lt;br /&gt;
* 해시 조인의 최대 해시 버킷 개수는 사전 설정된 고정값이 아니라, 조인하려는 테이블의 크기, 옵티마이저의 예측, 그리고 PGA_AGGREGATE_TARGET 파라미터에 의해 동적으로 결정&lt;br /&gt;
&lt;br /&gt;
# 빌드 테이블(Build Table)의 크기: 옵티마이저가 예측한 더 작은 테이블(빌드 테이블)의 크기에 따라 필요한 해시 버킷의 수가 결정됨&lt;br /&gt;
# 할당된 PGA 메모리: PGA_AGGREGATE_TARGET에 의해 세션에 할당된 PGA 메모리 내에서 해시 테이블이 생성됨.&lt;br /&gt;
# 메모리 부족 시: 만약 할당된 PGA 메모리만으로 해시 테이블을 모두 생성할 수 없다면, Oracle은 일부 해시 버킷을 디스크의 임시 테이블스페이스(temp tablespace)로 분할하여 저장합니다. &lt;br /&gt;
## 이를 &#039;해시 스필(Hash Spill)&#039;이라고 부르며, 이 경우 I/O가 발생하여 성능이 저하됨.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인이 느려지는 원인 ====&lt;br /&gt;
* 해시 조인 성능이 느려지는 주요 원인 중 하나는 해시 버킷 때문입니다.&lt;br /&gt;
* 정확히 말하면, 해시 충돌이 심하게 발생하거나 해시 테이블이 메모리에 맞지 않아 디스크 I/O가 발생하는 경우입니다.&lt;br /&gt;
* 해시 버킷 자체는 효율성을 위한 도구지만, 비정상적인 상황에서는 오히려 성능 저하의 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
1. 해시 충돌 (Hash Collisions)&lt;br /&gt;
* 원리: 해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다.&lt;br /&gt;
* 문제점: 해시 충돌이 발생하면, 하나의 버킷에 여러 행이 연결 리스트 형태로 쌓이게 됩니다. 이 버킷을 탐색할 때, 원하는 데이터를 찾기 위해 연결된 모든 데이터를 순차적으로 비교해야 하므로, 탐색 시간이 O(1)에서 O(N)으로 늘어납니다.&lt;br /&gt;
* 결과: 조인 키 데이터 분포가 불균형하거나(데이터 편중), 해시 함수가 비효율적이면 해시 충돌이 자주 발생하여 성능이 크게 저하됩니다.&lt;br /&gt;
* (결론) 해시조인은 조인키로 사용되는 컬럼이 중복이 많을수록  성능이 좋지 않음&lt;br /&gt;
&lt;br /&gt;
* 파이썬으로 해시 충돌 구현 예제&lt;br /&gt;
** 고의로 해시 충돌을 유발하는 데이터를 사용하여 해시 조인의 성능 차이를 보여줍니다. &lt;br /&gt;
** products_collision 리스트의 모든 product_id는 해시 함수를 거치면 같은 값을 반환하도록 설계되었습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터 (해시 충돌이 거의 없음)&lt;br /&gt;
products_normal = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 102, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 103, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 의도적으로 해시 충돌을 유발하는 데이터&lt;br /&gt;
# 101, 201, 301 모두 100으로 나눈 나머지가 1이 되도록 설계&lt;br /&gt;
products_collision = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 201, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 301, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인 대상 데이터 (각 product_id에 대한 sales 데이터)&lt;br /&gt;
sales = [{&#039;sale_id&#039;: i, &#039;product_id&#039;: 101, &#039;amount&#039;: 100} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+1000, &#039;product_id&#039;: 201, &#039;amount&#039;: 150} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+2000, &#039;product_id&#039;: 301, &#039;amount&#039;: 200} for i in range(1000)]&lt;br /&gt;
&lt;br /&gt;
# 사용자 정의 해시 함수 (단순화를 위해)&lt;br /&gt;
def custom_hash(key):&lt;br /&gt;
    return key % 100&lt;br /&gt;
&lt;br /&gt;
def hash_join(build_table, probe_table):&lt;br /&gt;
    # 빌드 단계: 해시 버킷(딕셔너리) 생성&lt;br /&gt;
    # 파이썬 딕셔너리의 키 충돌을 구현하기 위해, 딕셔너리의 값을 리스트로 저장&lt;br /&gt;
    hash_buckets = {}&lt;br /&gt;
    for item in build_table:&lt;br /&gt;
        key = item[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        if bucket_index not in hash_buckets:&lt;br /&gt;
            hash_buckets[bucket_index] = []&lt;br /&gt;
        hash_buckets[bucket_index].append(item)&lt;br /&gt;
&lt;br /&gt;
    # 프로브 단계: 조인&lt;br /&gt;
    start_time = time.time()&lt;br /&gt;
    joined_results = []&lt;br /&gt;
    for sale in probe_table:&lt;br /&gt;
        key = sale[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        &lt;br /&gt;
        # 해시 버킷에서 일치하는 데이터를 찾기 (충돌 발생 시 순차 탐색)&lt;br /&gt;
        if bucket_index in hash_buckets:&lt;br /&gt;
            for product_info in hash_buckets[bucket_index]:&lt;br /&gt;
                if product_info[&#039;product_id&#039;] == key:&lt;br /&gt;
                    joined_results.append({**sale, **product_info}) # **은 언패킹연산자 {**A,**B} 는 A,B딕셔너리를 합쳐줌.&lt;br /&gt;
                    break&lt;br /&gt;
    end_time = time.time()&lt;br /&gt;
    return end_time - start_time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터로 조인&lt;br /&gt;
normal_time = hash_join(products_normal, sales)&lt;br /&gt;
&lt;br /&gt;
# 해시 충돌 데이터로 조인&lt;br /&gt;
collision_time = hash_join(products_collision, sales)&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;일반 데이터 조인 시간: {normal_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;해시 충돌 조인 시간: {collision_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;성능 저하 비율: {(collision_time / normal_time):.2f}배 느림&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
일반 데이터 조인 시간: 0.000971 초&lt;br /&gt;
해시 충돌 조인 시간: 0.001568 초&lt;br /&gt;
성능 저하 비율: 1.61배 느림&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 메모리 부족 및 디스크 I/O&lt;br /&gt;
* 원리: 해시 조인은 더 작은 테이블(빌드 테이블)을 메모리에 올려 해시 테이블을 만드는 것이 기본 전제입니다.&lt;br /&gt;
* 문제점: 빌드 테이블이 너무 커서 **메모리(PGA)**에 다 담을 수 없게 되면, Oracle은 해시 테이블을 여러 파티션으로 나누어 일부를 **임시 테이블스페이스(Temporary Tablespace)**에 저장합니다.&lt;br /&gt;
* 결과: 조인을 수행하기 위해 디스크에 기록된 파티션 데이터를 읽고 쓰는 디스크 I/O 작업이 반복적으로 발생합니다. 이는 메모리에서만 작업할 때보다 훨씬 느리므로 성능 저하의 결정적인 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
* 해시 조인이 효율적으로 작동하는 것은 해시 버킷이 잘 분산되어 있고, 해시 테이블이 메모리에서 모두 처리될 때입니다. 만약 데이터가 한쪽으로 치우쳐 해시 충돌이 심하거나, 메모리 부족으로 디스크 I/O가 발생하면 해시 조인의 장점이 사라져 성능이 느려지게 됩니다.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 장/단점 ===&lt;br /&gt;
==== 장점 ====&lt;br /&gt;
# 대용량 테이블간 &amp;quot;=&amp;quot; 조건의 조인에 적합(정렬 불필요,입력 순서 무관)&lt;br /&gt;
# 빠른 접근 가능(버킷을 이용한 빠른 처리)&lt;br /&gt;
# 병렬처리에 적합하면 효율성이 좋음&lt;br /&gt;
&lt;br /&gt;
==== 단점 ==== &lt;br /&gt;
# &amp;quot;=&amp;quot; 조건에만 적합, 범위 조건(between 이나 like) 조인은 사용할수 없음.&lt;br /&gt;
# 메모리에 의존적임(빌드테이블이 메모리내에 들어가지 못하면 템프 i/o가 발생)&lt;br /&gt;
# 조인키값이 한쪽에 치우치거나(skew),중복도가 높으면 특정 버킷에 연결된 버킷이 길어져 탐색하는 시간이 증가함(CPU사용량 급증, 충돌증가)&lt;br /&gt;
# 버킷수가 데이터 대비 부족하면 충돌 발생과 체인 길이가 늘어 탐색 비용이 증가함  &lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1564</id>
		<title>HASH 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1564"/>
		<updated>2025-10-21T01:06:35Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해시 조인(Hash Join) ==&lt;br /&gt;
{{틀:서브&lt;br /&gt;
|제목=&#039;&#039;&#039;&amp;lt;big&amp;gt;해시조인시 사용되는 해시(hash)함수는 입력된 데이터를 &#039;잘게 다져서&#039; 고유하고 일정한 크기의 식별자로 만들어 냄.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
* 해시의 주요특징&lt;br /&gt;
** 단방향성 : 해시값을 원래의 값으로 되돌릴수 없다.&lt;br /&gt;
** 고유성 : 입력값이 바뀌면 해시값은 완전히 달라진다.&lt;br /&gt;
** 고정된길이 : 입력값의 크기와 상관없이 항상 동일한 길이의 출력값을 가진다.&lt;br /&gt;
|내용=* hash의 영문의 뜻 : &#039;&#039;&#039;잘게 썰다, 다지다의 의미(요리에서 재료를 잘게 썰어서 섞을때 쓰이는말, 예로 맥도날도 &#039;해시브라운(감자를 다져서 튀긴요리)&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
# 해시 조인(Hash Join)은 두 개의 테이블을 조인할 때, 더 작은 테이블을 메모리에 올리고 해시 테이블을 만들어 효율적으로 조인하는 방식&lt;br /&gt;
# 대용량 테이블을 조인할 때 주로 사용되며, 조인 조건에 인덱스가 없는 경우에도 빠르게 작동&lt;br /&gt;
&lt;br /&gt;
=== 해시 조인의 원리 ===&lt;br /&gt;
* 해시 조인은 크게 두 단계로 나뉩니다.&lt;br /&gt;
# 빌드 단계 (Build Phase):&lt;br /&gt;
## 옵티마이저는 두 테이블 중 더 작은 테이블(빌드 테이블)을 선택&lt;br /&gt;
## 이 테이블의 조인 키(컬럼)를 사용하여 PGA 메모리 영역에 해시 테이블을 생성. 각 조인 키 값은 해시 함수를 통하여 해시값으로 변환되어 해시 테이블의 특정 위치에 저장.&lt;br /&gt;
## 해시테이블 저장시 변환된 해시값들은 해시버킷의 갯수 만큼 쪼개서 할당되고 &amp;quot;버킷&amp;quot;에 행을 연결리스트(체인) 형태로 저장됨.&lt;br /&gt;
## 각 버킷은 &amp;quot;해당 해시 범위에 속하는 행들의 포인터(체인 헤드)&amp;quot; 역활을 하며 , 동일 해시값 이나 충돌이 발생하면 같은 버킷내에서 체인으로 연결됨.(많이 연결될수록 성능 저하,이구간이 성능저하 구간) &lt;br /&gt;
# 프로브 단계 (Probe Phase):&lt;br /&gt;
## 더 큰 테이블(프로브 테이블)을 순차적으로 읽어옮&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 테이블에서 일치하는 행을 찾음.(대상 버킷을 찾고 해당 버킷의 체인을 스캔하여 조인키를 비교)&lt;br /&gt;
## 일치하는 행을 찾으면, 두 테이블의 행을 결합하여 결과를 반환.&lt;br /&gt;
&lt;br /&gt;
* 만약 빌드 테이블이 PGA 메모리에 다 들어가지 않을 정도로 크다면, 테이블을 여러 개의 파티션으로 나누어 디스크에 임시로 저장한 뒤(성능 저하 구간), 각 파티션별로 빌드와 프로브 단계를 반복하는 &#039;그레이스풀 해시 조인(Graceful Hash Join)&#039;이 수행(성능저하 구간)&lt;br /&gt;
* 런타임 파티셔닝을 수행해 부분(배치/파티션) 단위로 템프에 내린 후(DISK I/O발생,성능저하) 다시 합쳐서 1-pass/멀티패스 방식으로 진행함.&lt;br /&gt;
&lt;br /&gt;
=== 파이썬 예제 ===&lt;br /&gt;
* 두 개의 CSV 파일 sales.csv와 products.csv가 있다고 가정하고, 이를 파이썬 딕셔너리로 해시 조인을 구현하는 예제.&lt;br /&gt;
1. 데이터 준비 (sales.csv, products.csv 파일)&lt;br /&gt;
&lt;br /&gt;
1) 대량 테이블 - 판매(sales.csv 파일)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sale_id,product_id,amount&lt;br /&gt;
1,101,100&lt;br /&gt;
2,102,150&lt;br /&gt;
3,101,200&lt;br /&gt;
4,103,50&lt;br /&gt;
....&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2) 제품 (products.csv)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
product_id,product_name&lt;br /&gt;
101,Laptop&lt;br /&gt;
102,Mouse&lt;br /&gt;
103,Keyboard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 해시 조인 구현 코드&lt;br /&gt;
&lt;br /&gt;
소량인 products 테이블을 빌드 테이블로, sales 테이블을 프로브 테이블로 사용하여 조인을 수행.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import csv&lt;br /&gt;
&lt;br /&gt;
# 1. 빌드 테이블: products.csv를 읽어 메모리 해시 테이블(딕셔너리) 생성&lt;br /&gt;
# &#039;product_id&#039;를 키로, &#039;product_name&#039;을 값으로 저장&lt;br /&gt;
products_hash_table = {}&lt;br /&gt;
with open(&#039;products.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # 해시 테이블 생성 (딕셔너리)&lt;br /&gt;
        products_hash_table[row[&#039;product_id&#039;]] = row[&#039;product_name&#039;]&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;--- 빌드 단계 (해시 테이블) ---&amp;quot;)&lt;br /&gt;
print(products_hash_table)&lt;br /&gt;
# 출력: {&#039;101&#039;: &#039;Laptop&#039;, &#039;102&#039;: &#039;Mouse&#039;, &#039;103&#039;: &#039;Keyboard&#039;}&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;\n--- 프로브 단계 (조인 결과) ---&amp;quot;)&lt;br /&gt;
# 2. 프로브 테이블: sales.csv를 읽어 해시 테이블에서 찾기&lt;br /&gt;
with open(&#039;sales.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # sale_id 3의 &#039;product_id&#039;인 &#039;101&#039;을 해시 테이블에서 찾음&lt;br /&gt;
        product_id = row[&#039;product_id&#039;]&lt;br /&gt;
        if product_id in products_hash_table:&lt;br /&gt;
            # 해시 테이블에서 &#039;product_id&#039;를 키로 검색하여 &#039;product_name&#039;을 가져옴&lt;br /&gt;
            product_name = products_hash_table[product_id]&lt;br /&gt;
            print(f&amp;quot;Sale ID: {row[&#039;sale_id&#039;]}, Product Name: {product_name}, Amount: {row[&#039;amount&#039;]}&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 예상 출력:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Sale ID: 1, Product Name: Laptop, Amount: 100&lt;br /&gt;
# Sale ID: 2, Product Name: Mouse, Amount: 150&lt;br /&gt;
# Sale ID: 3, Product Name: Laptop, Amount: 200&lt;br /&gt;
# Sale ID: 4, Product Name: Keyboard, Amount: 50&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# products_hash_table이라는 딕셔너리가 바로 해시 테이블 역할을 합니다. &lt;br /&gt;
# sales 테이블의 각 행을 읽을 때마다 product_id를 딕셔너리(해시 테이블)에서 빠르게 찾아 product_name을 가져와 조인된 결과를 출력하는 방식&lt;br /&gt;
# 이를 통해 전체 products 테이블을 다시 스캔할 필요 없이 O(1)에 가까운 시간 복잡도로 조인 조건을 만족하는 데이터를 찾을 수 있음.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 성능 향상 원리 ===&lt;br /&gt;
==== Hash 버킷의 역할과 기능 ====&lt;br /&gt;
::::* 해시 버킷은 두 테이블을 효율적으로 조인하기 위한 핵심적인 논리적 저장 공간. &lt;br /&gt;
::::* 이는 조인 키(Join Key)를 기반으로 데이터를 빠르게 찾고 저장하는 데 사용.&lt;br /&gt;
&lt;br /&gt;
===== 역할: 데이터의 논리적 분류 및 저장소 =====&lt;br /&gt;
:::::* 해시 버킷은 조인 키 값을 해시 함수에 넣어 얻은 해시 값이 같은 데이터들을 모아두는 공간.&lt;br /&gt;
:::::* 이는 거대한 데이터를 조인 키를 기준으로 더 작은 그룹으로 나누어 관리하는 역할을 합니다.&lt;br /&gt;
:::::* 이를 통해 빌드 테이블의 모든 데이터를 일일이 탐색하지 않고도, 특정 조인 키에 해당하는 데이터가 어느 버킷에 있는지 바로 알 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
===== 기능: Build 단계와 Probe 단계에서의 활용 =====&lt;br /&gt;
# Build 단계:&lt;br /&gt;
## Oracle은 먼저 두 테이블 중 더 작은 테이블을 선택하여 빌드 테이블로 지정합니다.&lt;br /&gt;
## 빌드 테이블의 각 행을 읽어 조인 키에 해시 함수를 적용하여 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값에 해당하는 버킷에 해당 행의 데이터를 저장합니다. 만약 같은 버킷에 이미 다른 데이터가 있다면, 이를 연결 리스트(Linked List) 형태로 연결하여 **해시 충돌(Hash Collision)**을 처리합니다.&lt;br /&gt;
# Probe 단계:&lt;br /&gt;
## 이제 더 큰 테이블인 프로브 테이블을 순차적으로 읽습니다.&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 버킷 위치를 즉시 찾아갑니다.&lt;br /&gt;
## 해당 버킷 안의 연결 리스트를 탐색하여 조인 키가 일치하는 행을 찾습니다.&lt;br /&gt;
## 일치하는 행이 발견되면 두 테이블의 데이터를 결합하여 최종 결과를 생성합니다.&lt;br /&gt;
&lt;br /&gt;
* 이러한 해시 버킷의 활용 덕분에, 해시 조인은 테이블의 크기와 상관없이 조인 키가 같은 데이터들을 O(1)에 가까운 시간 복잡도로 찾아낼 수 있어 대용량 데이터 조인에 매우 효과적입니다.&lt;br /&gt;
&lt;br /&gt;
==== 해시 버킷 개수의 결정 ====&lt;br /&gt;
* 해시 조인의 최대 해시 버킷 개수는 사전 설정된 고정값이 아니라, 조인하려는 테이블의 크기, 옵티마이저의 예측, 그리고 PGA_AGGREGATE_TARGET 파라미터에 의해 동적으로 결정&lt;br /&gt;
&lt;br /&gt;
# 빌드 테이블(Build Table)의 크기: 옵티마이저가 예측한 더 작은 테이블(빌드 테이블)의 크기에 따라 필요한 해시 버킷의 수가 결정됨&lt;br /&gt;
# 할당된 PGA 메모리: PGA_AGGREGATE_TARGET에 의해 세션에 할당된 PGA 메모리 내에서 해시 테이블이 생성됨.&lt;br /&gt;
# 메모리 부족 시: 만약 할당된 PGA 메모리만으로 해시 테이블을 모두 생성할 수 없다면, Oracle은 일부 해시 버킷을 디스크의 임시 테이블스페이스(temp tablespace)로 분할하여 저장합니다. &lt;br /&gt;
## 이를 &#039;해시 스필(Hash Spill)&#039;이라고 부르며, 이 경우 I/O가 발생하여 성능이 저하됨.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인이 느려지는 원인 ====&lt;br /&gt;
* 해시 조인 성능이 느려지는 주요 원인 중 하나는 해시 버킷 때문입니다.&lt;br /&gt;
* 정확히 말하면, 해시 충돌이 심하게 발생하거나 해시 테이블이 메모리에 맞지 않아 디스크 I/O가 발생하는 경우입니다.&lt;br /&gt;
* 해시 버킷 자체는 효율성을 위한 도구지만, 비정상적인 상황에서는 오히려 성능 저하의 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
1. 해시 충돌 (Hash Collisions)&lt;br /&gt;
* 원리: 해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다.&lt;br /&gt;
* 문제점: 해시 충돌이 발생하면, 하나의 버킷에 여러 행이 연결 리스트 형태로 쌓이게 됩니다. 이 버킷을 탐색할 때, 원하는 데이터를 찾기 위해 연결된 모든 데이터를 순차적으로 비교해야 하므로, 탐색 시간이 O(1)에서 O(N)으로 늘어납니다.&lt;br /&gt;
* 결과: 조인 키 데이터 분포가 불균형하거나(데이터 편중), 해시 함수가 비효율적이면 해시 충돌이 자주 발생하여 성능이 크게 저하됩니다.&lt;br /&gt;
* (결론) 해시조인은 조인키로 사용되는 컬럼이 중복이 많을수록  성능이 좋지 않음&lt;br /&gt;
&lt;br /&gt;
* 파이썬으로 해시 충돌 구현 예제&lt;br /&gt;
** 고의로 해시 충돌을 유발하는 데이터를 사용하여 해시 조인의 성능 차이를 보여줍니다. &lt;br /&gt;
** products_collision 리스트의 모든 product_id는 해시 함수를 거치면 같은 값을 반환하도록 설계되었습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터 (해시 충돌이 거의 없음)&lt;br /&gt;
products_normal = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 102, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 103, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 의도적으로 해시 충돌을 유발하는 데이터&lt;br /&gt;
# 101, 201, 301 모두 100으로 나눈 나머지가 1이 되도록 설계&lt;br /&gt;
products_collision = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 201, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 301, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인 대상 데이터 (각 product_id에 대한 sales 데이터)&lt;br /&gt;
sales = [{&#039;sale_id&#039;: i, &#039;product_id&#039;: 101, &#039;amount&#039;: 100} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+1000, &#039;product_id&#039;: 201, &#039;amount&#039;: 150} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+2000, &#039;product_id&#039;: 301, &#039;amount&#039;: 200} for i in range(1000)]&lt;br /&gt;
&lt;br /&gt;
# 사용자 정의 해시 함수 (단순화를 위해)&lt;br /&gt;
def custom_hash(key):&lt;br /&gt;
    return key % 100&lt;br /&gt;
&lt;br /&gt;
def hash_join(build_table, probe_table):&lt;br /&gt;
    # 빌드 단계: 해시 버킷(딕셔너리) 생성&lt;br /&gt;
    # 파이썬 딕셔너리의 키 충돌을 구현하기 위해, 딕셔너리의 값을 리스트로 저장&lt;br /&gt;
    hash_buckets = {}&lt;br /&gt;
    for item in build_table:&lt;br /&gt;
        key = item[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        if bucket_index not in hash_buckets:&lt;br /&gt;
            hash_buckets[bucket_index] = []&lt;br /&gt;
        hash_buckets[bucket_index].append(item)&lt;br /&gt;
&lt;br /&gt;
    # 프로브 단계: 조인&lt;br /&gt;
    start_time = time.time()&lt;br /&gt;
    joined_results = []&lt;br /&gt;
    for sale in probe_table:&lt;br /&gt;
        key = sale[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        &lt;br /&gt;
        # 해시 버킷에서 일치하는 데이터를 찾기 (충돌 발생 시 순차 탐색)&lt;br /&gt;
        if bucket_index in hash_buckets:&lt;br /&gt;
            for product_info in hash_buckets[bucket_index]:&lt;br /&gt;
                if product_info[&#039;product_id&#039;] == key:&lt;br /&gt;
                    joined_results.append({**sale, **product_info}) # **은 언패킹연산자 {**A,**B} 는 A,B딕셔너리를 합쳐줌.&lt;br /&gt;
                    break&lt;br /&gt;
    end_time = time.time()&lt;br /&gt;
    return end_time - start_time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터로 조인&lt;br /&gt;
normal_time = hash_join(products_normal, sales)&lt;br /&gt;
&lt;br /&gt;
# 해시 충돌 데이터로 조인&lt;br /&gt;
collision_time = hash_join(products_collision, sales)&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;일반 데이터 조인 시간: {normal_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;해시 충돌 조인 시간: {collision_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;성능 저하 비율: {(collision_time / normal_time):.2f}배 느림&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. 메모리 부족 및 디스크 I/O&lt;br /&gt;
* 원리: 해시 조인은 더 작은 테이블(빌드 테이블)을 메모리에 올려 해시 테이블을 만드는 것이 기본 전제입니다.&lt;br /&gt;
* 문제점: 빌드 테이블이 너무 커서 **메모리(PGA)**에 다 담을 수 없게 되면, Oracle은 해시 테이블을 여러 파티션으로 나누어 일부를 **임시 테이블스페이스(Temporary Tablespace)**에 저장합니다.&lt;br /&gt;
* 결과: 조인을 수행하기 위해 디스크에 기록된 파티션 데이터를 읽고 쓰는 디스크 I/O 작업이 반복적으로 발생합니다. 이는 메모리에서만 작업할 때보다 훨씬 느리므로 성능 저하의 결정적인 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
* 해시 조인이 효율적으로 작동하는 것은 해시 버킷이 잘 분산되어 있고, 해시 테이블이 메모리에서 모두 처리될 때입니다. 만약 데이터가 한쪽으로 치우쳐 해시 충돌이 심하거나, 메모리 부족으로 디스크 I/O가 발생하면 해시 조인의 장점이 사라져 성능이 느려지게 됩니다.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 장/단점 ===&lt;br /&gt;
==== 장점 ====&lt;br /&gt;
# 대용량 테이블간 &amp;quot;=&amp;quot; 조건의 조인에 적합(정렬 불필요,입력 순서 무관)&lt;br /&gt;
# 빠른 접근 가능(버킷을 이용한 빠른 처리)&lt;br /&gt;
# 병렬처리에 적합하면 효율성이 좋음&lt;br /&gt;
&lt;br /&gt;
==== 단점 ==== &lt;br /&gt;
# &amp;quot;=&amp;quot; 조건에만 적합, 범위 조건(between 이나 like) 조인은 사용할수 없음.&lt;br /&gt;
# 메모리에 의존적임(빌드테이블이 메모리내에 들어가지 못하면 템프 i/o가 발생)&lt;br /&gt;
# 조인키값이 한쪽에 치우치거나(skew),중복도가 높으면 특정 버킷에 연결된 버킷이 길어져 탐색하는 시간이 증가함(CPU사용량 급증, 충돌증가)&lt;br /&gt;
# 버킷수가 데이터 대비 부족하면 충돌 발생과 체인 길이가 늘어 탐색 비용이 증가함  &lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1563</id>
		<title>HASH 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1563"/>
		<updated>2025-10-21T00:41:22Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* HASH 조인의 성능 향상 원리 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해시 조인(Hash Join) ==&lt;br /&gt;
{{틀:서브&lt;br /&gt;
|제목=&#039;&#039;&#039;&amp;lt;big&amp;gt;해시조인시 사용되는 해시(hash)함수는 입력된 데이터를 &#039;잘게 다져서&#039; 고유하고 일정한 크기의 식별자로 만들어 냄.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
* 해시의 주요특징&lt;br /&gt;
** 단방향성 : 해시값을 원래의 값으로 되돌릴수 없다.&lt;br /&gt;
** 고유성 : 입력값이 바뀌면 해시값은 완전히 달라진다.&lt;br /&gt;
** 고정된길이 : 입력값의 크기와 상관없이 항상 동일한 길이의 출력값을 가진다.&lt;br /&gt;
|내용=* hash의 영문의 뜻 : &#039;&#039;&#039;잘게 썰다, 다지다의 의미(요리에서 재료를 잘게 썰어서 섞을때 쓰이는말, 예로 맥도날도 &#039;해시브라운(감자를 다져서 튀긴요리)&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
# 해시 조인(Hash Join)은 두 개의 테이블을 조인할 때, 더 작은 테이블을 메모리에 올리고 해시 테이블을 만들어 효율적으로 조인하는 방식&lt;br /&gt;
# 대용량 테이블을 조인할 때 주로 사용되며, 조인 조건에 인덱스가 없는 경우에도 빠르게 작동&lt;br /&gt;
&lt;br /&gt;
=== 해시 조인의 원리 ===&lt;br /&gt;
* 해시 조인은 크게 두 단계로 나뉩니다.&lt;br /&gt;
# 빌드 단계 (Build Phase):&lt;br /&gt;
## 옵티마이저는 두 테이블 중 더 작은 테이블(빌드 테이블)을 선택&lt;br /&gt;
## 이 테이블의 조인 키(컬럼)를 사용하여 PGA 메모리 영역에 해시 테이블을 생성. 각 조인 키 값은 해시 함수를 통하여 해시값으로 변환되어 해시 테이블의 특정 위치에 저장.&lt;br /&gt;
## 해시테이블 저장시 변환된 해시값들은 해시버킷의 갯수 만큼 쪼개서 할당되고 &amp;quot;버킷&amp;quot;에 행을 연결리스트(체인) 형태로 저장됨.&lt;br /&gt;
## 각 버킷은 &amp;quot;해당 해시 범위에 속하는 행들의 포인터(체인 헤드)&amp;quot; 역활을 하며 , 동일 해시값 이나 충돌이 발생하면 같은 버킷내에서 체인으로 연결됨.(많이 연결될수록 성능 저하,이구간이 성능저하 구간) &lt;br /&gt;
# 프로브 단계 (Probe Phase):&lt;br /&gt;
## 더 큰 테이블(프로브 테이블)을 순차적으로 읽어옮&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 테이블에서 일치하는 행을 찾음.(대상 버킷을 찾고 해당 버킷의 체인을 스캔하여 조인키를 비교)&lt;br /&gt;
## 일치하는 행을 찾으면, 두 테이블의 행을 결합하여 결과를 반환.&lt;br /&gt;
&lt;br /&gt;
* 만약 빌드 테이블이 PGA 메모리에 다 들어가지 않을 정도로 크다면, 테이블을 여러 개의 파티션으로 나누어 디스크에 임시로 저장한 뒤(성능 저하 구간), 각 파티션별로 빌드와 프로브 단계를 반복하는 &#039;그레이스풀 해시 조인(Graceful Hash Join)&#039;이 수행(성능저하 구간)&lt;br /&gt;
* 런타임 파티셔닝을 수행해 부분(배치/파티션) 단위로 템프에 내린 후(DISK I/O발생,성능저하) 다시 합쳐서 1-pass/멀티패스 방식으로 진행함.&lt;br /&gt;
&lt;br /&gt;
=== 파이썬 예제 ===&lt;br /&gt;
* 두 개의 CSV 파일 sales.csv와 products.csv가 있다고 가정하고, 이를 파이썬 딕셔너리로 해시 조인을 구현하는 예제.&lt;br /&gt;
1. 데이터 준비 (sales.csv, products.csv 파일)&lt;br /&gt;
&lt;br /&gt;
1) 대량 테이블 - 판매(sales.csv 파일)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sale_id,product_id,amount&lt;br /&gt;
1,101,100&lt;br /&gt;
2,102,150&lt;br /&gt;
3,101,200&lt;br /&gt;
4,103,50&lt;br /&gt;
....&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2) 제품 (products.csv)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
product_id,product_name&lt;br /&gt;
101,Laptop&lt;br /&gt;
102,Mouse&lt;br /&gt;
103,Keyboard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 해시 조인 구현 코드&lt;br /&gt;
&lt;br /&gt;
소량인 products 테이블을 빌드 테이블로, sales 테이블을 프로브 테이블로 사용하여 조인을 수행.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import csv&lt;br /&gt;
&lt;br /&gt;
# 1. 빌드 테이블: products.csv를 읽어 메모리 해시 테이블(딕셔너리) 생성&lt;br /&gt;
# &#039;product_id&#039;를 키로, &#039;product_name&#039;을 값으로 저장&lt;br /&gt;
products_hash_table = {}&lt;br /&gt;
with open(&#039;products.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # 해시 테이블 생성 (딕셔너리)&lt;br /&gt;
        products_hash_table[row[&#039;product_id&#039;]] = row[&#039;product_name&#039;]&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;--- 빌드 단계 (해시 테이블) ---&amp;quot;)&lt;br /&gt;
print(products_hash_table)&lt;br /&gt;
# 출력: {&#039;101&#039;: &#039;Laptop&#039;, &#039;102&#039;: &#039;Mouse&#039;, &#039;103&#039;: &#039;Keyboard&#039;}&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;\n--- 프로브 단계 (조인 결과) ---&amp;quot;)&lt;br /&gt;
# 2. 프로브 테이블: sales.csv를 읽어 해시 테이블에서 찾기&lt;br /&gt;
with open(&#039;sales.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # sale_id 3의 &#039;product_id&#039;인 &#039;101&#039;을 해시 테이블에서 찾음&lt;br /&gt;
        product_id = row[&#039;product_id&#039;]&lt;br /&gt;
        if product_id in products_hash_table:&lt;br /&gt;
            # 해시 테이블에서 &#039;product_id&#039;를 키로 검색하여 &#039;product_name&#039;을 가져옴&lt;br /&gt;
            product_name = products_hash_table[product_id]&lt;br /&gt;
            print(f&amp;quot;Sale ID: {row[&#039;sale_id&#039;]}, Product Name: {product_name}, Amount: {row[&#039;amount&#039;]}&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 예상 출력:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Sale ID: 1, Product Name: Laptop, Amount: 100&lt;br /&gt;
# Sale ID: 2, Product Name: Mouse, Amount: 150&lt;br /&gt;
# Sale ID: 3, Product Name: Laptop, Amount: 200&lt;br /&gt;
# Sale ID: 4, Product Name: Keyboard, Amount: 50&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# products_hash_table이라는 딕셔너리가 바로 해시 테이블 역할을 합니다. &lt;br /&gt;
# sales 테이블의 각 행을 읽을 때마다 product_id를 딕셔너리(해시 테이블)에서 빠르게 찾아 product_name을 가져와 조인된 결과를 출력하는 방식&lt;br /&gt;
# 이를 통해 전체 products 테이블을 다시 스캔할 필요 없이 O(1)에 가까운 시간 복잡도로 조인 조건을 만족하는 데이터를 찾을 수 있음.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 성능 향상 원리 ===&lt;br /&gt;
==== Hash 버킷의 역할과 기능 ====&lt;br /&gt;
::::* 해시 버킷은 두 테이블을 효율적으로 조인하기 위한 핵심적인 논리적 저장 공간. &lt;br /&gt;
::::* 이는 조인 키(Join Key)를 기반으로 데이터를 빠르게 찾고 저장하는 데 사용.&lt;br /&gt;
&lt;br /&gt;
===== 역할: 데이터의 논리적 분류 및 저장소 =====&lt;br /&gt;
:::::* 해시 버킷은 조인 키 값을 해시 함수에 넣어 얻은 해시 값이 같은 데이터들을 모아두는 공간.&lt;br /&gt;
:::::* 이는 거대한 데이터를 조인 키를 기준으로 더 작은 그룹으로 나누어 관리하는 역할을 합니다.&lt;br /&gt;
:::::* 이를 통해 빌드 테이블의 모든 데이터를 일일이 탐색하지 않고도, 특정 조인 키에 해당하는 데이터가 어느 버킷에 있는지 바로 알 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
===== 기능: Build 단계와 Probe 단계에서의 활용 =====&lt;br /&gt;
# Build 단계:&lt;br /&gt;
## Oracle은 먼저 두 테이블 중 더 작은 테이블을 선택하여 빌드 테이블로 지정합니다.&lt;br /&gt;
## 빌드 테이블의 각 행을 읽어 조인 키에 해시 함수를 적용하여 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값에 해당하는 버킷에 해당 행의 데이터를 저장합니다. 만약 같은 버킷에 이미 다른 데이터가 있다면, 이를 연결 리스트(Linked List) 형태로 연결하여 **해시 충돌(Hash Collision)**을 처리합니다.&lt;br /&gt;
# Probe 단계:&lt;br /&gt;
## 이제 더 큰 테이블인 프로브 테이블을 순차적으로 읽습니다.&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 버킷 위치를 즉시 찾아갑니다.&lt;br /&gt;
## 해당 버킷 안의 연결 리스트를 탐색하여 조인 키가 일치하는 행을 찾습니다.&lt;br /&gt;
## 일치하는 행이 발견되면 두 테이블의 데이터를 결합하여 최종 결과를 생성합니다.&lt;br /&gt;
&lt;br /&gt;
* 이러한 해시 버킷의 활용 덕분에, 해시 조인은 테이블의 크기와 상관없이 조인 키가 같은 데이터들을 O(1)에 가까운 시간 복잡도로 찾아낼 수 있어 대용량 데이터 조인에 매우 효과적입니다.&lt;br /&gt;
&lt;br /&gt;
==== 해시 버킷 개수의 결정 ====&lt;br /&gt;
* 해시 조인의 최대 해시 버킷 개수는 사전 설정된 고정값이 아니라, 조인하려는 테이블의 크기, 옵티마이저의 예측, 그리고 PGA_AGGREGATE_TARGET 파라미터에 의해 동적으로 결정&lt;br /&gt;
&lt;br /&gt;
# 빌드 테이블(Build Table)의 크기: 옵티마이저가 예측한 더 작은 테이블(빌드 테이블)의 크기에 따라 필요한 해시 버킷의 수가 결정됨&lt;br /&gt;
# 할당된 PGA 메모리: PGA_AGGREGATE_TARGET에 의해 세션에 할당된 PGA 메모리 내에서 해시 테이블이 생성됨.&lt;br /&gt;
# 메모리 부족 시: 만약 할당된 PGA 메모리만으로 해시 테이블을 모두 생성할 수 없다면, Oracle은 일부 해시 버킷을 디스크의 임시 테이블스페이스(temp tablespace)로 분할하여 저장합니다. &lt;br /&gt;
## 이를 &#039;해시 스필(Hash Spill)&#039;이라고 부르며, 이 경우 I/O가 발생하여 성능이 저하됨.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인이 느려지는 원인 ====&lt;br /&gt;
* 해시 조인 성능이 느려지는 주요 원인 중 하나는 해시 버킷 때문입니다.&lt;br /&gt;
* 정확히 말하면, 해시 충돌이 심하게 발생하거나 해시 테이블이 메모리에 맞지 않아 디스크 I/O가 발생하는 경우입니다.&lt;br /&gt;
* 해시 버킷 자체는 효율성을 위한 도구지만, 비정상적인 상황에서는 오히려 성능 저하의 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
1. 해시 충돌 (Hash Collisions)&lt;br /&gt;
* 원리: 해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다.&lt;br /&gt;
* 문제점: 해시 충돌이 발생하면, 하나의 버킷에 여러 행이 연결 리스트 형태로 쌓이게 됩니다. 이 버킷을 탐색할 때, 원하는 데이터를 찾기 위해 연결된 모든 데이터를 순차적으로 비교해야 하므로, 탐색 시간이 O(1)에서 O(N)으로 늘어납니다.&lt;br /&gt;
* 결과: 조인 키 데이터 분포가 불균형하거나(데이터 편중), 해시 함수가 비효율적이면 해시 충돌이 자주 발생하여 성능이 크게 저하됩니다.&lt;br /&gt;
* (결론) 해시조인은 조인키로 사용되는 컬럼이 중복이 많을수록  성능이 좋지 않음&lt;br /&gt;
&lt;br /&gt;
* 파이썬으로 해시 충돌 구현 예제&lt;br /&gt;
** 고의로 해시 충돌을 유발하는 데이터를 사용하여 해시 조인의 성능 차이를 보여줍니다. &lt;br /&gt;
** products_collision 리스트의 모든 product_id는 해시 함수를 거치면 같은 값을 반환하도록 설계되었습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터 (해시 충돌이 거의 없음)&lt;br /&gt;
products_normal = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 102, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 103, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 의도적으로 해시 충돌을 유발하는 데이터&lt;br /&gt;
# 101, 201, 301 모두 100으로 나눈 나머지가 1이 되도록 설계&lt;br /&gt;
products_collision = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 201, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 301, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인 대상 데이터 (각 product_id에 대한 sales 데이터)&lt;br /&gt;
sales = [{&#039;sale_id&#039;: i, &#039;product_id&#039;: 101, &#039;amount&#039;: 100} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+1000, &#039;product_id&#039;: 201, &#039;amount&#039;: 150} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+2000, &#039;product_id&#039;: 301, &#039;amount&#039;: 200} for i in range(1000)]&lt;br /&gt;
&lt;br /&gt;
# 사용자 정의 해시 함수 (단순화를 위해)&lt;br /&gt;
def custom_hash(key):&lt;br /&gt;
    return key % 100&lt;br /&gt;
&lt;br /&gt;
def hash_join(build_table, probe_table):&lt;br /&gt;
    # 빌드 단계: 해시 버킷(딕셔너리) 생성&lt;br /&gt;
    # 파이썬 딕셔너리의 키 충돌을 구현하기 위해, 딕셔너리의 값을 리스트로 저장&lt;br /&gt;
    hash_buckets = {}&lt;br /&gt;
    for item in build_table:&lt;br /&gt;
        key = item[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        if bucket_index not in hash_buckets:&lt;br /&gt;
            hash_buckets[bucket_index] = []&lt;br /&gt;
        hash_buckets[bucket_index].append(item)&lt;br /&gt;
&lt;br /&gt;
    # 프로브 단계: 조인&lt;br /&gt;
    start_time = time.time()&lt;br /&gt;
    joined_results = []&lt;br /&gt;
    for sale in probe_table:&lt;br /&gt;
        key = sale[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        &lt;br /&gt;
        # 해시 버킷에서 일치하는 데이터를 찾기 (충돌 발생 시 순차 탐색)&lt;br /&gt;
        if bucket_index in hash_buckets:&lt;br /&gt;
            for product_info in hash_buckets[bucket_index]:&lt;br /&gt;
                if product_info[&#039;product_id&#039;] == key:&lt;br /&gt;
                    joined_results.append({**sale, **product_info})&lt;br /&gt;
                    break&lt;br /&gt;
    end_time = time.time()&lt;br /&gt;
    return end_time - start_time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터로 조인&lt;br /&gt;
normal_time = hash_join(products_normal, sales)&lt;br /&gt;
&lt;br /&gt;
# 해시 충돌 데이터로 조인&lt;br /&gt;
collision_time = hash_join(products_collision, sales)&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;일반 데이터 조인 시간: {normal_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;해시 충돌 조인 시간: {collision_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;성능 저하 비율: {(collision_time / normal_time):.2f}배 느림&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. 메모리 부족 및 디스크 I/O&lt;br /&gt;
* 원리: 해시 조인은 더 작은 테이블(빌드 테이블)을 메모리에 올려 해시 테이블을 만드는 것이 기본 전제입니다.&lt;br /&gt;
* 문제점: 빌드 테이블이 너무 커서 **메모리(PGA)**에 다 담을 수 없게 되면, Oracle은 해시 테이블을 여러 파티션으로 나누어 일부를 **임시 테이블스페이스(Temporary Tablespace)**에 저장합니다.&lt;br /&gt;
* 결과: 조인을 수행하기 위해 디스크에 기록된 파티션 데이터를 읽고 쓰는 디스크 I/O 작업이 반복적으로 발생합니다. 이는 메모리에서만 작업할 때보다 훨씬 느리므로 성능 저하의 결정적인 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
* 해시 조인이 효율적으로 작동하는 것은 해시 버킷이 잘 분산되어 있고, 해시 테이블이 메모리에서 모두 처리될 때입니다. 만약 데이터가 한쪽으로 치우쳐 해시 충돌이 심하거나, 메모리 부족으로 디스크 I/O가 발생하면 해시 조인의 장점이 사라져 성능이 느려지게 됩니다.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 장/단점 ===&lt;br /&gt;
==== 장점 ====&lt;br /&gt;
# 대용량 테이블간 &amp;quot;=&amp;quot; 조건의 조인에 적합(정렬 불필요,입력 순서 무관)&lt;br /&gt;
# 빠른 접근 가능(버킷을 이용한 빠른 처리)&lt;br /&gt;
# 병렬처리에 적합하면 효율성이 좋음&lt;br /&gt;
&lt;br /&gt;
==== 단점 ==== &lt;br /&gt;
# &amp;quot;=&amp;quot; 조건에만 적합, 범위 조건(between 이나 like) 조인은 사용할수 없음.&lt;br /&gt;
# 메모리에 의존적임(빌드테이블이 메모리내에 들어가지 못하면 템프 i/o가 발생)&lt;br /&gt;
# 조인키값이 한쪽에 치우치거나(skew),중복도가 높으면 특정 버킷에 연결된 버킷이 길어져 탐색하는 시간이 증가함(CPU사용량 급증, 충돌증가)&lt;br /&gt;
# 버킷수가 데이터 대비 부족하면 충돌 발생과 체인 길이가 늘어 탐색 비용이 증가함  &lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1562</id>
		<title>HASH 조인</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=HASH_%EC%A1%B0%EC%9D%B8&amp;diff=1562"/>
		<updated>2025-10-21T00:40:12Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* Hash 버킷의 역할과 기능 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해시 조인(Hash Join) ==&lt;br /&gt;
{{틀:서브&lt;br /&gt;
|제목=&#039;&#039;&#039;&amp;lt;big&amp;gt;해시조인시 사용되는 해시(hash)함수는 입력된 데이터를 &#039;잘게 다져서&#039; 고유하고 일정한 크기의 식별자로 만들어 냄.&amp;lt;/big&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
* 해시의 주요특징&lt;br /&gt;
** 단방향성 : 해시값을 원래의 값으로 되돌릴수 없다.&lt;br /&gt;
** 고유성 : 입력값이 바뀌면 해시값은 완전히 달라진다.&lt;br /&gt;
** 고정된길이 : 입력값의 크기와 상관없이 항상 동일한 길이의 출력값을 가진다.&lt;br /&gt;
|내용=* hash의 영문의 뜻 : &#039;&#039;&#039;잘게 썰다, 다지다의 의미(요리에서 재료를 잘게 썰어서 섞을때 쓰이는말, 예로 맥도날도 &#039;해시브라운(감자를 다져서 튀긴요리)&#039;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
# 해시 조인(Hash Join)은 두 개의 테이블을 조인할 때, 더 작은 테이블을 메모리에 올리고 해시 테이블을 만들어 효율적으로 조인하는 방식&lt;br /&gt;
# 대용량 테이블을 조인할 때 주로 사용되며, 조인 조건에 인덱스가 없는 경우에도 빠르게 작동&lt;br /&gt;
&lt;br /&gt;
=== 해시 조인의 원리 ===&lt;br /&gt;
* 해시 조인은 크게 두 단계로 나뉩니다.&lt;br /&gt;
# 빌드 단계 (Build Phase):&lt;br /&gt;
## 옵티마이저는 두 테이블 중 더 작은 테이블(빌드 테이블)을 선택&lt;br /&gt;
## 이 테이블의 조인 키(컬럼)를 사용하여 PGA 메모리 영역에 해시 테이블을 생성. 각 조인 키 값은 해시 함수를 통하여 해시값으로 변환되어 해시 테이블의 특정 위치에 저장.&lt;br /&gt;
## 해시테이블 저장시 변환된 해시값들은 해시버킷의 갯수 만큼 쪼개서 할당되고 &amp;quot;버킷&amp;quot;에 행을 연결리스트(체인) 형태로 저장됨.&lt;br /&gt;
## 각 버킷은 &amp;quot;해당 해시 범위에 속하는 행들의 포인터(체인 헤드)&amp;quot; 역활을 하며 , 동일 해시값 이나 충돌이 발생하면 같은 버킷내에서 체인으로 연결됨.(많이 연결될수록 성능 저하,이구간이 성능저하 구간) &lt;br /&gt;
# 프로브 단계 (Probe Phase):&lt;br /&gt;
## 더 큰 테이블(프로브 테이블)을 순차적으로 읽어옮&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 테이블에서 일치하는 행을 찾음.(대상 버킷을 찾고 해당 버킷의 체인을 스캔하여 조인키를 비교)&lt;br /&gt;
## 일치하는 행을 찾으면, 두 테이블의 행을 결합하여 결과를 반환.&lt;br /&gt;
&lt;br /&gt;
* 만약 빌드 테이블이 PGA 메모리에 다 들어가지 않을 정도로 크다면, 테이블을 여러 개의 파티션으로 나누어 디스크에 임시로 저장한 뒤(성능 저하 구간), 각 파티션별로 빌드와 프로브 단계를 반복하는 &#039;그레이스풀 해시 조인(Graceful Hash Join)&#039;이 수행(성능저하 구간)&lt;br /&gt;
* 런타임 파티셔닝을 수행해 부분(배치/파티션) 단위로 템프에 내린 후(DISK I/O발생,성능저하) 다시 합쳐서 1-pass/멀티패스 방식으로 진행함.&lt;br /&gt;
&lt;br /&gt;
=== 파이썬 예제 ===&lt;br /&gt;
* 두 개의 CSV 파일 sales.csv와 products.csv가 있다고 가정하고, 이를 파이썬 딕셔너리로 해시 조인을 구현하는 예제.&lt;br /&gt;
1. 데이터 준비 (sales.csv, products.csv 파일)&lt;br /&gt;
&lt;br /&gt;
1) 대량 테이블 - 판매(sales.csv 파일)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sale_id,product_id,amount&lt;br /&gt;
1,101,100&lt;br /&gt;
2,102,150&lt;br /&gt;
3,101,200&lt;br /&gt;
4,103,50&lt;br /&gt;
....&lt;br /&gt;
....&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2) 제품 (products.csv)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
product_id,product_name&lt;br /&gt;
101,Laptop&lt;br /&gt;
102,Mouse&lt;br /&gt;
103,Keyboard&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. 해시 조인 구현 코드&lt;br /&gt;
&lt;br /&gt;
소량인 products 테이블을 빌드 테이블로, sales 테이블을 프로브 테이블로 사용하여 조인을 수행.&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import csv&lt;br /&gt;
&lt;br /&gt;
# 1. 빌드 테이블: products.csv를 읽어 메모리 해시 테이블(딕셔너리) 생성&lt;br /&gt;
# &#039;product_id&#039;를 키로, &#039;product_name&#039;을 값으로 저장&lt;br /&gt;
products_hash_table = {}&lt;br /&gt;
with open(&#039;products.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # 해시 테이블 생성 (딕셔너리)&lt;br /&gt;
        products_hash_table[row[&#039;product_id&#039;]] = row[&#039;product_name&#039;]&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;--- 빌드 단계 (해시 테이블) ---&amp;quot;)&lt;br /&gt;
print(products_hash_table)&lt;br /&gt;
# 출력: {&#039;101&#039;: &#039;Laptop&#039;, &#039;102&#039;: &#039;Mouse&#039;, &#039;103&#039;: &#039;Keyboard&#039;}&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;\n--- 프로브 단계 (조인 결과) ---&amp;quot;)&lt;br /&gt;
# 2. 프로브 테이블: sales.csv를 읽어 해시 테이블에서 찾기&lt;br /&gt;
with open(&#039;sales.csv&#039;, &#039;r&#039;) as f:&lt;br /&gt;
    reader = csv.DictReader(f)&lt;br /&gt;
    for row in reader:&lt;br /&gt;
        # sale_id 3의 &#039;product_id&#039;인 &#039;101&#039;을 해시 테이블에서 찾음&lt;br /&gt;
        product_id = row[&#039;product_id&#039;]&lt;br /&gt;
        if product_id in products_hash_table:&lt;br /&gt;
            # 해시 테이블에서 &#039;product_id&#039;를 키로 검색하여 &#039;product_name&#039;을 가져옴&lt;br /&gt;
            product_name = products_hash_table[product_id]&lt;br /&gt;
            print(f&amp;quot;Sale ID: {row[&#039;sale_id&#039;]}, Product Name: {product_name}, Amount: {row[&#039;amount&#039;]}&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 예상 출력:&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# Sale ID: 1, Product Name: Laptop, Amount: 100&lt;br /&gt;
# Sale ID: 2, Product Name: Mouse, Amount: 150&lt;br /&gt;
# Sale ID: 3, Product Name: Laptop, Amount: 200&lt;br /&gt;
# Sale ID: 4, Product Name: Keyboard, Amount: 50&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# products_hash_table이라는 딕셔너리가 바로 해시 테이블 역할을 합니다. &lt;br /&gt;
# sales 테이블의 각 행을 읽을 때마다 product_id를 딕셔너리(해시 테이블)에서 빠르게 찾아 product_name을 가져와 조인된 결과를 출력하는 방식&lt;br /&gt;
# 이를 통해 전체 products 테이블을 다시 스캔할 필요 없이 O(1)에 가까운 시간 복잡도로 조인 조건을 만족하는 데이터를 찾을 수 있음.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 성능 향상 원리 ===&lt;br /&gt;
==== Hash 버킷의 역할과 기능 ====&lt;br /&gt;
:::* 해시 버킷은 두 테이블을 효율적으로 조인하기 위한 핵심적인 논리적 저장 공간. &lt;br /&gt;
:::* 이는 조인 키(Join Key)를 기반으로 데이터를 빠르게 찾고 저장하는 데 사용.&lt;br /&gt;
&lt;br /&gt;
===== 역할: 데이터의 논리적 분류 및 저장소 =====&lt;br /&gt;
* 해시 버킷은 조인 키 값을 해시 함수에 넣어 얻은 해시 값이 같은 데이터들을 모아두는 공간.&lt;br /&gt;
* 이는 거대한 데이터를 조인 키를 기준으로 더 작은 그룹으로 나누어 관리하는 역할을 합니다.&lt;br /&gt;
* 이를 통해 빌드 테이블의 모든 데이터를 일일이 탐색하지 않고도, 특정 조인 키에 해당하는 데이터가 어느 버킷에 있는지 바로 알 수 있습니다.&lt;br /&gt;
&lt;br /&gt;
===== 기능: Build 단계와 Probe 단계에서의 활용 =====&lt;br /&gt;
# Build 단계:&lt;br /&gt;
## Oracle은 먼저 두 테이블 중 더 작은 테이블을 선택하여 빌드 테이블로 지정합니다.&lt;br /&gt;
## 빌드 테이블의 각 행을 읽어 조인 키에 해시 함수를 적용하여 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값에 해당하는 버킷에 해당 행의 데이터를 저장합니다. 만약 같은 버킷에 이미 다른 데이터가 있다면, 이를 연결 리스트(Linked List) 형태로 연결하여 **해시 충돌(Hash Collision)**을 처리합니다.&lt;br /&gt;
# Probe 단계:&lt;br /&gt;
## 이제 더 큰 테이블인 프로브 테이블을 순차적으로 읽습니다.&lt;br /&gt;
## 프로브 테이블의 각 행에서 조인 키를 추출하여, 빌드 단계에서 사용한 동일한 해시 함수로 해시 값을 계산합니다.&lt;br /&gt;
## 계산된 해시 값을 이용해 빌드 테이블의 해시 버킷 위치를 즉시 찾아갑니다.&lt;br /&gt;
## 해당 버킷 안의 연결 리스트를 탐색하여 조인 키가 일치하는 행을 찾습니다.&lt;br /&gt;
## 일치하는 행이 발견되면 두 테이블의 데이터를 결합하여 최종 결과를 생성합니다.&lt;br /&gt;
&lt;br /&gt;
* 이러한 해시 버킷의 활용 덕분에, 해시 조인은 테이블의 크기와 상관없이 조인 키가 같은 데이터들을 O(1)에 가까운 시간 복잡도로 찾아낼 수 있어 대용량 데이터 조인에 매우 효과적입니다.&lt;br /&gt;
&lt;br /&gt;
==== 해시 버킷 개수의 결정 ====&lt;br /&gt;
* 해시 조인의 최대 해시 버킷 개수는 사전 설정된 고정값이 아니라, 조인하려는 테이블의 크기, 옵티마이저의 예측, 그리고 PGA_AGGREGATE_TARGET 파라미터에 의해 동적으로 결정&lt;br /&gt;
&lt;br /&gt;
# 빌드 테이블(Build Table)의 크기: 옵티마이저가 예측한 더 작은 테이블(빌드 테이블)의 크기에 따라 필요한 해시 버킷의 수가 결정됨&lt;br /&gt;
# 할당된 PGA 메모리: PGA_AGGREGATE_TARGET에 의해 세션에 할당된 PGA 메모리 내에서 해시 테이블이 생성됨.&lt;br /&gt;
# 메모리 부족 시: 만약 할당된 PGA 메모리만으로 해시 테이블을 모두 생성할 수 없다면, Oracle은 일부 해시 버킷을 디스크의 임시 테이블스페이스(temp tablespace)로 분할하여 저장합니다. &lt;br /&gt;
## 이를 &#039;해시 스필(Hash Spill)&#039;이라고 부르며, 이 경우 I/O가 발생하여 성능이 저하됨.&lt;br /&gt;
&lt;br /&gt;
==== HASH 조인이 느려지는 원인 ====&lt;br /&gt;
* 해시 조인 성능이 느려지는 주요 원인 중 하나는 해시 버킷 때문입니다.&lt;br /&gt;
* 정확히 말하면, 해시 충돌이 심하게 발생하거나 해시 테이블이 메모리에 맞지 않아 디스크 I/O가 발생하는 경우입니다.&lt;br /&gt;
* 해시 버킷 자체는 효율성을 위한 도구지만, 비정상적인 상황에서는 오히려 성능 저하의 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
1. 해시 충돌 (Hash Collisions)&lt;br /&gt;
* 원리: 해시 함수는 조인 키가 다르더라도 동일한 해시 값을 반환할 수 있습니다. 이것이 해시 충돌입니다.&lt;br /&gt;
* 문제점: 해시 충돌이 발생하면, 하나의 버킷에 여러 행이 연결 리스트 형태로 쌓이게 됩니다. 이 버킷을 탐색할 때, 원하는 데이터를 찾기 위해 연결된 모든 데이터를 순차적으로 비교해야 하므로, 탐색 시간이 O(1)에서 O(N)으로 늘어납니다.&lt;br /&gt;
* 결과: 조인 키 데이터 분포가 불균형하거나(데이터 편중), 해시 함수가 비효율적이면 해시 충돌이 자주 발생하여 성능이 크게 저하됩니다.&lt;br /&gt;
* (결론) 해시조인은 조인키로 사용되는 컬럼이 중복이 많을수록  성능이 좋지 않음&lt;br /&gt;
&lt;br /&gt;
* 파이썬으로 해시 충돌 구현 예제&lt;br /&gt;
** 고의로 해시 충돌을 유발하는 데이터를 사용하여 해시 조인의 성능 차이를 보여줍니다. &lt;br /&gt;
** products_collision 리스트의 모든 product_id는 해시 함수를 거치면 같은 값을 반환하도록 설계되었습니다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터 (해시 충돌이 거의 없음)&lt;br /&gt;
products_normal = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 102, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 103, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 의도적으로 해시 충돌을 유발하는 데이터&lt;br /&gt;
# 101, 201, 301 모두 100으로 나눈 나머지가 1이 되도록 설계&lt;br /&gt;
products_collision = [&lt;br /&gt;
    {&#039;product_id&#039;: 101, &#039;product_name&#039;: &#039;Laptop&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 201, &#039;product_name&#039;: &#039;Mouse&#039;},&lt;br /&gt;
    {&#039;product_id&#039;: 301, &#039;product_name&#039;: &#039;Keyboard&#039;},&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
# 조인 대상 데이터 (각 product_id에 대한 sales 데이터)&lt;br /&gt;
sales = [{&#039;sale_id&#039;: i, &#039;product_id&#039;: 101, &#039;amount&#039;: 100} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+1000, &#039;product_id&#039;: 201, &#039;amount&#039;: 150} for i in range(1000)] + \&lt;br /&gt;
        [{&#039;sale_id&#039;: i+2000, &#039;product_id&#039;: 301, &#039;amount&#039;: 200} for i in range(1000)]&lt;br /&gt;
&lt;br /&gt;
# 사용자 정의 해시 함수 (단순화를 위해)&lt;br /&gt;
def custom_hash(key):&lt;br /&gt;
    return key % 100&lt;br /&gt;
&lt;br /&gt;
def hash_join(build_table, probe_table):&lt;br /&gt;
    # 빌드 단계: 해시 버킷(딕셔너리) 생성&lt;br /&gt;
    # 파이썬 딕셔너리의 키 충돌을 구현하기 위해, 딕셔너리의 값을 리스트로 저장&lt;br /&gt;
    hash_buckets = {}&lt;br /&gt;
    for item in build_table:&lt;br /&gt;
        key = item[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        if bucket_index not in hash_buckets:&lt;br /&gt;
            hash_buckets[bucket_index] = []&lt;br /&gt;
        hash_buckets[bucket_index].append(item)&lt;br /&gt;
&lt;br /&gt;
    # 프로브 단계: 조인&lt;br /&gt;
    start_time = time.time()&lt;br /&gt;
    joined_results = []&lt;br /&gt;
    for sale in probe_table:&lt;br /&gt;
        key = sale[&#039;product_id&#039;]&lt;br /&gt;
        bucket_index = custom_hash(key)&lt;br /&gt;
        &lt;br /&gt;
        # 해시 버킷에서 일치하는 데이터를 찾기 (충돌 발생 시 순차 탐색)&lt;br /&gt;
        if bucket_index in hash_buckets:&lt;br /&gt;
            for product_info in hash_buckets[bucket_index]:&lt;br /&gt;
                if product_info[&#039;product_id&#039;] == key:&lt;br /&gt;
                    joined_results.append({**sale, **product_info})&lt;br /&gt;
                    break&lt;br /&gt;
    end_time = time.time()&lt;br /&gt;
    return end_time - start_time&lt;br /&gt;
&lt;br /&gt;
# 일반적인 데이터로 조인&lt;br /&gt;
normal_time = hash_join(products_normal, sales)&lt;br /&gt;
&lt;br /&gt;
# 해시 충돌 데이터로 조인&lt;br /&gt;
collision_time = hash_join(products_collision, sales)&lt;br /&gt;
&lt;br /&gt;
print(f&amp;quot;일반 데이터 조인 시간: {normal_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;해시 충돌 조인 시간: {collision_time:.6f} 초&amp;quot;)&lt;br /&gt;
print(f&amp;quot;성능 저하 비율: {(collision_time / normal_time):.2f}배 느림&amp;quot;)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. 메모리 부족 및 디스크 I/O&lt;br /&gt;
* 원리: 해시 조인은 더 작은 테이블(빌드 테이블)을 메모리에 올려 해시 테이블을 만드는 것이 기본 전제입니다.&lt;br /&gt;
* 문제점: 빌드 테이블이 너무 커서 **메모리(PGA)**에 다 담을 수 없게 되면, Oracle은 해시 테이블을 여러 파티션으로 나누어 일부를 **임시 테이블스페이스(Temporary Tablespace)**에 저장합니다.&lt;br /&gt;
* 결과: 조인을 수행하기 위해 디스크에 기록된 파티션 데이터를 읽고 쓰는 디스크 I/O 작업이 반복적으로 발생합니다. 이는 메모리에서만 작업할 때보다 훨씬 느리므로 성능 저하의 결정적인 원인이 됩니다.&lt;br /&gt;
&lt;br /&gt;
==== 결론 ====&lt;br /&gt;
* 해시 조인이 효율적으로 작동하는 것은 해시 버킷이 잘 분산되어 있고, 해시 테이블이 메모리에서 모두 처리될 때입니다. 만약 데이터가 한쪽으로 치우쳐 해시 충돌이 심하거나, 메모리 부족으로 디스크 I/O가 발생하면 해시 조인의 장점이 사라져 성능이 느려지게 됩니다.&lt;br /&gt;
&lt;br /&gt;
=== HASH 조인의 장/단점 ===&lt;br /&gt;
==== 장점 ====&lt;br /&gt;
# 대용량 테이블간 &amp;quot;=&amp;quot; 조건의 조인에 적합(정렬 불필요,입력 순서 무관)&lt;br /&gt;
# 빠른 접근 가능(버킷을 이용한 빠른 처리)&lt;br /&gt;
# 병렬처리에 적합하면 효율성이 좋음&lt;br /&gt;
&lt;br /&gt;
==== 단점 ==== &lt;br /&gt;
# &amp;quot;=&amp;quot; 조건에만 적합, 범위 조건(between 이나 like) 조인은 사용할수 없음.&lt;br /&gt;
# 메모리에 의존적임(빌드테이블이 메모리내에 들어가지 못하면 템프 i/o가 발생)&lt;br /&gt;
# 조인키값이 한쪽에 치우치거나(skew),중복도가 높으면 특정 버킷에 연결된 버킷이 길어져 탐색하는 시간이 증가함(CPU사용량 급증, 충돌증가)&lt;br /&gt;
# 버킷수가 데이터 대비 부족하면 충돌 발생과 체인 길이가 늘어 탐색 비용이 증가함  &lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=1561</id>
		<title>미디어위키:Common.css</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=1561"/>
		<updated>2025-10-05T21:26:10Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* 이 CSS 설정은 모든 스킨에 적용됩니다 */&lt;br /&gt;
/* 나눔고딕 미설치 컴에도 나눔고딕을 사용할 수 있게 함 구글 폰트에서 퍼옴 */&lt;br /&gt;
@font-face {&lt;br /&gt;
  font-family: &#039;Nanum Gothic&#039;;&lt;br /&gt;
  font-style: normal;&lt;br /&gt;
  font-weight: 400;&lt;br /&gt;
  src: url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-Regular.eot);&lt;br /&gt;
  src: url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-Regular.eot?#iefix) format(&#039;embedded-opentype&#039;),&lt;br /&gt;
       url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-Regular.woff2) format(&#039;woff2&#039;),&lt;br /&gt;
       url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-Regular.woff) format(&#039;woff&#039;),&lt;br /&gt;
       url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-Regular.ttf) format(&#039;truetype&#039;);&lt;br /&gt;
}&lt;br /&gt;
@font-face {&lt;br /&gt;
  font-family: &#039;Nanum Gothic&#039;;&lt;br /&gt;
  font-style: normal;&lt;br /&gt;
  font-weight: 700;&lt;br /&gt;
  src: url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-Bold.eot);&lt;br /&gt;
  src: url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-Bold.eot?#iefix) format(&#039;embedded-opentype&#039;),&lt;br /&gt;
       url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-Bold.woff2) format(&#039;woff2&#039;),&lt;br /&gt;
       url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-Bold.woff) format(&#039;woff&#039;),&lt;br /&gt;
       url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-Bold.ttf) format(&#039;truetype&#039;);&lt;br /&gt;
}&lt;br /&gt;
@font-face {&lt;br /&gt;
  font-family: &#039;Nanum Gothic&#039;;&lt;br /&gt;
  font-style: normal;&lt;br /&gt;
  font-weight: 800;&lt;br /&gt;
  src: url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-ExtraBold.eot);&lt;br /&gt;
  src: url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-ExtraBold.eot?#iefix) format(&#039;embedded-opentype&#039;),&lt;br /&gt;
       url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-ExtraBold.woff2) format(&#039;woff2&#039;),&lt;br /&gt;
       url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-ExtraBold.woff) format(&#039;woff&#039;),&lt;br /&gt;
       url(http://fonts.gstatic.com/ea/nanumgothic/v5/NanumGothic-ExtraBold.ttf) format(&#039;truetype&#039;);&lt;br /&gt;
}&lt;br /&gt;
* { font-family: &#039;Nanum Gothic&#039;, sans-serif; }/*모든 글꼴을 나눔고딕으로. */&lt;br /&gt;
&lt;br /&gt;
body.page-대문 h1.firstHeading {display:none;}&lt;br /&gt;
ol { &lt;br /&gt;
	margin-left: 2cm;&lt;br /&gt;
list-style-type: decimal; &lt;br /&gt;
}&lt;br /&gt;
img {&lt;br /&gt;
          display: block;&lt;br /&gt;
          margin: 0 auto;&lt;br /&gt;
          max-width: 100%;&lt;br /&gt;
    }&lt;br /&gt;
      &lt;br /&gt;
/* .tocnumber:after { content: &#039;.&#039; }*/&lt;br /&gt;
&lt;br /&gt;
/* 반응형 그리드 레이아웃 */&lt;br /&gt;
.responsive-grid {&lt;br /&gt;
    display: grid;&lt;br /&gt;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));&lt;br /&gt;
    gap: 20px;&lt;br /&gt;
    padding: 20px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 반응형 이미지 */&lt;br /&gt;
.responsive-image {&lt;br /&gt;
        display: block;&lt;br /&gt;
        margin: 0 auto;&lt;br /&gt;
        max-width: 100%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.responsive-img {&lt;br /&gt;
    width: 100%; /* 부모 요소의 너비에 맞게 조정 */&lt;br /&gt;
    height: 600px; /* 원하는 높이 설정 */&lt;br /&gt;
    background-size: cover; /* 이미지가 div를 채우도록 조정 */&lt;br /&gt;
    background-position: center; /* 이미지의 중심을 기준으로 위치 조정 */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 모바일 환경에서의 텍스트 정렬 */&lt;br /&gt;
@media (max-width: 768px) {&lt;br /&gt;
    .main-page-intro {&lt;br /&gt;
        text-align: center;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 데스크탑에서는 그리드 레이아웃 사용 */&lt;br /&gt;
@media (min-width: 769px) {&lt;br /&gt;
    .main-page-intro {&lt;br /&gt;
        display: flex;&lt;br /&gt;
        justify-content: space-between;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 목차(TOC)에 넘버링 추가 */&lt;br /&gt;
#toc ul {&lt;br /&gt;
  counter-reset: item;&lt;br /&gt;
}&lt;br /&gt;
#toc li {&lt;br /&gt;
  display: block;&lt;br /&gt;
}&lt;br /&gt;
#toc li:before {&lt;br /&gt;
  content: counters(item, &amp;quot;.&amp;quot;) &amp;quot;. &amp;quot;;&lt;br /&gt;
  counter-increment: item;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 문서 섹션 제목(h2~h6)에 넘버링 추가 */&lt;br /&gt;
.mw-headline {&lt;br /&gt;
  counter-reset: subsection;&lt;br /&gt;
}&lt;br /&gt;
h2 .mw-headline:before {&lt;br /&gt;
  counter-increment: section;&lt;br /&gt;
  content: counter(section) &amp;quot;. &amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
h3 .mw-headline:before {&lt;br /&gt;
  counter-increment: subsection;&lt;br /&gt;
  content: counter(section) &amp;quot;.&amp;quot; counter(subsection) &amp;quot; &amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 섹션 제목 들여쓰기 */&lt;br /&gt;
.mw-parser-output h2 {&lt;br /&gt;
    margin-left: 0em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output h3 {&lt;br /&gt;
    margin-left: 2em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output h4 {&lt;br /&gt;
    margin-left: 4em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output h5 {&lt;br /&gt;
    margin-left: 6em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output h6 {&lt;br /&gt;
    margin-left: 8em;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EC%98%A4%EB%9D%BC%ED%81%B4_%EC%BB%AC%EB%9F%BC%EC%A0%80%EC%9E%A5_%EB%B0%A9%EC%8B%9D_%EA%B0%9C%EC%84%A0_(12c_%EC%97%85%EA%B7%B8%EB%A0%88%EC%9D%B4%EB%93%9C)&amp;diff=1548</id>
		<title>오라클 컬럼저장 방식 개선 (12c 업그레이드)</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EC%98%A4%EB%9D%BC%ED%81%B4_%EC%BB%AC%EB%9F%BC%EC%A0%80%EC%9E%A5_%EB%B0%A9%EC%8B%9D_%EA%B0%9C%EC%84%A0_(12c_%EC%97%85%EA%B7%B8%EB%A0%88%EC%9D%B4%EB%93%9C)&amp;diff=1548"/>
		<updated>2025-09-30T05:05:22Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==컬럼 default not null 변경시 컬럼저장 방식 개선==&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목= 오라클 12c이후 부터는 not null 컬럼 추가시 물리적인 저장을 하지 않고 메터정보를 참고 하도록 변경되었다.&lt;br /&gt;
:::: - 12c부터는 NOT NULL 여부와 관계없이 DEFAULT 값이 있는 컬럼 추가 시 메타데이터만 저장되며, 실제 데이터가 UPDATE될 때 비로소 물리적으로 저장됩니다.&lt;br /&gt;
|내용= 오라클 공식 레퍼런스 내용 참조 &lt;br /&gt;
:::# Oracle 12c에서는 NULL을 허용하는 컬럼의 기본값이 데이터 딕셔너리에 유지되며, DEFAULT 값을 가진 새 컬럼을 추가할 때 더 이상 기존의 모든 레코드에 기본값을 저장할 필요가 없습니다. 이를 통해 스키마 수정이 몇 초 안에 완료되고 기존 데이터 볼륨과 무관하며 공간도 소비하지 않습니다. Oracle Database 12c Release 1 (12.1.0.1) New Features&lt;br /&gt;
:::# ALTER TABLE 컬럼에 DEFAULT 절을 지정하면 기본값이 메타데이터로 저장되지만 컬럼 자체는 데이터로 채워지지 않습니다. 그러나 새 컬럼을 지정하는 후속 쿼리는 결과 세트에 기본값이 반환되도록 재작성됩니다.&lt;br /&gt;
:::#  Oracle 11g에서는 메타데이터 전용 기본값 개념을 도입했습니다. DEFAULT 절이 있는 NOT NULL 컬럼을 기존 테이블에 추가하는 것이 테이블의 모든 행을 변경하는 대신 메타데이터 변경만 수반했습니다. 새 컬럼에 대한 쿼리는 옵티마이저에 의해 재작성되어 결과가 기본값 정의와 일치하도록 보장했습니다. Oracle 12c는 이를 한 단계 더 발전시켜 필수 컬럼과 선택적 컬럼 모두에 대한 메타데이터 전용 기본값을 허용합니다. 따라서 DEFAULT 절이 있는 새 컬럼을 기존 테이블에 추가하는 것은 해당 컬럼이 NOT NULL로 정의되었는지 여부에 관계없이 메타데이터 전용 변경으로 처리됩니다. 이는 공간 절약과 성능 향상을 모두 &lt;br /&gt;
:::# 내부 동작 방식&lt;br /&gt;
:::#: - 11g에서는 Oracle이 내부적으로 NVL 표현식으로 컬럼을 재작성합니다. 12c에서는 NULL을 허용하는 컬럼의 경우 더 복잡한 표현식인 DECODE와 SYS_OP_VECBIT 함수를 사용하여 처리합니다. &lt;br /&gt;
:::# 제약 사항&lt;br /&gt;
:::#: - 인덱스 구성 테이블(iot), 임시 테이블, 클러스터의 일부는 불가하며 , 큐 테이블, 객체 테이블 또는 구체화된 뷰의 컨테이너 테이블도 불가함.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== default 컬럼 블럭 확인 및 테스트 ===&lt;br /&gt;
* 컬럼의 블럭을 확인 하는 절차는 다음과 같다&lt;br /&gt;
# 테스트 테이블 생성 &lt;br /&gt;
# default not null 컬럼 추가 (TEST_YN)&lt;br /&gt;
# 블럭 트레이스 및 덤프 파일에서 결과 확인&lt;br /&gt;
# 신규 컬럼 추가 (ADD1_YN) : 컬럼이 추가 되었때 블럭의 변화를 확인 하기 위함&lt;br /&gt;
# 블럭 트레이스 및 결과 확인 (실제 데이터가 있는지 확인)&lt;br /&gt;
&lt;br /&gt;
=== 테이블 물리적 블럭 내용  === &lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 1. 테이블 생성 &lt;br /&gt;
CREATE TABLE COL_NOTNULL_CHK_TEST(REG_NO VARCHAR2(1 BYTE) NOT NULL);&lt;br /&gt;
&lt;br /&gt;
-- 2. 테스트를 위해 기존 테이블에서 데이터 10개 복사(개별로 추가해도 됨)&lt;br /&gt;
CREATE TABLE COL_NOTNULL_CHK_TEST&lt;br /&gt;
AS&lt;br /&gt;
SELECT REG_NO FROM COL_NOTNULL_CHK -- 더미테이블임(사용자에게 없을수 있음.)&lt;br /&gt;
WHERE ROWNUM &amp;lt;= 10&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 컬럼의 물리적 파일-블럭 구조 조회&lt;br /&gt;
SELECT DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) AS FILE_ID -- 파일 번호&lt;br /&gt;
     , DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS BLOCK_ID -- 블럭 번호&lt;br /&gt;
     , DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)  AS ROW_NUM -- 로우번호 &lt;br /&gt;
FROM COL_NOTNULL_CHK_TEST&lt;br /&gt;
WHERE REG_NO=1003976357&lt;br /&gt;
;&lt;br /&gt;
-- [결과] &lt;br /&gt;
--FILE_ID, BLOCK_ID, ROW_NUM&lt;br /&gt;
     15   935475      0&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프 수행 &lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP&#039;; -- 덤프파일 식별자 지정&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
-- 덤프파일 트레이스 파일 경로&lt;br /&gt;
select trace_filename,to_char(modify_time,&#039;YYYY-MM-DD HH24:MI:SS&#039;) as modified&lt;br /&gt;
       , filesize&lt;br /&gt;
  from v$diag_trace_file&lt;br /&gt;
 where trace_filename like &#039;%ora_%&#039;&lt;br /&gt;
 order by modify_time DESC;&lt;br /&gt;
&lt;br /&gt;
-- .trc 파일 확인 &lt;br /&gt;
Dump of buffer cache at level 3 for pdb# tsn=14 rdba=03850b15&lt;br /&gt;
BH (0x42f4d8ca8) file# 15 rdba: 0x03cce633 (15/935475) class: 1 ba: 0x42eea8000&lt;br /&gt;
....&lt;br /&gt;
block_row_dump:&lt;br /&gt;
tab 0, row 0, @0x1f72&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38&lt;br /&gt;
end_of_block_dump&lt;br /&gt;
;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 디폴트 not null 컬럼 추가 ===&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [2] TEST_YN CHAR(1) 컬럼 추가 NOT NULL DEFAULT &#039;N&#039;&lt;br /&gt;
ALTER TABLE COL_NOTNULL_CHK_TEST ADD TEST_YN CHAR(1) DEFAULT &#039;N&#039; NOT NULL;&lt;br /&gt;
&lt;br /&gt;
-- [1]번째 ROW UPDATE - 데이터가 실제 저장되는지 확인 &lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST &lt;br /&gt;
   SET TEST_YN=&#039;Y&#039;&lt;br /&gt;
WHERE REG_NO=1006036461;&lt;br /&gt;
&lt;br /&gt;
SELECT * FROM DBAKM.COL_NOTNULL_CHK_TEST&lt;br /&gt;
-- WHERE REG_NO=1006036404&lt;br /&gt;
;&lt;br /&gt;
-- REG_NO, TEST_YN&lt;br /&gt;
--1006036404,N&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP2&#039;;&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
-- 트레이스 파일 경로&lt;br /&gt;
select trace_filename,to_char(modify_time,&#039;YYYY-MM-DD HH24:MI:SS&#039;) as modified&lt;br /&gt;
       , filesize&lt;br /&gt;
  from v$diag_trace_file&lt;br /&gt;
 where trace_filename like &#039;%cra_%&#039;&lt;br /&gt;
 order by modify_time DESC;&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- .trc파일에서 FILE_ID = 15 ,BLOCK_ID=935475 , ROW_NUM=1 인 로우를 찾기 (1번째 UPDATE 치고 덤프 조회 )&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
tab 0, row 0, @0x1e63        &amp;lt;== 1번째 ROW (REG_NO=1006036404) ,&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x2  cc: 3  &amp;lt;== tl 컬럼 행길이 : 17바이트 ,  fb행플그 :  Head-piece,First row piece,Last row piece / lb : lock byte :0x02(로우락&lt;br /&gt;
킹) // cc:3  컬럼갯수(column count: 3개 )&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38     &amp;lt;== hex값:  1006036404&lt;br /&gt;
col  1: *NULL*                      &amp;lt;== 이건 무슨 컬럼인가??? (아래 참조 설명) &lt;br /&gt;
col  2: [ 1]  59                    &amp;lt;== 아스키코드 0x59 는 &#039;Y&#039;&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
tab 0, row 1, @0x1f64&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37   &amp;lt;=&lt;br /&gt;
.....&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 이후 2번째 row의 업데이트를 실시   &amp;lt;== REG_NO=1001976357&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [2번째 ROW UPDATE - 데이터가 실제 저장되는지 확인 ]&lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST &lt;br /&gt;
   SET TEST_YN=&#039;Y&#039;&lt;br /&gt;
WHERE REG_NO=1001976357;&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
-- [REG_NO=1006036404]&lt;br /&gt;
tab 0, row 0, @0x1e63&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x2  cc: 3&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38 39 34&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59    &amp;lt;== 1번째 ROW UPDATE한 값&lt;br /&gt;
&lt;br /&gt;
-- [REG_NO=1001976357] &amp;lt;== 2번째 업데이트 처리&lt;br /&gt;
tab 0, row 1, @0x1e02&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x3  cc: 3&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59     &amp;lt;== 2번째 ROW UPDATE한 값&lt;br /&gt;
&lt;br /&gt;
-- [REG_NO=*** ] 3번째 row , 2번째 컬럼값이 기본값이 저장됨.&lt;br /&gt;
tab 0, row 2, @0x1f56&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 31 31 37 30 36 35 36 30 38&lt;br /&gt;
-- [REG_NO=*** ] 3번째 row , 2번째 컬럼값이 기본값이 저장됨.&lt;br /&gt;
tab 0, row 3, @0x1f48&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 36 34 32 35 38 37 39 34&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* [참고] ===&amp;gt; 여기에서 cc가 컬럼갯수인데 현재 컬럼을 2개 추가했는데 cc:3인 이유?&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
select column_name,data_type,hidden_column,virtual_column&lt;br /&gt;
  from dba_tab_cols&lt;br /&gt;
 where table_name=&#039;COL_NOTNULL_CHK_TEST&#039;;&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
REG_NO        VARCHAR2       NO        NO&lt;br /&gt;
SYS_C00002_25092615-44:55 CHAR      YES       NO  &amp;lt;== 기존에 존재하던 컬럼이 삭제하거나 가상컬럼이 저장된것임.&lt;br /&gt;
TEST_YN              CHAR           NO        NO&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 테스트 결과 ===&lt;br /&gt;
{{요점&lt;br /&gt;
|내용= * 오라클 12C 부터는 not null 컬럼이 추가될 경우 실제 데이터가 저장되지 않고 메타정보만 저장 된다.&lt;br /&gt;
* 단, 데이터가 입력되거나 변경시 컬럼이 실제 데이터가 기록된다.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== 추가 의문 사항 ===&lt;br /&gt;
# 신규로 컬럼의 추가 하면 블럭의 변화가 발생 될까?&lt;br /&gt;
#: -- (결론) 다른 컬럼이 추가/변경 되더라도 기존 컬럼구조는 변경되지 않는다.&lt;br /&gt;
#: -- (데이터에 값이 존재하지 않는 상태 그대로 존재함.TEST_YN 은 default &#039;N&#039; not null  이지만 실제 데이터블럭에는 값이 저장되어 있지 않음.)&lt;br /&gt;
#: -- 다른 컬럼[ADD1_YN] 추가로 업데이트 되더라도  TEST_YN 컬럼은 변경되지 않는다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [추가 확장 검증 ]&lt;br /&gt;
ALTER TABLE COL_NOTNULL_CHK_TEST ADD ADD1_YN CHAR(1) DEFAULT &#039;N&#039; NOT NULL;&lt;br /&gt;
SELECT * FROM COL_NOTNULL_CHK_TEST;&lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST SET ADD1_YN=&#039;C&#039;;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
&lt;br /&gt;
-- 컬럼의 물리적 구조&lt;br /&gt;
SELECT DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) AS FILE_ID&lt;br /&gt;
     , DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS BLOCK_ID&lt;br /&gt;
     , DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)  AS ROW_NUM&lt;br /&gt;
  FROM COL_NOTNULL_CHK_TEST&lt;br /&gt;
 WHERE REG_NO=1006036404&lt;br /&gt;
;&lt;br /&gt;
--FILE_ID, BLOCK_ID, ROW_NUM&lt;br /&gt;
15       935475     0&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP2&#039;;&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
tab 0, row 0, @0x1e0f&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 39 34&lt;br /&gt;
col  2: [ 1]  59   &amp;lt;== 1번째 &lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 1, @0x1eac&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59   &amp;lt;== 2번째 &lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 2, @0x1e9a&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 31 31 37 30 36 35 33 39 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       -- &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 3, @0x1e88&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 36 34 32 35 38 37 39 34 37 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       --  &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 4, @0x1e76&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 31 39 37 36 35 33 39 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 5, @0x1e64&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 30 33 39 36 35 39 39 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       --  &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 6, @0x1e52&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 37 39 36 37 39 37 34 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 7, @0x1e40&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 35 33 37 39 32 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 8, @0x1e2e&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 32 34 36 31 31 37 39&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 9, @0x1e1c&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 32 30 39 37 31 35 32&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
end_of_block_dump&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
{{결론&lt;br /&gt;
|내용= 테스트한 결과 오라클 12c부터는 NOT NULL DEFAULT 컬럼 추가 시 실제 데이터를 저장하지 않고 메타데이터만 저장하며, 데이터가 UPDATE될 때 비로소 물리적으로 저장된다는 점입니다.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[category:oracle]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EC%98%A4%EB%9D%BC%ED%81%B4_%EC%BB%AC%EB%9F%BC%EC%A0%80%EC%9E%A5_%EB%B0%A9%EC%8B%9D_%EA%B0%9C%EC%84%A0_(12c_%EC%97%85%EA%B7%B8%EB%A0%88%EC%9D%B4%EB%93%9C)&amp;diff=1547</id>
		<title>오라클 컬럼저장 방식 개선 (12c 업그레이드)</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EC%98%A4%EB%9D%BC%ED%81%B4_%EC%BB%AC%EB%9F%BC%EC%A0%80%EC%9E%A5_%EB%B0%A9%EC%8B%9D_%EA%B0%9C%EC%84%A0_(12c_%EC%97%85%EA%B7%B8%EB%A0%88%EC%9D%B4%EB%93%9C)&amp;diff=1547"/>
		<updated>2025-09-30T05:03:23Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 추가 의문 사항 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==컬럼 default not null 변경시 컬럼저장 방식 개선==&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목= 오라클 12c이후 부터는 not null 컬럼 추가시 물리적인 저장을 하지 않고 메터정보를 참고 하도록 변경되었다.&lt;br /&gt;
:::: - 12c부터는 NOT NULL 여부와 관계없이 DEFAULT 값이 있는 컬럼 추가 시 메타데이터만 저장되며, 실제 데이터가 UPDATE될 때 비로소 물리적으로 저장됩니다.&lt;br /&gt;
|내용= 오라클 공식 레퍼런스 내용 참조 &lt;br /&gt;
:::# Oracle 12c에서는 NULL을 허용하는 컬럼의 기본값이 데이터 딕셔너리에 유지되며, DEFAULT 값을 가진 새 컬럼을 추가할 때 더 이상 기존의 모든 레코드에 기본값을 저장할 필요가 없습니다. 이를 통해 스키마 수정이 몇 초 안에 완료되고 기존 데이터 볼륨과 무관하며 공간도 소비하지 않습니다. Oracle Database 12c Release 1 (12.1.0.1) New Features&lt;br /&gt;
:::# ALTER TABLE 컬럼에 DEFAULT 절을 지정하면 기본값이 메타데이터로 저장되지만 컬럼 자체는 데이터로 채워지지 않습니다. 그러나 새 컬럼을 지정하는 후속 쿼리는 결과 세트에 기본값이 반환되도록 재작성됩니다.&lt;br /&gt;
:::#  Oracle 11g에서는 메타데이터 전용 기본값 개념을 도입했습니다. DEFAULT 절이 있는 NOT NULL 컬럼을 기존 테이블에 추가하는 것이 테이블의 모든 행을 변경하는 대신 메타데이터 변경만 수반했습니다. 새 컬럼에 대한 쿼리는 옵티마이저에 의해 재작성되어 결과가 기본값 정의와 일치하도록 보장했습니다. Oracle 12c는 이를 한 단계 더 발전시켜 필수 컬럼과 선택적 컬럼 모두에 대한 메타데이터 전용 기본값을 허용합니다. 따라서 DEFAULT 절이 있는 새 컬럼을 기존 테이블에 추가하는 것은 해당 컬럼이 NOT NULL로 정의되었는지 여부에 관계없이 메타데이터 전용 변경으로 처리됩니다. 이는 공간 절약과 성능 향상을 모두 &lt;br /&gt;
:::# 내부 동작 방식&lt;br /&gt;
:::#: - 11g에서는 Oracle이 내부적으로 NVL 표현식으로 컬럼을 재작성합니다. 12c에서는 NULL을 허용하는 컬럼의 경우 더 복잡한 표현식인 DECODE와 SYS_OP_VECBIT 함수를 사용하여 처리합니다. &lt;br /&gt;
:::# 제약 사항&lt;br /&gt;
:::#: - 인덱스 구성 테이블(iot), 임시 테이블, 클러스터의 일부는 불가하며 , 큐 테이블, 객체 테이블 또는 구체화된 뷰의 컨테이너 테이블도 불가함.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== default 컬럼 블럭 확인 및 테스트 ===&lt;br /&gt;
* 컬럼의 블럭을 확인 하는 절차는 다음과 같다&lt;br /&gt;
# 테스트 테이블 생성 &lt;br /&gt;
# default not null 컬럼 추가 (TEST_YN)&lt;br /&gt;
# 블럭 트레이스 및 덤프 파일에서 결과 확인&lt;br /&gt;
# 신규 컬럼 추가 (ADD1_YN) : 컬럼이 추가 되었때 블럭의 변화를 확인 하기 위함&lt;br /&gt;
# 블럭 트레이스 및 결과 확인 (실제 데이터가 있는지 확인)&lt;br /&gt;
&lt;br /&gt;
=== 테이블 물리적 블럭 내용  === &lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 1. 테이블 생성 &lt;br /&gt;
CREATE TABLE COL_NOTNULL_CHK_TEST(REG_NO VARCHAR2(1 BYTE) NOT NULL);&lt;br /&gt;
&lt;br /&gt;
-- 2. 테스트를 위해 기존 테이블에서 데이터 10개 복사(개별로 추가해도 됨)&lt;br /&gt;
CREATE TABLE COL_NOTNULL_CHK_TEST&lt;br /&gt;
AS&lt;br /&gt;
SELECT REG_NO FROM COL_NOTNULL_CHK -- 더미테이블임(사용자에게 없을수 있음.)&lt;br /&gt;
WHERE ROWNUM &amp;lt;= 10&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 컬럼의 물리적 파일-블럭 구조 조회&lt;br /&gt;
SELECT DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) AS FILE_ID -- 파일 번호&lt;br /&gt;
     , DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS BLOCK_ID -- 블럭 번호&lt;br /&gt;
     , DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)  AS ROW_NUM -- 로우번호 &lt;br /&gt;
FROM COL_NOTNULL_CHK_TEST&lt;br /&gt;
WHERE REG_NO=1003976357&lt;br /&gt;
;&lt;br /&gt;
-- [결과] &lt;br /&gt;
--FILE_ID, BLOCK_ID, ROW_NUM&lt;br /&gt;
     15   935475      0&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프 수행 &lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP&#039;; -- 덤프파일 식별자 지정&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
-- 덤프파일 트레이스 파일 경로&lt;br /&gt;
select trace_filename,to_char(modify_time,&#039;YYYY-MM-DD HH24:MI:SS&#039;) as modified&lt;br /&gt;
       , filesize&lt;br /&gt;
  from v$diag_trace_file&lt;br /&gt;
 where trace_filename like &#039;%ora_%&#039;&lt;br /&gt;
 order by modify_time DESC;&lt;br /&gt;
&lt;br /&gt;
-- .trc 파일 확인 &lt;br /&gt;
Dump of buffer cache at level 3 for pdb# tsn=14 rdba=03850b15&lt;br /&gt;
BH (0x42f4d8ca8) file# 15 rdba: 0x03cce633 (15/935475) class: 1 ba: 0x42eea8000&lt;br /&gt;
....&lt;br /&gt;
block_row_dump:&lt;br /&gt;
tab 0, row 0, @0x1f72&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38&lt;br /&gt;
end_of_block_dump&lt;br /&gt;
;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 디폴트 not null 컬럼 추가 ===&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [2] TEST_YN CHAR(1) 컬럼 추가 NOT NULL DEFAULT &#039;N&#039;&lt;br /&gt;
ALTER TABLE COL_NOTNULL_CHK_TEST ADD TEST_YN CHAR(1) DEFAULT &#039;N&#039; NOT NULL;&lt;br /&gt;
&lt;br /&gt;
-- [1]번째 ROW UPDATE - 데이터가 실제 저장되는지 확인 &lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST &lt;br /&gt;
   SET TEST_YN=&#039;Y&#039;&lt;br /&gt;
WHERE REG_NO=1006036461;&lt;br /&gt;
&lt;br /&gt;
SELECT * FROM DBAKM.COL_NOTNULL_CHK_TEST&lt;br /&gt;
-- WHERE REG_NO=1006036404&lt;br /&gt;
;&lt;br /&gt;
-- REG_NO, TEST_YN&lt;br /&gt;
--1006036404,N&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP2&#039;;&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
-- 트레이스 파일 경로&lt;br /&gt;
select trace_filename,to_char(modify_time,&#039;YYYY-MM-DD HH24:MI:SS&#039;) as modified&lt;br /&gt;
       , filesize&lt;br /&gt;
  from v$diag_trace_file&lt;br /&gt;
 where trace_filename like &#039;%cra_%&#039;&lt;br /&gt;
 order by modify_time DESC;&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- .trc파일에서 FILE_ID = 15 ,BLOCK_ID=935475 , ROW_NUM=1 인 로우를 찾기 (1번째 UPDATE 치고 덤프 조회 )&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
tab 0, row 0, @0x1e63        &amp;lt;== 1번째 ROW (REG_NO=1006036404) ,&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x2  cc: 3  &amp;lt;== tl 컬럼 행길이 : 17바이트 ,  fb행플그 :  Head-piece,First row piece,Last row piece / lb : lock byte :0x02(로우락&lt;br /&gt;
킹) // cc:3  컬럼갯수(column count: 3개 )&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38     &amp;lt;== hex값:  1006036404&lt;br /&gt;
col  1: *NULL*                      &amp;lt;== 이건 무슨 컬럼인가??? (아래 참조 설명) &lt;br /&gt;
col  2: [ 1]  59                    &amp;lt;== 아스키코드 0x59 는 &#039;Y&#039;&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
tab 0, row 1, @0x1f64&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37   &amp;lt;=&lt;br /&gt;
.....&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 이후 2번째 row의 업데이트를 실시   &amp;lt;== REG_NO=1001976357&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [2번째 ROW UPDATE - 데이터가 실제 저장되는지 확인 ]&lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST &lt;br /&gt;
   SET TEST_YN=&#039;Y&#039;&lt;br /&gt;
WHERE REG_NO=1001976357;&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
-- [REG_NO=1006036404]&lt;br /&gt;
tab 0, row 0, @0x1e63&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x2  cc: 3&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38 39 34&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59    &amp;lt;== 1번째 ROW UPDATE한 값&lt;br /&gt;
&lt;br /&gt;
-- [REG_NO=1001976357] &amp;lt;== 2번째 업데이트 처리&lt;br /&gt;
tab 0, row 1, @0x1e02&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x3  cc: 3&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59     &amp;lt;== 2번째 ROW UPDATE한 값&lt;br /&gt;
&lt;br /&gt;
-- [REG_NO=*** ] 3번째 row , 2번째 컬럼값이 기본값이 저장됨.&lt;br /&gt;
tab 0, row 2, @0x1f56&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 31 31 37 30 36 35 36 30 38&lt;br /&gt;
-- [REG_NO=*** ] 3번째 row , 2번째 컬럼값이 기본값이 저장됨.&lt;br /&gt;
tab 0, row 3, @0x1f48&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 36 34 32 35 38 37 39 34&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* [참고] ===&amp;gt; 여기에서 cc가 컬럼갯수인데 현재 컬럼을 2개 추가했는데 cc:3인 이유?&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
select column_name,data_type,hidden_column,virtual_column&lt;br /&gt;
  from dba_tab_cols&lt;br /&gt;
 where table_name=&#039;COL_NOTNULL_CHK_TEST&#039;;&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
REG_NO        VARCHAR2       NO        NO&lt;br /&gt;
SYS_C00002_25092615-44:55 CHAR      YES       NO  &amp;lt;== 기존에 존재하던 컬럼이 삭제하거나 가상컬럼이 저장된것임.&lt;br /&gt;
TEST_YN              CHAR           NO        NO&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 테스트 결과 ===&lt;br /&gt;
{{요점&lt;br /&gt;
|내용= * 오라클 12C 부터는 not null 컬럼이 추가될 경우 실제 데이터가 저장되지 않고 메타정보만 저장 된다.&lt;br /&gt;
* 단, 데이터가 입력되거나 변경시 컬럼이 실제 데이터가 기록된다.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== 추가 의문 사항 ===&lt;br /&gt;
# 신규로 컬럼의 추가 하면 블럭의 변화가 발생 될까?&lt;br /&gt;
#: -- (결론) 다른 컬럼이 추가/변경 되더라도 기존 컬럼구조는 변경되지 않는다.&lt;br /&gt;
#: -- (데이터에 값이 존재하지 않는 상태 그대로 존재함.TEST_YN 은 default &#039;N&#039; not null  이지만 실제 데이터블럭에는 값이 저장되어 있지 않음.)&lt;br /&gt;
#: -- 다른 컬럼[ADD1_YN] 추가로 업데이트 되더라도  TEST_YN 컬럼은 변경되지 않는다.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [추가 확장 검증 ]&lt;br /&gt;
ALTER TABLE COL_NOTNULL_CHK_TEST ADD ADD1_YN CHAR(1) DEFAULT &#039;N&#039; NOT NULL;&lt;br /&gt;
SELECT * FROM COL_NOTNULL_CHK_TEST;&lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST SET ADD1_YN=&#039;C&#039;;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
&lt;br /&gt;
-- 컬럼의 물리적 구조&lt;br /&gt;
SELECT DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) AS FILE_ID&lt;br /&gt;
     , DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS BLOCK_ID&lt;br /&gt;
     , DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)  AS ROW_NUM&lt;br /&gt;
  FROM DBAKM.COL_NOTNULL_CHK_TEST&lt;br /&gt;
 WHERE AGRNEN_REG_NO=1006036404&lt;br /&gt;
;&lt;br /&gt;
--FILE_ID, BLOCK_ID, ROW_NUM&lt;br /&gt;
15       935475     0&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP2&#039;;&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
tab 0, row 0, @0x1e0f&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 39 34&lt;br /&gt;
col  2: [ 1]  59   &amp;lt;== 1번째 &lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 1, @0x1eac&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59   &amp;lt;== 2번째 &lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 2, @0x1e9a&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 31 31 37 30 36 35 33 39 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       -- &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 3, @0x1e88&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 36 34 32 35 38 37 39 34 37 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       --  &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 4, @0x1e76&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 31 39 37 36 35 33 39 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 5, @0x1e64&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 30 33 39 36 35 39 39 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       --  &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 6, @0x1e52&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 37 39 36 37 39 37 34 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 7, @0x1e40&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 35 33 37 39 32 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 8, @0x1e2e&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 32 34 36 31 31 37 39&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 9, @0x1e1c&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 32 30 39 37 31 35 32&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
end_of_block_dump&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
{{결론&lt;br /&gt;
|내용= 테스트한 결과 오라클 12c부터는 NOT NULL DEFAULT 컬럼 추가 시 실제 데이터를 저장하지 않고 메타데이터만 저장하며, 데이터가 UPDATE될 때 비로소 물리적으로 저장된다는 점입니다.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[category:oracle]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EC%98%A4%EB%9D%BC%ED%81%B4_%EC%BB%AC%EB%9F%BC%EC%A0%80%EC%9E%A5_%EB%B0%A9%EC%8B%9D_%EA%B0%9C%EC%84%A0_(12c_%EC%97%85%EA%B7%B8%EB%A0%88%EC%9D%B4%EB%93%9C)&amp;diff=1546</id>
		<title>오라클 컬럼저장 방식 개선 (12c 업그레이드)</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EC%98%A4%EB%9D%BC%ED%81%B4_%EC%BB%AC%EB%9F%BC%EC%A0%80%EC%9E%A5_%EB%B0%A9%EC%8B%9D_%EA%B0%9C%EC%84%A0_(12c_%EC%97%85%EA%B7%B8%EB%A0%88%EC%9D%B4%EB%93%9C)&amp;diff=1546"/>
		<updated>2025-09-30T05:02:24Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 테스트 결과 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==컬럼 default not null 변경시 컬럼저장 방식 개선==&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목= 오라클 12c이후 부터는 not null 컬럼 추가시 물리적인 저장을 하지 않고 메터정보를 참고 하도록 변경되었다.&lt;br /&gt;
:::: - 12c부터는 NOT NULL 여부와 관계없이 DEFAULT 값이 있는 컬럼 추가 시 메타데이터만 저장되며, 실제 데이터가 UPDATE될 때 비로소 물리적으로 저장됩니다.&lt;br /&gt;
|내용= 오라클 공식 레퍼런스 내용 참조 &lt;br /&gt;
:::# Oracle 12c에서는 NULL을 허용하는 컬럼의 기본값이 데이터 딕셔너리에 유지되며, DEFAULT 값을 가진 새 컬럼을 추가할 때 더 이상 기존의 모든 레코드에 기본값을 저장할 필요가 없습니다. 이를 통해 스키마 수정이 몇 초 안에 완료되고 기존 데이터 볼륨과 무관하며 공간도 소비하지 않습니다. Oracle Database 12c Release 1 (12.1.0.1) New Features&lt;br /&gt;
:::# ALTER TABLE 컬럼에 DEFAULT 절을 지정하면 기본값이 메타데이터로 저장되지만 컬럼 자체는 데이터로 채워지지 않습니다. 그러나 새 컬럼을 지정하는 후속 쿼리는 결과 세트에 기본값이 반환되도록 재작성됩니다.&lt;br /&gt;
:::#  Oracle 11g에서는 메타데이터 전용 기본값 개념을 도입했습니다. DEFAULT 절이 있는 NOT NULL 컬럼을 기존 테이블에 추가하는 것이 테이블의 모든 행을 변경하는 대신 메타데이터 변경만 수반했습니다. 새 컬럼에 대한 쿼리는 옵티마이저에 의해 재작성되어 결과가 기본값 정의와 일치하도록 보장했습니다. Oracle 12c는 이를 한 단계 더 발전시켜 필수 컬럼과 선택적 컬럼 모두에 대한 메타데이터 전용 기본값을 허용합니다. 따라서 DEFAULT 절이 있는 새 컬럼을 기존 테이블에 추가하는 것은 해당 컬럼이 NOT NULL로 정의되었는지 여부에 관계없이 메타데이터 전용 변경으로 처리됩니다. 이는 공간 절약과 성능 향상을 모두 &lt;br /&gt;
:::# 내부 동작 방식&lt;br /&gt;
:::#: - 11g에서는 Oracle이 내부적으로 NVL 표현식으로 컬럼을 재작성합니다. 12c에서는 NULL을 허용하는 컬럼의 경우 더 복잡한 표현식인 DECODE와 SYS_OP_VECBIT 함수를 사용하여 처리합니다. &lt;br /&gt;
:::# 제약 사항&lt;br /&gt;
:::#: - 인덱스 구성 테이블(iot), 임시 테이블, 클러스터의 일부는 불가하며 , 큐 테이블, 객체 테이블 또는 구체화된 뷰의 컨테이너 테이블도 불가함.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== default 컬럼 블럭 확인 및 테스트 ===&lt;br /&gt;
* 컬럼의 블럭을 확인 하는 절차는 다음과 같다&lt;br /&gt;
# 테스트 테이블 생성 &lt;br /&gt;
# default not null 컬럼 추가 (TEST_YN)&lt;br /&gt;
# 블럭 트레이스 및 덤프 파일에서 결과 확인&lt;br /&gt;
# 신규 컬럼 추가 (ADD1_YN) : 컬럼이 추가 되었때 블럭의 변화를 확인 하기 위함&lt;br /&gt;
# 블럭 트레이스 및 결과 확인 (실제 데이터가 있는지 확인)&lt;br /&gt;
&lt;br /&gt;
=== 테이블 물리적 블럭 내용  === &lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 1. 테이블 생성 &lt;br /&gt;
CREATE TABLE COL_NOTNULL_CHK_TEST(REG_NO VARCHAR2(1 BYTE) NOT NULL);&lt;br /&gt;
&lt;br /&gt;
-- 2. 테스트를 위해 기존 테이블에서 데이터 10개 복사(개별로 추가해도 됨)&lt;br /&gt;
CREATE TABLE COL_NOTNULL_CHK_TEST&lt;br /&gt;
AS&lt;br /&gt;
SELECT REG_NO FROM COL_NOTNULL_CHK -- 더미테이블임(사용자에게 없을수 있음.)&lt;br /&gt;
WHERE ROWNUM &amp;lt;= 10&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 컬럼의 물리적 파일-블럭 구조 조회&lt;br /&gt;
SELECT DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) AS FILE_ID -- 파일 번호&lt;br /&gt;
     , DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS BLOCK_ID -- 블럭 번호&lt;br /&gt;
     , DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)  AS ROW_NUM -- 로우번호 &lt;br /&gt;
FROM COL_NOTNULL_CHK_TEST&lt;br /&gt;
WHERE REG_NO=1003976357&lt;br /&gt;
;&lt;br /&gt;
-- [결과] &lt;br /&gt;
--FILE_ID, BLOCK_ID, ROW_NUM&lt;br /&gt;
     15   935475      0&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프 수행 &lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP&#039;; -- 덤프파일 식별자 지정&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
-- 덤프파일 트레이스 파일 경로&lt;br /&gt;
select trace_filename,to_char(modify_time,&#039;YYYY-MM-DD HH24:MI:SS&#039;) as modified&lt;br /&gt;
       , filesize&lt;br /&gt;
  from v$diag_trace_file&lt;br /&gt;
 where trace_filename like &#039;%ora_%&#039;&lt;br /&gt;
 order by modify_time DESC;&lt;br /&gt;
&lt;br /&gt;
-- .trc 파일 확인 &lt;br /&gt;
Dump of buffer cache at level 3 for pdb# tsn=14 rdba=03850b15&lt;br /&gt;
BH (0x42f4d8ca8) file# 15 rdba: 0x03cce633 (15/935475) class: 1 ba: 0x42eea8000&lt;br /&gt;
....&lt;br /&gt;
block_row_dump:&lt;br /&gt;
tab 0, row 0, @0x1f72&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38&lt;br /&gt;
end_of_block_dump&lt;br /&gt;
;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 디폴트 not null 컬럼 추가 ===&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [2] TEST_YN CHAR(1) 컬럼 추가 NOT NULL DEFAULT &#039;N&#039;&lt;br /&gt;
ALTER TABLE COL_NOTNULL_CHK_TEST ADD TEST_YN CHAR(1) DEFAULT &#039;N&#039; NOT NULL;&lt;br /&gt;
&lt;br /&gt;
-- [1]번째 ROW UPDATE - 데이터가 실제 저장되는지 확인 &lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST &lt;br /&gt;
   SET TEST_YN=&#039;Y&#039;&lt;br /&gt;
WHERE REG_NO=1006036461;&lt;br /&gt;
&lt;br /&gt;
SELECT * FROM DBAKM.COL_NOTNULL_CHK_TEST&lt;br /&gt;
-- WHERE REG_NO=1006036404&lt;br /&gt;
;&lt;br /&gt;
-- REG_NO, TEST_YN&lt;br /&gt;
--1006036404,N&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP2&#039;;&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
-- 트레이스 파일 경로&lt;br /&gt;
select trace_filename,to_char(modify_time,&#039;YYYY-MM-DD HH24:MI:SS&#039;) as modified&lt;br /&gt;
       , filesize&lt;br /&gt;
  from v$diag_trace_file&lt;br /&gt;
 where trace_filename like &#039;%cra_%&#039;&lt;br /&gt;
 order by modify_time DESC;&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- .trc파일에서 FILE_ID = 15 ,BLOCK_ID=935475 , ROW_NUM=1 인 로우를 찾기 (1번째 UPDATE 치고 덤프 조회 )&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
tab 0, row 0, @0x1e63        &amp;lt;== 1번째 ROW (REG_NO=1006036404) ,&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x2  cc: 3  &amp;lt;== tl 컬럼 행길이 : 17바이트 ,  fb행플그 :  Head-piece,First row piece,Last row piece / lb : lock byte :0x02(로우락&lt;br /&gt;
킹) // cc:3  컬럼갯수(column count: 3개 )&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38     &amp;lt;== hex값:  1006036404&lt;br /&gt;
col  1: *NULL*                      &amp;lt;== 이건 무슨 컬럼인가??? (아래 참조 설명) &lt;br /&gt;
col  2: [ 1]  59                    &amp;lt;== 아스키코드 0x59 는 &#039;Y&#039;&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
tab 0, row 1, @0x1f64&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37   &amp;lt;=&lt;br /&gt;
.....&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 이후 2번째 row의 업데이트를 실시   &amp;lt;== REG_NO=1001976357&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [2번째 ROW UPDATE - 데이터가 실제 저장되는지 확인 ]&lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST &lt;br /&gt;
   SET TEST_YN=&#039;Y&#039;&lt;br /&gt;
WHERE REG_NO=1001976357;&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
-- [REG_NO=1006036404]&lt;br /&gt;
tab 0, row 0, @0x1e63&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x2  cc: 3&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38 39 34&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59    &amp;lt;== 1번째 ROW UPDATE한 값&lt;br /&gt;
&lt;br /&gt;
-- [REG_NO=1001976357] &amp;lt;== 2번째 업데이트 처리&lt;br /&gt;
tab 0, row 1, @0x1e02&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x3  cc: 3&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59     &amp;lt;== 2번째 ROW UPDATE한 값&lt;br /&gt;
&lt;br /&gt;
-- [REG_NO=*** ] 3번째 row , 2번째 컬럼값이 기본값이 저장됨.&lt;br /&gt;
tab 0, row 2, @0x1f56&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 31 31 37 30 36 35 36 30 38&lt;br /&gt;
-- [REG_NO=*** ] 3번째 row , 2번째 컬럼값이 기본값이 저장됨.&lt;br /&gt;
tab 0, row 3, @0x1f48&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 36 34 32 35 38 37 39 34&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* [참고] ===&amp;gt; 여기에서 cc가 컬럼갯수인데 현재 컬럼을 2개 추가했는데 cc:3인 이유?&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
select column_name,data_type,hidden_column,virtual_column&lt;br /&gt;
  from dba_tab_cols&lt;br /&gt;
 where table_name=&#039;COL_NOTNULL_CHK_TEST&#039;;&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
REG_NO        VARCHAR2       NO        NO&lt;br /&gt;
SYS_C00002_25092615-44:55 CHAR      YES       NO  &amp;lt;== 기존에 존재하던 컬럼이 삭제하거나 가상컬럼이 저장된것임.&lt;br /&gt;
TEST_YN              CHAR           NO        NO&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 테스트 결과 ===&lt;br /&gt;
{{요점&lt;br /&gt;
|내용= * 오라클 12C 부터는 not null 컬럼이 추가될 경우 실제 데이터가 저장되지 않고 메타정보만 저장 된다.&lt;br /&gt;
* 단, 데이터가 입력되거나 변경시 컬럼이 실제 데이터가 기록된다.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== 추가 의문 사항 ===&lt;br /&gt;
# 신규로 컬럼의 추가 하면 블럭의 변화가 발생 될까?&lt;br /&gt;
#: -- (결론) 다른 컬럼이 추가/변경 되더라도 기존 컬럼구조는 변경되지 않는다.&lt;br /&gt;
#: -- (데이터에 값이 존재하지 않는 상태 그대로 존재함.TEST_YN 은 default &#039;N&#039; not null  이지만 실제 데이터블럭에는 값이 저장되어 있지 않음.)&lt;br /&gt;
#: -- 다른 컬럼[ADD1_YN] 추가로 업데이트 되더라도  TEST_YN 컬럼은 변경되지 않는다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [추가 확장 검증 ]&lt;br /&gt;
ALTER TABLE COL_NOTNULL_CHK_TEST ADD ADD1_YN CHAR(1) DEFAULT &#039;N&#039; NOT NULL;&lt;br /&gt;
SELECT * FROM COL_NOTNULL_CHK_TEST;&lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST SET ADD1_YN=&#039;C&#039;;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
&lt;br /&gt;
-- 컬럼의 물리적 구조&lt;br /&gt;
SELECT DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) AS FILE_ID&lt;br /&gt;
     , DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS BLOCK_ID&lt;br /&gt;
     , DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)  AS ROW_NUM&lt;br /&gt;
  FROM DBAKM.COL_NOTNULL_CHK_TEST&lt;br /&gt;
 WHERE AGRNEN_REG_NO=1006036404&lt;br /&gt;
;&lt;br /&gt;
--FILE_ID, BLOCK_ID, ROW_NUM&lt;br /&gt;
15       935475     0&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP2&#039;;&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
tab 0, row 0, @0x1e0f&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 39 34&lt;br /&gt;
col  2: [ 1]  59   &amp;lt;== 1번째 &lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 1, @0x1eac&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59   &amp;lt;== 2번째 &lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 2, @0x1e9a&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 31 31 37 30 36 35 33 39 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       -- &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 3, @0x1e88&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 36 34 32 35 38 37 39 34 37 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       --  &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 4, @0x1e76&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 31 39 37 36 35 33 39 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 5, @0x1e64&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 30 33 39 36 35 39 39 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       --  &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 6, @0x1e52&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 37 39 36 37 39 37 34 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 7, @0x1e40&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 35 33 37 39 32 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 8, @0x1e2e&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 32 34 36 31 31 37 39&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 9, @0x1e1c&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 32 30 39 37 31 35 32&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
end_of_block_dump&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
{{결론&lt;br /&gt;
|내용= 테스트한 결과 오라클 12c부터는 NOT NULL DEFAULT 컬럼 추가 시 실제 데이터를 저장하지 않고 메타데이터만 저장하며, 데이터가 UPDATE될 때 비로소 물리적으로 저장된다는 점입니다.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[category:oracle]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EC%98%A4%EB%9D%BC%ED%81%B4_%EC%BB%AC%EB%9F%BC%EC%A0%80%EC%9E%A5_%EB%B0%A9%EC%8B%9D_%EA%B0%9C%EC%84%A0_(12c_%EC%97%85%EA%B7%B8%EB%A0%88%EC%9D%B4%EB%93%9C)&amp;diff=1545</id>
		<title>오라클 컬럼저장 방식 개선 (12c 업그레이드)</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EC%98%A4%EB%9D%BC%ED%81%B4_%EC%BB%AC%EB%9F%BC%EC%A0%80%EC%9E%A5_%EB%B0%A9%EC%8B%9D_%EA%B0%9C%EC%84%A0_(12c_%EC%97%85%EA%B7%B8%EB%A0%88%EC%9D%B4%EB%93%9C)&amp;diff=1545"/>
		<updated>2025-09-30T05:01:42Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 디폴트 not null 컬럼 추가 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==컬럼 default not null 변경시 컬럼저장 방식 개선==&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목= 오라클 12c이후 부터는 not null 컬럼 추가시 물리적인 저장을 하지 않고 메터정보를 참고 하도록 변경되었다.&lt;br /&gt;
:::: - 12c부터는 NOT NULL 여부와 관계없이 DEFAULT 값이 있는 컬럼 추가 시 메타데이터만 저장되며, 실제 데이터가 UPDATE될 때 비로소 물리적으로 저장됩니다.&lt;br /&gt;
|내용= 오라클 공식 레퍼런스 내용 참조 &lt;br /&gt;
:::# Oracle 12c에서는 NULL을 허용하는 컬럼의 기본값이 데이터 딕셔너리에 유지되며, DEFAULT 값을 가진 새 컬럼을 추가할 때 더 이상 기존의 모든 레코드에 기본값을 저장할 필요가 없습니다. 이를 통해 스키마 수정이 몇 초 안에 완료되고 기존 데이터 볼륨과 무관하며 공간도 소비하지 않습니다. Oracle Database 12c Release 1 (12.1.0.1) New Features&lt;br /&gt;
:::# ALTER TABLE 컬럼에 DEFAULT 절을 지정하면 기본값이 메타데이터로 저장되지만 컬럼 자체는 데이터로 채워지지 않습니다. 그러나 새 컬럼을 지정하는 후속 쿼리는 결과 세트에 기본값이 반환되도록 재작성됩니다.&lt;br /&gt;
:::#  Oracle 11g에서는 메타데이터 전용 기본값 개념을 도입했습니다. DEFAULT 절이 있는 NOT NULL 컬럼을 기존 테이블에 추가하는 것이 테이블의 모든 행을 변경하는 대신 메타데이터 변경만 수반했습니다. 새 컬럼에 대한 쿼리는 옵티마이저에 의해 재작성되어 결과가 기본값 정의와 일치하도록 보장했습니다. Oracle 12c는 이를 한 단계 더 발전시켜 필수 컬럼과 선택적 컬럼 모두에 대한 메타데이터 전용 기본값을 허용합니다. 따라서 DEFAULT 절이 있는 새 컬럼을 기존 테이블에 추가하는 것은 해당 컬럼이 NOT NULL로 정의되었는지 여부에 관계없이 메타데이터 전용 변경으로 처리됩니다. 이는 공간 절약과 성능 향상을 모두 &lt;br /&gt;
:::# 내부 동작 방식&lt;br /&gt;
:::#: - 11g에서는 Oracle이 내부적으로 NVL 표현식으로 컬럼을 재작성합니다. 12c에서는 NULL을 허용하는 컬럼의 경우 더 복잡한 표현식인 DECODE와 SYS_OP_VECBIT 함수를 사용하여 처리합니다. &lt;br /&gt;
:::# 제약 사항&lt;br /&gt;
:::#: - 인덱스 구성 테이블(iot), 임시 테이블, 클러스터의 일부는 불가하며 , 큐 테이블, 객체 테이블 또는 구체화된 뷰의 컨테이너 테이블도 불가함.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== default 컬럼 블럭 확인 및 테스트 ===&lt;br /&gt;
* 컬럼의 블럭을 확인 하는 절차는 다음과 같다&lt;br /&gt;
# 테스트 테이블 생성 &lt;br /&gt;
# default not null 컬럼 추가 (TEST_YN)&lt;br /&gt;
# 블럭 트레이스 및 덤프 파일에서 결과 확인&lt;br /&gt;
# 신규 컬럼 추가 (ADD1_YN) : 컬럼이 추가 되었때 블럭의 변화를 확인 하기 위함&lt;br /&gt;
# 블럭 트레이스 및 결과 확인 (실제 데이터가 있는지 확인)&lt;br /&gt;
&lt;br /&gt;
=== 테이블 물리적 블럭 내용  === &lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 1. 테이블 생성 &lt;br /&gt;
CREATE TABLE COL_NOTNULL_CHK_TEST(REG_NO VARCHAR2(1 BYTE) NOT NULL);&lt;br /&gt;
&lt;br /&gt;
-- 2. 테스트를 위해 기존 테이블에서 데이터 10개 복사(개별로 추가해도 됨)&lt;br /&gt;
CREATE TABLE COL_NOTNULL_CHK_TEST&lt;br /&gt;
AS&lt;br /&gt;
SELECT REG_NO FROM COL_NOTNULL_CHK -- 더미테이블임(사용자에게 없을수 있음.)&lt;br /&gt;
WHERE ROWNUM &amp;lt;= 10&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 컬럼의 물리적 파일-블럭 구조 조회&lt;br /&gt;
SELECT DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) AS FILE_ID -- 파일 번호&lt;br /&gt;
     , DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS BLOCK_ID -- 블럭 번호&lt;br /&gt;
     , DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)  AS ROW_NUM -- 로우번호 &lt;br /&gt;
FROM COL_NOTNULL_CHK_TEST&lt;br /&gt;
WHERE REG_NO=1003976357&lt;br /&gt;
;&lt;br /&gt;
-- [결과] &lt;br /&gt;
--FILE_ID, BLOCK_ID, ROW_NUM&lt;br /&gt;
     15   935475      0&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프 수행 &lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP&#039;; -- 덤프파일 식별자 지정&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
-- 덤프파일 트레이스 파일 경로&lt;br /&gt;
select trace_filename,to_char(modify_time,&#039;YYYY-MM-DD HH24:MI:SS&#039;) as modified&lt;br /&gt;
       , filesize&lt;br /&gt;
  from v$diag_trace_file&lt;br /&gt;
 where trace_filename like &#039;%ora_%&#039;&lt;br /&gt;
 order by modify_time DESC;&lt;br /&gt;
&lt;br /&gt;
-- .trc 파일 확인 &lt;br /&gt;
Dump of buffer cache at level 3 for pdb# tsn=14 rdba=03850b15&lt;br /&gt;
BH (0x42f4d8ca8) file# 15 rdba: 0x03cce633 (15/935475) class: 1 ba: 0x42eea8000&lt;br /&gt;
....&lt;br /&gt;
block_row_dump:&lt;br /&gt;
tab 0, row 0, @0x1f72&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38&lt;br /&gt;
end_of_block_dump&lt;br /&gt;
;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 디폴트 not null 컬럼 추가 ===&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [2] TEST_YN CHAR(1) 컬럼 추가 NOT NULL DEFAULT &#039;N&#039;&lt;br /&gt;
ALTER TABLE COL_NOTNULL_CHK_TEST ADD TEST_YN CHAR(1) DEFAULT &#039;N&#039; NOT NULL;&lt;br /&gt;
&lt;br /&gt;
-- [1]번째 ROW UPDATE - 데이터가 실제 저장되는지 확인 &lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST &lt;br /&gt;
   SET TEST_YN=&#039;Y&#039;&lt;br /&gt;
WHERE REG_NO=1006036461;&lt;br /&gt;
&lt;br /&gt;
SELECT * FROM DBAKM.COL_NOTNULL_CHK_TEST&lt;br /&gt;
-- WHERE REG_NO=1006036404&lt;br /&gt;
;&lt;br /&gt;
-- REG_NO, TEST_YN&lt;br /&gt;
--1006036404,N&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP2&#039;;&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
-- 트레이스 파일 경로&lt;br /&gt;
select trace_filename,to_char(modify_time,&#039;YYYY-MM-DD HH24:MI:SS&#039;) as modified&lt;br /&gt;
       , filesize&lt;br /&gt;
  from v$diag_trace_file&lt;br /&gt;
 where trace_filename like &#039;%cra_%&#039;&lt;br /&gt;
 order by modify_time DESC;&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- .trc파일에서 FILE_ID = 15 ,BLOCK_ID=935475 , ROW_NUM=1 인 로우를 찾기 (1번째 UPDATE 치고 덤프 조회 )&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
tab 0, row 0, @0x1e63        &amp;lt;== 1번째 ROW (REG_NO=1006036404) ,&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x2  cc: 3  &amp;lt;== tl 컬럼 행길이 : 17바이트 ,  fb행플그 :  Head-piece,First row piece,Last row piece / lb : lock byte :0x02(로우락&lt;br /&gt;
킹) // cc:3  컬럼갯수(column count: 3개 )&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38     &amp;lt;== hex값:  1006036404&lt;br /&gt;
col  1: *NULL*                      &amp;lt;== 이건 무슨 컬럼인가??? (아래 참조 설명) &lt;br /&gt;
col  2: [ 1]  59                    &amp;lt;== 아스키코드 0x59 는 &#039;Y&#039;&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
tab 0, row 1, @0x1f64&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37   &amp;lt;=&lt;br /&gt;
.....&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 이후 2번째 row의 업데이트를 실시   &amp;lt;== REG_NO=1001976357&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [2번째 ROW UPDATE - 데이터가 실제 저장되는지 확인 ]&lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST &lt;br /&gt;
   SET TEST_YN=&#039;Y&#039;&lt;br /&gt;
WHERE REG_NO=1001976357;&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
-- [REG_NO=1006036404]&lt;br /&gt;
tab 0, row 0, @0x1e63&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x2  cc: 3&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38 39 34&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59    &amp;lt;== 1번째 ROW UPDATE한 값&lt;br /&gt;
&lt;br /&gt;
-- [REG_NO=1001976357] &amp;lt;== 2번째 업데이트 처리&lt;br /&gt;
tab 0, row 1, @0x1e02&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x3  cc: 3&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59     &amp;lt;== 2번째 ROW UPDATE한 값&lt;br /&gt;
&lt;br /&gt;
-- [REG_NO=*** ] 3번째 row , 2번째 컬럼값이 기본값이 저장됨.&lt;br /&gt;
tab 0, row 2, @0x1f56&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 31 31 37 30 36 35 36 30 38&lt;br /&gt;
-- [REG_NO=*** ] 3번째 row , 2번째 컬럼값이 기본값이 저장됨.&lt;br /&gt;
tab 0, row 3, @0x1f48&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 36 34 32 35 38 37 39 34&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* [참고] ===&amp;gt; 여기에서 cc가 컬럼갯수인데 현재 컬럼을 2개 추가했는데 cc:3인 이유?&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
select column_name,data_type,hidden_column,virtual_column&lt;br /&gt;
  from dba_tab_cols&lt;br /&gt;
 where table_name=&#039;COL_NOTNULL_CHK_TEST&#039;;&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
REG_NO        VARCHAR2       NO        NO&lt;br /&gt;
SYS_C00002_25092615-44:55 CHAR      YES       NO  &amp;lt;== 기존에 존재하던 컬럼이 삭제하거나 가상컬럼이 저장된것임.&lt;br /&gt;
TEST_YN              CHAR           NO        NO&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 테스트 결과 ===&lt;br /&gt;
{{요점&lt;br /&gt;
|내용= * 오라클 12C 부터는 not null 컬럼이 추가될 경우 실제 데이터가 저장되지 않고 메타정보만 저장 된다.&lt;br /&gt;
* -- 단, 데이터가 입력되거나 변경시 컬럼이 실제 데이터가 기록된다.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== 추가 의문 사항 ===&lt;br /&gt;
# 신규로 컬럼의 추가 하면 블럭의 변화가 발생 될까?&lt;br /&gt;
#: -- (결론) 다른 컬럼이 추가/변경 되더라도 기존 컬럼구조는 변경되지 않는다.&lt;br /&gt;
#: -- (데이터에 값이 존재하지 않는 상태 그대로 존재함.TEST_YN 은 default &#039;N&#039; not null  이지만 실제 데이터블럭에는 값이 저장되어 있지 않음.)&lt;br /&gt;
#: -- 다른 컬럼[ADD1_YN] 추가로 업데이트 되더라도  TEST_YN 컬럼은 변경되지 않는다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [추가 확장 검증 ]&lt;br /&gt;
ALTER TABLE COL_NOTNULL_CHK_TEST ADD ADD1_YN CHAR(1) DEFAULT &#039;N&#039; NOT NULL;&lt;br /&gt;
SELECT * FROM COL_NOTNULL_CHK_TEST;&lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST SET ADD1_YN=&#039;C&#039;;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
&lt;br /&gt;
-- 컬럼의 물리적 구조&lt;br /&gt;
SELECT DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) AS FILE_ID&lt;br /&gt;
     , DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS BLOCK_ID&lt;br /&gt;
     , DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)  AS ROW_NUM&lt;br /&gt;
  FROM DBAKM.COL_NOTNULL_CHK_TEST&lt;br /&gt;
 WHERE AGRNEN_REG_NO=1006036404&lt;br /&gt;
;&lt;br /&gt;
--FILE_ID, BLOCK_ID, ROW_NUM&lt;br /&gt;
15       935475     0&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP2&#039;;&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
tab 0, row 0, @0x1e0f&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 39 34&lt;br /&gt;
col  2: [ 1]  59   &amp;lt;== 1번째 &lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 1, @0x1eac&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59   &amp;lt;== 2번째 &lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 2, @0x1e9a&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 31 31 37 30 36 35 33 39 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       -- &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 3, @0x1e88&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 36 34 32 35 38 37 39 34 37 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       --  &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 4, @0x1e76&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 31 39 37 36 35 33 39 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 5, @0x1e64&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 30 33 39 36 35 39 39 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       --  &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 6, @0x1e52&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 37 39 36 37 39 37 34 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 7, @0x1e40&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 35 33 37 39 32 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 8, @0x1e2e&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 32 34 36 31 31 37 39&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 9, @0x1e1c&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 32 30 39 37 31 35 32&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
end_of_block_dump&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
{{결론&lt;br /&gt;
|내용= 테스트한 결과 오라클 12c부터는 NOT NULL DEFAULT 컬럼 추가 시 실제 데이터를 저장하지 않고 메타데이터만 저장하며, 데이터가 UPDATE될 때 비로소 물리적으로 저장된다는 점입니다.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[category:oracle]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
	<entry>
		<id>https://dbstudy.co.kr/w/index.php?title=%EC%98%A4%EB%9D%BC%ED%81%B4_%EC%BB%AC%EB%9F%BC%EC%A0%80%EC%9E%A5_%EB%B0%A9%EC%8B%9D_%EA%B0%9C%EC%84%A0_(12c_%EC%97%85%EA%B7%B8%EB%A0%88%EC%9D%B4%EB%93%9C)&amp;diff=1544</id>
		<title>오라클 컬럼저장 방식 개선 (12c 업그레이드)</title>
		<link rel="alternate" type="text/html" href="https://dbstudy.co.kr/w/index.php?title=%EC%98%A4%EB%9D%BC%ED%81%B4_%EC%BB%AC%EB%9F%BC%EC%A0%80%EC%9E%A5_%EB%B0%A9%EC%8B%9D_%EA%B0%9C%EC%84%A0_(12c_%EC%97%85%EA%B7%B8%EB%A0%88%EC%9D%B4%EB%93%9C)&amp;diff=1544"/>
		<updated>2025-09-30T04:59:42Z</updated>

		<summary type="html">&lt;p&gt;Dbstudy: /* 테이블 물리적 블럭 내용 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==컬럼 default not null 변경시 컬럼저장 방식 개선==&lt;br /&gt;
{{핵심&lt;br /&gt;
|제목= 오라클 12c이후 부터는 not null 컬럼 추가시 물리적인 저장을 하지 않고 메터정보를 참고 하도록 변경되었다.&lt;br /&gt;
:::: - 12c부터는 NOT NULL 여부와 관계없이 DEFAULT 값이 있는 컬럼 추가 시 메타데이터만 저장되며, 실제 데이터가 UPDATE될 때 비로소 물리적으로 저장됩니다.&lt;br /&gt;
|내용= 오라클 공식 레퍼런스 내용 참조 &lt;br /&gt;
:::# Oracle 12c에서는 NULL을 허용하는 컬럼의 기본값이 데이터 딕셔너리에 유지되며, DEFAULT 값을 가진 새 컬럼을 추가할 때 더 이상 기존의 모든 레코드에 기본값을 저장할 필요가 없습니다. 이를 통해 스키마 수정이 몇 초 안에 완료되고 기존 데이터 볼륨과 무관하며 공간도 소비하지 않습니다. Oracle Database 12c Release 1 (12.1.0.1) New Features&lt;br /&gt;
:::# ALTER TABLE 컬럼에 DEFAULT 절을 지정하면 기본값이 메타데이터로 저장되지만 컬럼 자체는 데이터로 채워지지 않습니다. 그러나 새 컬럼을 지정하는 후속 쿼리는 결과 세트에 기본값이 반환되도록 재작성됩니다.&lt;br /&gt;
:::#  Oracle 11g에서는 메타데이터 전용 기본값 개념을 도입했습니다. DEFAULT 절이 있는 NOT NULL 컬럼을 기존 테이블에 추가하는 것이 테이블의 모든 행을 변경하는 대신 메타데이터 변경만 수반했습니다. 새 컬럼에 대한 쿼리는 옵티마이저에 의해 재작성되어 결과가 기본값 정의와 일치하도록 보장했습니다. Oracle 12c는 이를 한 단계 더 발전시켜 필수 컬럼과 선택적 컬럼 모두에 대한 메타데이터 전용 기본값을 허용합니다. 따라서 DEFAULT 절이 있는 새 컬럼을 기존 테이블에 추가하는 것은 해당 컬럼이 NOT NULL로 정의되었는지 여부에 관계없이 메타데이터 전용 변경으로 처리됩니다. 이는 공간 절약과 성능 향상을 모두 &lt;br /&gt;
:::# 내부 동작 방식&lt;br /&gt;
:::#: - 11g에서는 Oracle이 내부적으로 NVL 표현식으로 컬럼을 재작성합니다. 12c에서는 NULL을 허용하는 컬럼의 경우 더 복잡한 표현식인 DECODE와 SYS_OP_VECBIT 함수를 사용하여 처리합니다. &lt;br /&gt;
:::# 제약 사항&lt;br /&gt;
:::#: - 인덱스 구성 테이블(iot), 임시 테이블, 클러스터의 일부는 불가하며 , 큐 테이블, 객체 테이블 또는 구체화된 뷰의 컨테이너 테이블도 불가함.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== default 컬럼 블럭 확인 및 테스트 ===&lt;br /&gt;
* 컬럼의 블럭을 확인 하는 절차는 다음과 같다&lt;br /&gt;
# 테스트 테이블 생성 &lt;br /&gt;
# default not null 컬럼 추가 (TEST_YN)&lt;br /&gt;
# 블럭 트레이스 및 덤프 파일에서 결과 확인&lt;br /&gt;
# 신규 컬럼 추가 (ADD1_YN) : 컬럼이 추가 되었때 블럭의 변화를 확인 하기 위함&lt;br /&gt;
# 블럭 트레이스 및 결과 확인 (실제 데이터가 있는지 확인)&lt;br /&gt;
&lt;br /&gt;
=== 테이블 물리적 블럭 내용  === &lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
-- 1. 테이블 생성 &lt;br /&gt;
CREATE TABLE COL_NOTNULL_CHK_TEST(REG_NO VARCHAR2(1 BYTE) NOT NULL);&lt;br /&gt;
&lt;br /&gt;
-- 2. 테스트를 위해 기존 테이블에서 데이터 10개 복사(개별로 추가해도 됨)&lt;br /&gt;
CREATE TABLE COL_NOTNULL_CHK_TEST&lt;br /&gt;
AS&lt;br /&gt;
SELECT REG_NO FROM COL_NOTNULL_CHK -- 더미테이블임(사용자에게 없을수 있음.)&lt;br /&gt;
WHERE ROWNUM &amp;lt;= 10&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 컬럼의 물리적 파일-블럭 구조 조회&lt;br /&gt;
SELECT DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) AS FILE_ID -- 파일 번호&lt;br /&gt;
     , DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS BLOCK_ID -- 블럭 번호&lt;br /&gt;
     , DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)  AS ROW_NUM -- 로우번호 &lt;br /&gt;
FROM COL_NOTNULL_CHK_TEST&lt;br /&gt;
WHERE REG_NO=1003976357&lt;br /&gt;
;&lt;br /&gt;
-- [결과] &lt;br /&gt;
--FILE_ID, BLOCK_ID, ROW_NUM&lt;br /&gt;
     15   935475      0&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프 수행 &lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP&#039;; -- 덤프파일 식별자 지정&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
-- 덤프파일 트레이스 파일 경로&lt;br /&gt;
select trace_filename,to_char(modify_time,&#039;YYYY-MM-DD HH24:MI:SS&#039;) as modified&lt;br /&gt;
       , filesize&lt;br /&gt;
  from v$diag_trace_file&lt;br /&gt;
 where trace_filename like &#039;%ora_%&#039;&lt;br /&gt;
 order by modify_time DESC;&lt;br /&gt;
&lt;br /&gt;
-- .trc 파일 확인 &lt;br /&gt;
Dump of buffer cache at level 3 for pdb# tsn=14 rdba=03850b15&lt;br /&gt;
BH (0x42f4d8ca8) file# 15 rdba: 0x03cce633 (15/935475) class: 1 ba: 0x42eea8000&lt;br /&gt;
....&lt;br /&gt;
block_row_dump:&lt;br /&gt;
tab 0, row 0, @0x1f72&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38&lt;br /&gt;
end_of_block_dump&lt;br /&gt;
;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 디폴트 not null 컬럼 추가 ===&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [2] TEST_YN CHAR(1) 컬럼 추가 NOT NULL DEFAULT &#039;N&#039;&lt;br /&gt;
ALTER TABLE COL_NOTNULL_CHK_TEST ADD TEST_YN CHAR(1) DEFAULT &#039;N&#039; NOT NULL;&lt;br /&gt;
&lt;br /&gt;
-- [1]번째 ROW UPDATE - 데이터가 실제 저장되는지 확인 &lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST &lt;br /&gt;
   SET TEST_YN=&#039;Y&#039;&lt;br /&gt;
WHERE AGRNEN_REG_NO=1006036461;&lt;br /&gt;
&lt;br /&gt;
SELECT * FROM DBAKM.COL_NOTNULL_CHK_TEST&lt;br /&gt;
-- WHERE AGRNEN_REG_NO=1006036404&lt;br /&gt;
;&lt;br /&gt;
-- AGRNEN_REG_NO, TEST_YN&lt;br /&gt;
--1006036404,N&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP2&#039;;&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
-- 트레이스 파일 경로&lt;br /&gt;
select trace_filename,to_char(modify_time,&#039;YYYY-MM-DD HH24:MI:SS&#039;) as modified&lt;br /&gt;
       , filesize&lt;br /&gt;
  from v$diag_trace_file&lt;br /&gt;
 where trace_filename like &#039;%cra_%&#039;&lt;br /&gt;
 order by modify_time DESC;&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- .trc파일에서 FILE_ID = 15 ,BLOCK_ID=935475 , ROW_NUM=1 인 로우를 찾기 (1번째 UPDATE 치고 덤프 조회 )&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
tab 0, row 0, @0x1e63        &amp;lt;== 1번째 ROW (AGRNEN_REG_NO=1006036404) ,&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x2  cc: 3  &amp;lt;== tl 컬럼 행길이 : 17바이트 ,  fb행플그 :  Head-piece,First row piece,Last row piece / lb : lock byte :0x02(로우락&lt;br /&gt;
킹) // cc:3  컬럼갯수(column count: 3개 )&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38     &amp;lt;== hex값:  1006036404&lt;br /&gt;
col  1: *NULL*                      &amp;lt;== 이건 무슨 컬럼인가??? (아래 참조 설명) &lt;br /&gt;
col  2: [ 1]  59                    &amp;lt;== 아스키코드 0x59 는 &#039;Y&#039;&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
tab 0, row 1, @0x1f64&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37   &amp;lt;=&lt;br /&gt;
.....&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 이후 2번째 row의 업데이트를 실시   &amp;lt;== AGRNEN_REG_NO=1001976357&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [2번째 ROW UPDATE - 데이터가 실제 저장되는지 확인 ]&lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST &lt;br /&gt;
   SET TEST_YN=&#039;Y&#039;&lt;br /&gt;
WHERE AGRNEN_REG_NO=1001976357;&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
-- [AGRNEN_REG_NO=1006036404]&lt;br /&gt;
tab 0, row 0, @0x1e63&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x2  cc: 3&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 38 39 34&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59    &amp;lt;== 1번째 ROW UPDATE한 값&lt;br /&gt;
&lt;br /&gt;
-- [AGRNEN_REG_NO=1001976357] &amp;lt;== 2번째 업데이트 처리&lt;br /&gt;
tab 0, row 1, @0x1e02&lt;br /&gt;
tl: 17 fb: --H-FL-- lb: 0x3  cc: 3&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59     &amp;lt;== 2번째 ROW UPDATE한 값&lt;br /&gt;
&lt;br /&gt;
-- [AGRNEN_REG_NO=*** ] 3번째 row , 2번째 컬럼값이 기본값이 저장됨.&lt;br /&gt;
tab 0, row 2, @0x1f56&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 31 31 37 30 36 35 36 30 38&lt;br /&gt;
-- [AGRNEN_REG_NO=*** ] 3번째 row , 2번째 컬럼값이 기본값이 저장됨.&lt;br /&gt;
tab 0, row 3, @0x1f48&lt;br /&gt;
tl: 14 fb: --H-FL-- lb: 0x0  cc: 1&lt;br /&gt;
col  0: [10]  31 30 30 31 39 36 34 32 35 38 37 39 34&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* [참고] ===&amp;gt; 여기에서 cc가 컬럼갯수인데 현재 컬럼을 2개 추가했는데 cc:3인 이유?&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
select column_name,data_type,hidden_column,virtual_column&lt;br /&gt;
  from dba_tab_cols&lt;br /&gt;
 where table_name=&#039;COL_NOTNULL_CHK_TEST&#039;;&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
AGRNEN_REG_NO        VARCHAR2       NO        NO&lt;br /&gt;
SYS_C00002_25092615-44:55 CHAR      YES       NO  &amp;lt;== 기존에 존재하던 컬럼이 삭제하거나 가상컬럼이 저장된것임.&lt;br /&gt;
TEST_YN              CHAR           NO        NO&lt;br /&gt;
------------------------------------------------------------&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 테스트 결과 ===&lt;br /&gt;
{{요점&lt;br /&gt;
|내용= * 오라클 12C 부터는 not null 컬럼이 추가될 경우 실제 데이터가 저장되지 않고 메타정보만 저장 된다.&lt;br /&gt;
* -- 단, 데이터가 입력되거나 변경시 컬럼이 실제 데이터가 기록된다.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== 추가 의문 사항 ===&lt;br /&gt;
# 신규로 컬럼의 추가 하면 블럭의 변화가 발생 될까?&lt;br /&gt;
#: -- (결론) 다른 컬럼이 추가/변경 되더라도 기존 컬럼구조는 변경되지 않는다.&lt;br /&gt;
#: -- (데이터에 값이 존재하지 않는 상태 그대로 존재함.TEST_YN 은 default &#039;N&#039; not null  이지만 실제 데이터블럭에는 값이 저장되어 있지 않음.)&lt;br /&gt;
#: -- 다른 컬럼[ADD1_YN] 추가로 업데이트 되더라도  TEST_YN 컬럼은 변경되지 않는다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=sql&amp;gt;&lt;br /&gt;
&lt;br /&gt;
-- [추가 확장 검증 ]&lt;br /&gt;
ALTER TABLE COL_NOTNULL_CHK_TEST ADD ADD1_YN CHAR(1) DEFAULT &#039;N&#039; NOT NULL;&lt;br /&gt;
SELECT * FROM COL_NOTNULL_CHK_TEST;&lt;br /&gt;
UPDATE COL_NOTNULL_CHK_TEST SET ADD1_YN=&#039;C&#039;;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
&lt;br /&gt;
-- 컬럼의 물리적 구조&lt;br /&gt;
SELECT DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) AS FILE_ID&lt;br /&gt;
     , DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS BLOCK_ID&lt;br /&gt;
     , DBMS_ROWID.ROWID_ROW_NUMBER(ROWID)  AS ROW_NUM&lt;br /&gt;
  FROM DBAKM.COL_NOTNULL_CHK_TEST&lt;br /&gt;
 WHERE AGRNEN_REG_NO=1006036404&lt;br /&gt;
;&lt;br /&gt;
--FILE_ID, BLOCK_ID, ROW_NUM&lt;br /&gt;
15       935475     0&lt;br /&gt;
;&lt;br /&gt;
&lt;br /&gt;
-- 블럭 덤프&lt;br /&gt;
alter session set timed_statistics = TRUE;&lt;br /&gt;
--alter session set max_dump_file_size = 100M;&lt;br /&gt;
alter session set tracefile_identifier=&#039;CYKUN_DUMP2&#039;;&lt;br /&gt;
&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = TRUE;&lt;br /&gt;
ALTER SYSTEM DUMP DATAFILE 15 block 935475;&lt;br /&gt;
ALTER SESSION SET SQL_TRACE = FALSE;&lt;br /&gt;
&lt;br /&gt;
block_row_dump:&lt;br /&gt;
tab 0, row 0, @0x1e0f&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 36 35 36 30 38 30 39 34&lt;br /&gt;
col  2: [ 1]  59   &amp;lt;== 1번째 &lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 1, @0x1eac&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 37 39 36 35 39 31 35 37&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: [ 1]  59   &amp;lt;== 2번째 &lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 2, @0x1e9a&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 31 39 31 31 37 30 36 35 33 39 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       -- &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 3, @0x1e88&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 36 34 32 35 38 37 39 34 37 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       --  &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 4, @0x1e76&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 31 39 37 36 35 33 39 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 5, @0x1e64&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 30 33 39 36 35 39 39 36&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*       --  &amp;lt;== 데이터가 저장되어 있지않음( 결국 , 조회시 메타정보를 이용한다는 의미임)&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 6, @0x1e52&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 37 39 36 37 39 37 34 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 7, @0x1e40&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 30 31 35 33 37 39 32 30&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 8, @0x1e2e&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 32 34 36 31 31 37 39&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
tab 0, row 9, @0x1e1c&lt;br /&gt;
tl: 18 fb: --H-FL-- lb: 0x1  cc: 4&lt;br /&gt;
col  0: [10]  31 30 30 31 39 32 30 39 37 31 35 32&lt;br /&gt;
col  1: *NULL*&lt;br /&gt;
col  2: *NULL*&lt;br /&gt;
col  3: [ 1]  43&lt;br /&gt;
end_of_block_dump&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
{{결론&lt;br /&gt;
|내용= 테스트한 결과 오라클 12c부터는 NOT NULL DEFAULT 컬럼 추가 시 실제 데이터를 저장하지 않고 메타데이터만 저장하며, 데이터가 UPDATE될 때 비로소 물리적으로 저장된다는 점입니다.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[category:oracle]]&lt;/div&gt;</summary>
		<author><name>Dbstudy</name></author>
	</entry>
</feed>