Parsing
데이터베이스에서 파싱(Parsing)은 매우 중요한 의미를 가지고 있습니다. 데이터베이스는 사용자가 입력한 SQL 문장을 그대로 실행하지 않습니다. 먼저 해당 SQL 문장을 해석하고, 그 구조와 의미를 분석하는 과정을 거치는데, 이 단계가 바로 파싱 과정입니다.
예를 들어, 다음과 같은 SQL 문장이 있다고 가정해봅시다.
FROM users
WHERE age > 20;
데이터베이스는 이 문장을 그대로 처리하는 것이 아니라,
👉 SELECT는 조회 명령인지
👉 name은 어떤 컬럼인지
👉 users는 어떤 테이블인지
👉 WHERE 조건은 무엇인지
👉 문법적으로 올바른지
를 하나씩 분석합니다.
1️⃣ Parse
Syntax Check (문법 검사)
Semantic Check (의미 검사)
SQL 문장이 문법적으로 올바른지 확인합니다.
2️⃣ Optimize (실행 계획 수립)
Parse가 끝나면 데이터베이스는 SQL을 어떻게 실행할지 결정합니다.
이 과정을 Query Optimization(쿼리 최적화)라고 합니다.
3️⃣ Execute (실행)
Optimizer가 만든 실행 계획에 따라 실제로 데이터를 읽고, 계산하고, 조인을 수행합니다.
디스크에서 데이터를 읽고 메모리에서 연산을 수행하고 조건을 필터링합니다.
4️⃣ Fetch (결과 반환)
결과를 클라이언트(사용자)에게 전달합니다.
Hard Parsing vs Soft Parsing
Parse
첫 번째 파싱 단계에서 Oracle Database에서 매우 중요하게 다뤄지는 개념인 Hard Parsing과 Soft Parsing이 등장합니다.
SQL 문장이 데이터베이스에 전달되면, 가장 먼저 Parse 단계가 수행됩니다. 이때 오라클은 해당 SQL을 Hard Parse로 처리할지, Soft Parse로 처리할지 결정하게 됩니다.
그렇다면 누가 이 결정을 내릴까요? 이 결정은 개발자나 사용자가 직접 선택하는 것이 아니라, 오라클의 SQL 처리 엔진 내부 메커니즘이 자동으로 판단합니다.구체적으로는 다음과 같은 과정이 이루어집니다.
1️⃣사용자가 SQL을 실행하면
2️⃣ 오라클은 먼저 Shared Pool의 Library Cache에서 동일한 SQL 문장과 실행 계획이 이미 존재하는지 검색합니다.
3️⃣ 만약 기존에 생성된 실행 계획이 존재하고 재사용이 가능하다면 → Soft Parse가 수행됩니다.
4️⃣ 실행 계획이 존재하지 않거나 재사용할 수 없는 경우 → Optimizer를 다시 수행하여 Hard Parse가 발생합니다.

사용자가 SQL 문장을 실행하면, 오라클은 가장 먼저 해당 문장에 해시 함수(Hash Function)를 적용합니다. 이 과정을 통해 생성된 고유한 해시값(Hash Value)은 라이브러리 캐시 내에서 해당 SQL의 정보를 찾기 위한 "주소" 역할을 합니다.
생성된 해시값을 가지고 수많은 데이터가 담긴 Hash 버킷(Bucket)을 탐색해야 합니다. 이때 데이터의 일관성을 유지하고 충돌을 방지하기 위해, 반드시 Library cache latch라는 권한(Lock)을 획득해야만 버킷에 접근할 수 있습니다.
Hash 버킷 내부에는 여러 데이터가 Chain 구조로 연결되어 있습니다. 각 체인에는 Handle이 달려 있는데, 이 핸들은 실제 SQL 정보가 담긴 LCO(Library Cache Object)를 가리키는 포인터 주소값을 가지고 있습니다. 즉, 핸들을 찾으면 실제 실행 계획이 어디에 있는지 알 수 있게 됩니다.
위 과정을 통해 라이브러리 캐시에서 동일한 LCO를 찾아내는 데 성공하면, 이미 메모리에 적재된 SQL 문장과 실행 계획(Plan)을 즉시 매칭합니다. 새롭게 최적화 단계를 거치지 않고 기존 정보를 재사용하는 이 상태를 우리는 Soft Parsing이라고 부릅니다.

Library Cache Object(LCO) 생성을 위한 공간은 Shared Pool로부터 할당받아야 합니다. Shared Pool은 Heap 관리 기법을 따르며, 이는 Heap Descriptor를 정점으로 여러 개의 Extent가 Linked List 형태로 연결된 계층 구조를 가집니다.
메모리 할당을 위해 먼저 Shared Pool Latch를 획득한 후, 관리 효율을 위해 크기별로 버킷화된 Free List를 탐색하여 즉시 사용 가능한 Free Chunk가 있는지 확인하며, 만약 Free List에서 적절한 크기의 Chunk를 찾지 못할 경우, LRU(Least Recently Used) 알고리즘에 따라 재사용 가능성이 낮은 Chunk들을 해제함으로써 새로운 할당 공간을 확보합니다.

하드 파싱이 시작되면 먼저 해당 SQL에 대응하는 Handle을 생성하기 위해 Library Cache Lock을 Exclusive 모드로 획득하고 Library Cache Object(LCO)를 생성합니다. LCO의 기본 골격 생성이 완료되면 Lock 상태를 Null 모드로 전환합니다. 이후 실제 실행 계획을 생성하고 데이터를 채우기 위해 해당 LCO에 대한 Library Cache Pin을 Exclusive 모드로 획득합니다. 모든 정보가 구성되면 해당 LCO를 해시 버킷(Hash Bucket)에 최종적으로 연결합니다.
이러한 LCO 생성부터 실행 계획 확정까지의 전체 과정을 Hard Parsing이라고 합니다. 파싱이 완료되면 점유했던 Lock과 Pin을 모두 Shared 모드로 전환하여, 데이터 정합성을 보장하면서 SQL 문장을 실제 수행하는 Execute 단계로 진입하게 되며, 이 과정에서 경합이 발생하면 library cache lock이나 library cache pin 대기 이벤트가 발생하게 됩니다.
Logical Read vs Physical Read

Hard Parse 또는 Soft Parse가 완료되면 SQL은 실행(Execute) 단계로 넘어갑니다. 이 단계에서 서버 프로세스는 실제 데이터 조회를 위해, 필요한 데이터 블록이 버퍼 캐시(Buffer Cache)에 존재하는지 먼저 확인합니다.이를 위해 오라클은 데이터 블록의 파일 번호(File#), 블록 번호(Block#) 와 같은 주소 정보를 기반으로 해시 함수를 적용하여 해당 블록이 속한 해시 버킷(Hash Bucket) 을 찾습니다.
각 해시 버킷은 체인(Linked List) 구조로 연결되어 있으며, 여러 세션이 동시에 접근할 수 있기 때문에 무결성을 보장해야 합니다.
따라서 서버 프로세스는 해당 버킷 체인을 보호하기 위해 Cache Buffers Chains(CBC) Latch를 획득한 후, 버퍼 캐시에 원하는 블록이 존재하는지 탐색합니다.
해시 버킷을 탐색하여 타겟 블록의 Buffer Header를 찾고, 해당 헤더가 포인팅하는 버퍼 캐시 영역 내에 블록이 존재하면 Cache Hit가 발생합니다. 이처럼 메모리 내에서 Latch를 획득하고 데이터 블록을 읽어들이는 일련의 작업을 Logical Reads라고 정의합니다.
버퍼 캐시에 해당 블록이 존재하지 않는 경우 Cache Miss가 발생합니다. 이때 서버 프로세스는 데이터파일로부터 블록을 읽어올 메모리 공간을 확보해야 합니다. 이를 위해 LRU(Least Recently Used) List와 LRUW(Write) List로 구성된 Working Set을 탐색하여 Free Buffer를 할당받는 과정을 거칩니다.
할당받은 Free Buffer에 데이터파일로부터 읽어온 블록을 적재하며, 이 과정에서 발생하는 물리적 디스크 읽기 작업이 Physical I/O입니다. 이후 프로세스는 다시 해당 블록을 참조하여 실행을 이어갑니다.

서버 프로세스는 데이터 읽기를 위해 cache buffers lru chain 래치를 확보한 뒤, LRU 보조 리스트를 우선 탐색하여 프리 버퍼를 확보합니다. 보조 리스트에 가용한 버퍼가 없는 경우 LRU 메인 리스트를 탐색하며, 이 과정에서 발견된 더티 버퍼는 LRUW 리스트로 이동시켜 관리합니다.
💡 더티 버퍼(Dirty Buffer)?
는 Buffer Cache에 적재된 데이터 블록이 사용자에 의해 수정(Update/Insert/Delete)되었지만, 아직 디스크(Datafile)에는 기록되지 않은 상태의 버퍼를 말합니다.

서버 프로세스가 필요한 데이터 블록을 버퍼 캐시에서 찾지 못하면, 새로운 블록을 적재하기 위해 프리 버퍼(Free Buffer) 를 확보해야 합니다. 이때 LRU 메인 리스트에서 즉시 사용 가능한 프리 버퍼를 확보하지 못할 경우, 오라클은 LRUW 리스트에 존재하는 더티 버퍼를 데이터 파일에 기록하도록 DBWn프로세스에 요청합니다.
DBWn이 더티 버퍼를 디스크에 기록하면 해당 버퍼는 재사용 가능한 프리 버퍼로 전환됩니다. 이후 서버 프로세스는 확보된 프리 버퍼에 새로운 블록을 적재하기 위해, 해당 버퍼에 대해 Exclusive 모드의 버퍼 락을 획득하여 데이터 정합성을 보장합니다. 그 다음, 디스크의 데이터 파일로부터 필요한 블록을 읽어 메모리(버퍼 캐시)에 적재하는 Physical Read 작업을 수행합니다.
결국 어떠한 데이터베이스를 사용하든 성능 최적화의 핵심은 "불필요한 일을 하지 않게 만드는 것'"에 있습니다. 물리적 I/O(Physical Read)를 줄여 메모리(Logical Read)를 효율적으로 활용하고, 하드 파싱의 부하를 소프트 파싱으로 전환하여 CPU 자원을 방어하는 것이 SQL을 다루는 고급 인프라 엔지니어의 덕목입니다.
'데이터베이스' 카테고리의 다른 글
| [데이터베이스] ASM(Automatic Storage Management)의 구조와 동작 메커니즘 (0) | 2026.02.24 |
|---|---|
| [데이터베이스] Oracle RAC 구조와 작동 원리 이해하기 (0) | 2026.02.19 |
| [데이터베이스] 오라클의 타임머신과 블랙박스 Undo와 Redo (0) | 2026.02.12 |
| [데이터베이스] SQL 성능의 나침반, 오라클 옵티마이저(Optimizer)와 통계 정보의 중요성 (0) | 2026.02.03 |
| [데이터베이스] DBA의 필수 역량: 솔루션 없이 AWR 스냅샷으로 DB 병목 지점 추적하기 (0) | 2026.02.03 |