---
description: 인덱스
---

# Week3 Index

## 랜덤 I/O와 순차 I/O에 대해 설명해주세요

**랜덤 I/O**: 헤드가 디스크의 여러 위치로 빠르게 이동해야 하기 때문에 탐색 시간이 길어지고, 그 결과 성능이 저하됩니다. 비연속적인 위치에서 데이터를 읽거나 쓰는 방식입니다. 파일 시스템이나 데이터베이스에서 데이터가 디스크의 여러 위치에 흩어져 있을 때 주로 발생합니다.

* **특징**: 데이터를 읽고 쓰는 위치가 임의적이므로, 디스크의 헤드가 자주 움직여야 합니다. 특히 하드 디스크(HDD)에서 이 방식은 성능 저하를 초래할 수 있습니다. 디스크 헤드의 이동(시크 타임)이 많이 발생하고, 디스크 회전 대기 시간이 길어질 수 있기 때문입니다.
* **장점**: 특정 위치에 빠르게 접근하여 데이터를 처리할 수 있으므로, 소량의 데이터를 여러 곳에서 읽어야 하는 경우에 유리합니다.
* **단점**: 물리적 디스크의 특성상, I/O 성능이 낮아질 수 있습니다. 특히 하드 디스크(HDD)에서는 랜덤 접근이 비효율적입니다.

**예시**

* 데이터베이스에서 인덱스를 통해 특정 레코드에 접근할 때
* 파일 시스템에서 여러 파일을 동시에 읽거나 쓸 때



**순차 I/O**: 헤드가 디스크에서 연속적으로 데이터를 읽기 때문에 탐색 시간이 줄어들어 성능이 훨씬 빠릅니다.

순차 I/O는 연속적인 데이터 블록을 순서대로 읽거나 쓰는 방식입니다. 이는 디스크에 저장된 데이터가 연속적인 위치에 저장되어 있을 때 주로 발생합니다.

* **특징**: 데이터를 순차적으로 처리하므로, 디스크 헤드가 한 번에 긴 데이터 블록을 읽거나 쓸 수 있어 I/O 효율성이 높습니다. 특히 하드 디스크에서는 순차 I/O가 매우 빠르며, 디스크 회전과 헤드 이동이 최소화됩니다.
* **장점**: 대용량 데이터를 빠르게 처리할 수 있으며, 성능이 매우 우수합니다. 일반적인 HDD나 SSD 모두 순차 I/O에서 높은 성능을 보입니다.
* **단점**: 특정 위치에 있는 데이터를 즉시 접근하기보다는, 전체 데이터 흐름을 따라 읽거나 써야 하기 때문에 유연성이 떨어질 수 있습니다.

**예시**

* 로그 파일에 데이터를 추가할 때 (로그는 주로 순차적으로 쓰여짐)
* 대용량 파일을 연속적으로 읽을 때 (예: 비디오 파일)

## 인덱스가 뭔가요?

* **인덱스**는 데이터베이스에서 **검색 성능을 높이기 위해** 사용하는 자료 구조
* 인덱스가 없으면, 테이블의 모든 데이터를 처음부터 끝까지 스캔하는 **풀 스캔**이 발생하지만, 인덱스를 사용하면 인덱스를 통해 필요한 레코드의 위치를 빠르게 조회
* 인덱스는 주로 **B-트리(B-tree)** 구조로 구현되며, 이는 **균형 잡힌 트리**로서 삽입, 삭제, 검색 시 일정한 성능을 보장
* 하지만 인덱스는 삽입, 수정, 삭제 작업 시 **추가적인 오버헤드**를 발생시킵니다. 인덱스는 항상 정렬된 상태를 유지해야 하므로, 레코드를 수정하거나 삭제할 때 인덱스도 함께 갱신



## 인덱스의 동작 방식에 대해서 설명해주세요

인덱스 테이블에 특정 컬럼들만 저장하고 해시 또는 B트리를 통해 빠르게 검색이 가능합니다.

B-트리

* **B-트리 구조**로 구현. **균형 이진 트리**와 유사하지만, 각 노드가 여러 개의 자식 노드를 가질 수 있는 구조
* **데이터 삽입, 삭제, 검색 작업이 log(n)**&#xC758; 시간 복잡도
* 내부 노드에는 해당 노드의 **키 값**과 함께 각 자식 노드로 이동하기 위한 **포인터**가 포함되어 있으며, 검색 대상이 내부 노드의 키 값에 맞는 경우에는 리프 노드까지 가지 않고도 데이터를 얻을 수 있습니다.

해시

해시 인덱스는 특정 값에 대해 해시 함수를 적용하여 데이터가 저장된 위치를 빠르게 찾는 방식입니다. 이 방식은 **정확한 값**을 검색할 때 매우 효율적이지만, 범위 검색에는 적합하지 않습니다.

* **해시 값 계산**: 해시 함수는 입력된 키 값을 기반으로 고유한 해시 값을 계산합니다.
* **해시 버킷**: 계산된 해시 값은 특정 버킷(bucket)에 매핑됩니다. 해당 버킷에 저장된 데이터를 읽어와 비교하여 원하는 데이터를 찾습니다.





## 어떤 기준으로 인덱스를 설정해야 할까요?

* 유일성, 카디널리티, 검색 패턴, 데이터 수정 빈도 등을 종합적으로 고려
* **카디널리티가 높은 컬럼**: 인덱스는 주로 **카디널리티가 높은 컬럼**에 설정하는 것이 좋습니다. 카디널리티란 해당 컬럼에서 **유일한 값의 개수**를 의미
* **자주 검색되는 컬럼**: 인덱스는 **검색 조건에 자주 사용되는 컬럼**에 설정하는 것이 효율적입니다. WHERE 절에서 자주 사용되는 컬럼이나 JOIN에서 자주 쓰이는 컬럼에 인덱스를 걸면, 검색 속도를 크게 향상시킬 수 있습니다.
* 삽입/수정/삭제 빈도 고려.



## 테이블에 인덱스를 많이 설정하면 좋을까요?

* **너무 많은 인덱스**를 설정하는 것은 **성능에 부정적인 영향**
* 쓰기 작업의 성능 저하 (읽기 성능과 쓰기 성능 간의 트레이드 오프)
* 인덱스 저장 공간
* 쿼리 옵티마이저의 인덱스 선택 문제: 옵티마이저가 잘못된 인덱스를 선택할 가능성



## 커버링 인덱스(Covering Index)에 대해서 설명해주세요

* 쿼리에서 필요한 모든 데이터를 인덱스 자체에서 조회할 수 있는 인덱스. 즉, 인덱스만으로 결과를 반환할 수 있는 경우.
* **디스크 I/O 감소**: 테이블의 데이터를 직접 읽는 대신, 인덱스만으로 데이터를 가져올 수 있으므로 **디스크 I/O가 줄어들어** 성능이 크게 향상
* 성능 최적화

```sql
CREATE INDEX idx_department_name ON employees (department, id, name);
이 인덱스는 department, id, name 열을 포함합니다. 이제 동일한 쿼리를 실행하면:

SELECT id, name FROM employees WHERE department = 'Sales';
```

이 경우, 인덱스 **idx\_department\_name**이 department, id, name 열을 모두 포함하고 있기 때문에, 데이터베이스는 인덱스만으로도 필요한 모든 데이터를 가져올 수 있습니다. 즉, 테이블에 접근할 필요 없이 인덱스만으로 쿼리가 처리됩니다.



## 다중 컬럼 인덱스(복합 인덱스)에 대해서 설명해주세요

* 두 개 이상의 컬럼을 하나의 인덱스로 묶어 **한 번에 여러 조건**을 기반으로 검색 성능을 최적화할 수 있는 인덱스
* **컬럼의 순서**에 따라 성능이 달라집니다. (예를 들어, `(A, B, C)`로 설정된 인덱스는 \*\*첫 번째 컬럼(A)\*\*에 기반한 검색에서만 성능 향상이 있습니다. 만약 `WHERE B = ?`와 같이 두 번째 컬럼만 검색할 경우에는 인덱스가 제대로 사용되지 않을 수 있습니다.) **선두 컬럼**부터 차례대로 조건에 맞는 값을 찾아나가며, 따라서 **쿼리의 조건이 선두 컬럼과 일치**할 때에만 인덱스가 효과적으로 사용
* 향상된 검색 성능
* 특정 쿼리에 최적화: WHERE A = ? AND B = ?
* 메모리 사용 증가
* 삽입/삭제/수정 시 성능 저하
* 쿼리 옵티마이저 선택 문제



## B-, B+ 트리 인덱스에 대해 설명해주세요.

**자가 균형 트리(Self-balancing Tree)**&#xB85C;, 데이터베이스 인덱스에서 **효율적인 검색, 삽입, 삭제**를 위해 사용되는 트리 구조

B- 트리

* **B-Tree**는 내부 노드와 리프 노드 모두에 **데이터와 키 값**을 저장
* **내부 노드**에서 원하는 데이터를 찾으면 **리프 노드까지 탐색할 필요가 없음**
* **범위 검색에는 비효율적**

**B+ 트리**

* 모든 데이터를 **리프 노드에만 저장**하는 구조
* 범위 검색에 유리 (순차 검색이 빠름)
* 메모리 효율성: 내부 노드에 데이터를 저장하지 않기떄문에 더 많은 키를 저장할 수 있어서 트리의 깊이가 작음



## Hash 인덱스에 대해 설명해주세요

* **정확한 값 일치 검색**에 매우 빠른 성능 하지만 범위 범색 불가능
* 데이터를 **정렬하지 않기 때문**에, `BETWEEN`, `>`, `<` 같은 조건을 사용할 때 매우 비효율적
* 체이닝 필요: 해시 함수는 서로 다른 입력 값이 동일한 해시 값을 가질 수 있는 **충돌(Collision)**&#xC774; 발생할 수 있습니다.

<details>

<summary>해시 충돌의 발생 원리</summary>

해시 함수는 입력 값을 일정한 범위 내의 값으로 변환하는 함수입니다. 해시 테이블의 크기는 유한하므로 해시 함수가 생성할 수 있는 해시 값의 수 역시 유한합니다. 반면, 입력 값은 매우 다양하고 무한한 경우가 있을 수 있기 때문에 서로 다른 두 입력 값이 같은 해시 값을 가질 수밖에 없습니다. 이때 해시 테이블에서 동일한 해시 값에 여러 입력 값이 할당되면서 **충돌**이 발생합니다.

</details>

* 해시 충돌 해결 방법
  * 체이닝: 동일한 해시 값을 가진 데이터를 연결 리스트로 관리하는 방식입니다.
  * 오픈 어드레싱: 충돌이 발생하면 다른 빈 버킷을 찾아 데이터를 저장하는 방식입니다.

<details>

<summary>연결 리스트로 연결된 데이터 조회 과정</summary>

1.  **해시 함수 적용**: 조회하려는 키에 해시 함수를 적용하여 해당 키가 저장된 **버킷**을 결정합니다. 해시 함수는 주어진 키에 대해 일정한 해시 값을 계산하고, 그 해시 값에 해당하는 버킷을 반환합니다.

    예를 들어, 해시 함수가 다음과 같이 정의되어 있다고 가정합니다:

    ```plaintext
    plaintext코드 복사해시 값 = key % 10
    ```

    키 `key = 12`에 대해 해시 함수는 다음과 같은 해시 값을 반환합니다.

    ```plaintext
    plaintext코드 복사12 % 10 = 2
    ```

    따라서 키 `12`는 **버킷 2**에 저장되어 있음을 알 수 있습니다.
2. **해당 버킷의 연결 리스트 탐색**: 해시 테이블에서 **버킷 2**로 이동하여 그 버킷에 저장된 연결 리스트를 조회합니다. 연결 리스트는 충돌로 인해 같은 해시 값을 가지는 여러 항목이 저장되어 있으므로, 이 리스트에서 원하는 키를 찾아야 합니다.
3. **연결 리스트에서 키 비교**: 연결 리스트의 각 노드를 순차적으로 탐색하며, 저장된 키와 조회하려는 키를 **하나씩 비교**합니다. 만약 같은 키를 찾으면 해당 키에 연결된 데이터를 반환합니다.
4. **키가 일치하는 데이터 반환**: 연결 리스트에서 조회하려는 키를 찾았다면, 해당 키에 저장된 **데이터 값**을 반환합니다.
5. **키가 일치하지 않으면 조회 실패**: 연결 리스트를 모두 탐색했지만 일치하는 키를 찾지 못하면, 해당 키에 대한 데이터는 해시 테이블에 존재하지 않음을 의미합니다.

</details>





## 클러스터링 인덱스에 대해서 설명해주세요

* **데이터베이스 테이블의 물리적인 저장 순서를 인덱스의 순서와 일치**시키는 방식(해당 인덱스의 값에 따라 **데이터가 디스크에 물리적으로 정렬**되어 저장)
* 테이블당 단 하나만 존재 가능. 기본키 / 고유키에 따라 정렬됨
* InnoDB와 같은 스토리지 엔진에서는 자동으로 기본키가 클러스터링 인덱스가 됨
* 빠른 범위 검색
* 인접 데이터 액세스
* 데이터 수정 시 오버헤드(재배치)



## 인덱스 스캔 방식에 대해 설명해주세요.

인덱스 유니크 스캔

* **인덱스 유니크 스캔**은 **고유한 값**을 검색할 때 사용됩니다. 이 방식은 주로 **프라이머리 키(Primary Key)**&#xB098; **유니크 인덱스(Unique Index)**&#xC5D0; 대해 정확한 값 일치 검색을 수행하는 경우 사용됩니다. 인덱스에서 해당 키를 바로 찾아가기 때문에 성능이 매우 빠릅니다.

인덱스 레인지 스캔

* 검색할 데이터의 범위가 정해졌을 때 사용되는 스캔 방식 (WHERE, BETWEEN, <, > 등)

인덱스 시크

* **B-트리(B-Tree)** 또는 **B+트리(B+Tree)** 구조를 사용하여 **빠르게 특정 값** 또는 **좁은 범위의 값**을 찾는 방식입니다.
* 트리 구조를 활용하여 **루트 노드부터 리프 노드까지** 검색하면서 조건에 맞는 데이터를 효율적으로 찾습니다.

인덱스 풀 스캔

* **인덱스의 처음부터 끝까지 모든 엔트리**를 순차적으로 스캔하는 방식입니다.
* 테이블 전체를 읽는 것이 아닌, 인덱스에 저장된 특정 열만 읽기 때문에 **테이블 풀 스캔(Table Full Scan)**&#xBCF4;다 효율적일 수 있습니다. (왜냐하면 인덱스는 테이블의 전체 컬럼을 읽지 않고, 필요한 컬럼만 포함하고 있기 때문입니다.)
*   **모든 레코드를 인덱스를 통해 검색**하지만, 트리 구조의 장점(빠른 탐색)을 활용하지 않고 **순차적 탐색**이 발생합니다.

    예시:

    ```sql
    SELECT * FROM employees WHERE department_id IS NOT NULL;
    ```

    만약 `department_id`에 인덱스가 있다면, 쿼리의 결과가 많은 데이터를 반환할 경우 인덱스 풀 스캔이 발생할 수 있습니다. 이때 테이블의 모든 열을 읽지 않고, 인덱스에 포함된 `department_id`와 관련된 데이터만을 순차적으로 스캔합니다.

인덱스 패스트 풀 스캔 (Index Fast Full Scan)

* 인덱스를 **정렬되지 않은 순서로 병렬로 읽는 방식**입니다.&#x20;
* 일반적인 인덱스 풀 스캔보다 더 빠르게 인덱스를 조회할 수 있지만, 인덱스의 정렬이 유지되지 않으므로 범위 검색에 적합하지 않습니다.&#x20;
* 빠르게 데이터를 읽어와야 하지만 순서가 중요하지 않을 때 사용됩니다.

<details>

<summary>Table Full Scan vs Index Full Scan</summary>

테이블 풀 스캔은 모든 컬럼에 대해서 읽은 후에 조건을 확인합니다.

인덱스 풀 스캔은 일부 컬럼만 저장되어서 디스크에서 읽어야 하는 데이터 양이 적습니다.

레코드 수는 같아도, 읽어야 하는 총 데이터 양에서 차이가 납니다.

</details>





## 쿼리 실행 계획에 대해서 설명해주세요. 실행 계획을 확인해본 적이 있나요?

<div data-full-width="true"><figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2FEJ3tpEhvqT2uDROqbTuV%2Fimage.png?alt=media&#x26;token=322379b4-ded6-426f-a117-b87aa7cb78df" alt=""><figcaption></figcaption></figure></div>

<div data-full-width="true"><figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2F6AvSN2LE3RaDBZpmRlSI%2Fimage.png?alt=media&#x26;token=bd82cbc7-0165-4689-9c7d-9ac35d2d7085" alt=""><figcaption></figcaption></figure></div>

* 데이터베이스가 **SQL 쿼리를 어떻게 실행할지에 대한 상세한 계획**
* 옵티마이저의 실행 계획은 그 과정에서 테이블에 어떻게 접근할 것인지, **어떤 인덱스를 사용할 것인지**, **조인을 어떻게 처리할 것인지** 등을 보여줍니다.
* EXPLAIN 키워드를 사용.\
  EXPLAIN SELECT \* FROM users WHERE age = 30;



## 쿼리힌트가 뭔가요?

* 옵티마이저에게  특정 실행 계획을 따르도록 지시하는 것.
* MySQL에서는 USE INDEX 같은 힌트를 사용할 수 있습니다. \
  SELECT \* FROM users USE INDEX (idx\_age) WHERE age = 30;



## 인덱스가 잘 동작하고 있는지 어떻게 확인할 수 있을까요?

* 쿼리 실행 계획(EXPLAIN)을 확인
* 쿼리 성능 테스트
* 시스템 테이블로 불필요한 인덱스 식별



## 인덱스 사용시 주의할점

* 인덱스의 개수
* 중복인덱스: 동일한 컬럼에 여러개의 인덱스
* 카디널리티 고려 (유일한 값의 개수)
* 충분히 데이터양이 큰지 확인



## GROUP BY 사용 시 인덱스가 걸리는 조건에 대해 설명해주세요

* 인덱스 컬럼의 순서: **GROUP BY** 절에서 사용하는 컬럼이 **인덱스에 설정된 순서와 일치해야** 인덱스를 사용 가능 (GROUP BY a,b,c - OK / GROUP BY b,c - X)
* GROUP BY 구문에서 사용된 컬럼이 모두 인덱스에 포함되어야 합니다.

GROUP BY는 결과를 정렬하는데 인덱스는 이미 정렬되어 있기떄문에 그루핑이 가능해집니다.



<details>

<summary>예시로 이해하기</summary>

**1. 올바른 순서로 `GROUP BY` 하는 경우**

인덱스가 `(a, b, c)` 순서로 설정된 경우를 가정해봅시다.

```sql
CREATE INDEX idx_abc ON table_name(a, b, c);

SELECT a, b, c, COUNT(*)
FROM table_name
GROUP BY a, b, c;
```

이 경우:

* **a**를 기준으로 먼저 데이터를 정렬하고, **b**를 기준으로 그 안에서 다시 데이터를 정렬하며, 마지막으로 **c**를 기준으로 정렬된 데이터가 인덱스에 저장됩니다.
* `GROUP BY a, b, c`는 인덱스의 정렬 순서를 그대로 따르므로, 인덱스를 그대로 사용하여 데이터를 효율적으로 그룹핑할 수 있습니다. 인덱스를 타고 내려가면서 **정렬된 데이터**를 빠르게 탐색할 수 있습니다.

**2. 중간 컬럼을 건너뛰고 `GROUP BY` 하는 경우**

```sql
SELECT b, c, COUNT(*)
FROM table_name
GROUP BY b, c;
```

이 경우:

* **인덱스는 a를 기준으로 먼저 정렬**되었기 때문에, **a를 건너뛰고 b, c만으로 그룹핑**하려는 경우 인덱스를 활용할 수 없습니다.
* 이는 인덱스가 **a로 먼저 정렬된 상태**에서 **b와 c**로 그룹핑을 진행할 수 없기 때문입니다. 인덱스는 `a`의 값에 따라 **첫 번째로 분류된 데이터 하위에서** `b`, `c`가 정렬된 상태이므로, `a`를 무시하고 `b`, `c`만을 기준으로 그룹핑하는 것이 불가능합니다.

**3. 부분적으로 `GROUP BY` 하는 경우**

```sql
SELECT a, b, COUNT(*)
FROM table_name
GROUP BY a, b;
```

이 경우:

* 인덱스가 `(a, b, c)` 순서로 설정되어 있고, `GROUP BY`에서 **a**와 **b**만 사용하면 인덱스는 사용될 수 있습니다.
* 인덱스는 **a와 b까지는 정렬된 상태**로 저장되어 있기 때문에, `GROUP BY a, b`는 인덱스를 활용하여 효율적으로 그룹핑할 수 있습니다. **c**는 무시되더라도 문제가 되지 않습니다.

#### **왜 `GROUP BY b, c`는 인덱스를 사용하지 못하는가?**

* **인덱스가 a부터 정렬**된 상태이기 때문에, 인덱스는 **a**의 정렬 없이 **b, c**로 바로 접근할 수 없습니다.
* **B-트리 구조**는 첫 번째 컬럼을 기준으로 데이터를 분류하고, 그 하위에서 두 번째, 세 번째 컬럼이 정렬되기 때문에, 첫 번째 컬럼을 건너뛰고 그다음 컬럼들만 사용한 `GROUP BY`는 인덱스가 **트리의 정렬된 특성**을 제대로 활용할 수 없게 만듭니다.

#### **정리**

* **인덱스의 컬럼 순서**와 **`GROUP BY` 절의 컬럼 순서**는 일치해야 합니다.
* **첫 번째 컬럼**을 기준으로 데이터가 정렬되기 때문에, `GROUP BY`에서 **첫 번째 컬럼을 건너뛰면** 인덱스가 제대로 사용되지 않습니다.
* **부분적으로** `GROUP BY`에서 인덱스의 **첫 번째부터 연속된 컬럼**만 사용하면, 인덱스는 효과적으로 사용될 수 있습니다.

</details>





