앞선 편들에서 B-Tree 스토리지 엔진의 원리를 살펴보았습니다. 이번 편에서는 시야를 좁혀 MySQL이 실제로 B-Tree 인덱스를 어떻게 구성하고 활용하는지를 다룹니다. 이론적인 자료구조를 넘어, 인덱스가 추가·삭제될 때 내부에서 무슨 일이 일어나는지, 어떤 조건에서 인덱스가 실제로 효과를 발휘하는지를 구체적으로 살펴보겠습니다.
1. B-Tree 인덱스의 구조 및 특성
InnoDB의 클러스터형 인덱스
MySQL InnoDB 스토리지 엔진은 테이블 데이터 자체를 클러스터형 인덱스(Clustered Index) 형태로 저장합니다. 즉, 테이블은 별도의 힙 파일(heap file)이 아니라 B-Tree 자체가 곧 테이블입니다.

클러스터형 인덱스의 리프 노드에는 실제 행 데이터 전체가 담겨 있습니다. 기본 키(Primary Key) 순서대로 물리적으로 정렬되어 저장되므로, 기본 키 범위 검색 시 디스크 I/O가 최소화됩니다.
기본 키가 없는 경우 InnoDB는 다음 순서로 클러스터형 인덱스 기준을 결정합니다.
NOT NULL인UNIQUE인덱스 중 첫 번째- 위 조건을 만족하는 것이 없으면 내부적으로 숨겨진 6바이트
Row ID를 기본 키로 사용
보조 인덱스 (Secondary Index)
기본 키 이외의 칼럼에 생성한 인덱스를 보조 인덱스(Secondary Index) 라고 합니다. 보조 인덱스의 리프 노드에는 인덱스 키 값과 함께 해당 행의 기본 키 값이 저장됩니다.

보조 인덱스를 통해 데이터를 읽을 때는 두 번의 B-Tree 탐색이 발생합니다.
- 보조 인덱스 B-Tree에서 기본 키 값을 찾는다.
- 클러스터형 인덱스 B-Tree에서 기본 키로 실제 행을 찾는다.
이 두 번째 탐색을 북마크 룩업(Bookmark Lookup) 이라고 부르며, 레코드 한 건마다 랜덤 I/O가 발생합니다. 이 비용이 보조 인덱스를 통한 데이터 읽기의 핵심 병목입니다.
클러스터형 인덱스의 장단점
클러스터형 인덱스 구조는 명확한 트레이드오프를 가집니다.
장점
기본 키 기준의 범위 검색에서 성능이 뛰어납니다. 리프 노드에 행 데이터가 기본 키 순서대로 연속 저장되어 있으므로, 범위 내 레코드를 순차 I/O로 읽을 수 있습니다.
보조 인덱스의 리프 노드에는 인덱스 키 값과 함께 기본 키 값이 항상 저장되어 있습니다. 따라서 기본 키 칼럼을 SELECT에 포함하더라도 북마크 룩업 없이 보조 인덱스만으로 결과를 반환할 수 있습니다. 예를 들어 (email) 인덱스만 있어도 SELECT email, user_id FROM users WHERE email = ? 쿼리는 커버링 인덱스로 처리됩니다. 기본 키가 조회 조건이나 결과에 자주 포함된다면, 이 특성을 활용해 인덱스를 설계하는 것이 효과적입니다.
단점 및 주의할 점
보조 인덱스의 리프 노드마다 기본 키 값이 저장되므로, 기본 키 크기가 클수록 모든 보조 인덱스의 저장 공간이 함께 늘어납니다. 보조 인덱스가 많은 테이블일수록 이 영향이 커지므로, 기본 키는 가능한 한 작은 타입(INT, BIGINT)을 사용하는 것이 좋습니다.
기본 키 값을 변경하는 UPDATE는 비용이 매우 큽니다. 클러스터형 인덱스 내의 행 위치가 바뀌는 것은 물론, 해당 행의 기본 키를 저장하고 있는 모든 보조 인덱스도 함께 갱신해야 하기 때문입니다. 기본 키는 변경되지 않는 값으로 설계하는 것이 원칙입니다.
무작위 순서의 기본 키(UUID 등)는 삽입 시 페이지 분리와 단편화를 유발합니다. 이 문제는 2절에서 자세히 다룹니다.
B-Tree 인덱스의 일반적인 특성
B-Tree 인덱스는 아래와 같은 검색에 효과적입니다.
| 검색 유형 | 예시 |
| 완전 키 검색 | WHERE name = '홍길동' |
| 키 범위 검색 | WHERE age BETWEEN 20 AND 30 |
| 키 접두사 검색 | WHERE name LIKE '홍%' |
| 정렬 (ORDER BY) | ORDER BY name ASC |
| 그루핑 (GROUP BY) | GROUP BY department |
반대로 WHERE name LIKE '%길동'처럼 접두사가 없는 와일드카드 검색에는 B-Tree 인덱스를 사용할 수 없습니다.
2. 키 추가 및 삭제
키 추가 — 페이지 분리 (Page Split)
B-Tree에 새로운 레코드를 삽입할 때, 해당 위치의 리프 페이지에 빈 공간이 있으면 단순히 삽입합니다. 그러나 페이지가 꽉 찼다면 페이지 분리(Page Split) 가 일어납니다.

페이지 분리 과정은 다음과 같습니다.
- 새 페이지를 할당한다.
- 기존 페이지의 레코드를 절반씩 나눠 두 페이지에 분배한다.
- 부모 노드에 새 페이지의 분리 키(separator key)를 추가한다.
- 부모 노드도 꽉 찼다면 재귀적으로 분리가 일어날 수 있다.
페이지 분리는 비용이 큰 작업입니다. 디스크 쓰기가 여러 번 발생하고, 잠금(Lock)이 수반되기 때문에 쓰기 성능에 직접적인 영향을 줍니다. 이것이 단조 증가하는 기본 키(AUTO_INCREMENT) 를 권장하는 이유입니다. 항상 오른쪽 끝 페이지에만 삽입이 일어나므로 분리가 최소화됩니다.
반면 UUID처럼 무작위 순서의 기본 키를 쓰면, 삽입 위치가 랜덤하게 분산되어 페이지 분리가 빈번하게 발생하고 페이지 단편화(fragmentation)도 심해집니다. 이것이 1절에서 언급한 UUID 기본 키의 문제입니다.
키 삭제 — 마킹과 병합
레코드 삭제 시 InnoDB는 즉시 공간을 회수하지 않습니다. 해당 레코드에 삭제 마크(delete mark) 를 설정하고, 이후 퍼지(Purge) 스레드가 백그라운드에서 실제 공간을 정리합니다.
페이지가 특정 임계치 이하로 채워지면 인접 페이지와 병합(Merge) 이 일어날 수 있습니다. 이는 페이지 분리의 역방향 작업으로, 마찬가지로 부모 노드 갱신이 수반됩니다.
키 변경 — 삭제 후 재삽입
인덱스 키 값을 변경하는 것은 내부적으로 이전 키 삭제 + 새 키 삽입의 두 단계로 처리됩니다. 따라서 인덱스가 많은 테이블에서 UPDATE가 잦으면 성능 부담이 커집니다.
3. 인덱스 선택에 영향을 미치는 요소
카디널리티와 선택도
카디널리티(Cardinality) 는 인덱스 칼럼이 가지는 유니크한 값의 수입니다. 선택도(Selectivity) 는 이를 전체 행 수로 나눈 비율(카디널리티 / 전체 행 수)로, 1에 가까울수록 인덱스가 결과를 좁히는 데 효과적입니다.
-- 카디널리티 확인
SHOW INDEX FROM employees;
-- 직접 계산
SELECT COUNT(DISTINCT department) / COUNT(*) FROM employees; -- 낮은 선택도
SELECT COUNT(DISTINCT email) / COUNT(*) FROM employees; -- 높은 선택도
성별처럼 M/F 두 값밖에 없는 칼럼은 선택도가 매우 낮습니다. 전체 행의 절반을 반환하는 쿼리라면 인덱스를 타는 것보다 풀 테이블 스캔이 오히려 빠를 수 있습니다. 일반적으로 선택도가 5~10% 이하일 때 인덱스 사용이 효과적입니다.
읽어야 하는 레코드 건수
카디널리티와 선택도는 인덱스 자체의 특성이지만, 옵티마이저가 실제로 판단하는 기준은 이 쿼리에서 몇 건을 읽어야 하는가입니다. 같은 인덱스라도 조건에 따라 읽는 건수가 달라지고, 그 건수가 일정 임계치를 넘으면 인덱스를 포기하고 풀 테이블 스캔을 선택합니다.
1절에서 설명한 것처럼, 보조 인덱스를 통한 조회는 북마크 룩업이 레코드 한 건마다 발생합니다. 읽는 레코드가 많아질수록 이 랜덤 I/O가 누적되어, 어느 순간 테이블을 처음부터 순차적으로 읽는 풀 테이블 스캔보다 느려집니다.
MySQL 옵티마이저는 전체 레코드의 20~25% 이상을 읽어야 한다고 판단하면 인덱스를 사용하지 않는 경향이 있습니다. 물론 이 임계치는 데이터 분포, 버퍼 풀 캐시 상태, I/O 비용 모델에 따라 달라집니다.
EXPLAIN SELECT * FROM employees WHERE department = 'Engineering';
-- rows: 1500 ← 옵티마이저가 읽을 것으로 추정하는 레코드 수
-- 전체 10만 건 중 1,500건 → 1.5% → 인덱스 사용
-- 전체 10만 건 중 3만 건 → 30% → 풀 테이블 스캔 선택 가능
인덱스가 존재하고 조건도 맞는데 실행 계획에서 인덱스가 사용되지 않는다면, 옵티마이저가 레코드 건수 기준으로 풀 스캔이 더 낫다고 판단한 것일 가능성이 높습니다. EXPLAIN의 rows 칼럼을 먼저 확인하는 것이 원인 파악의 출발점입니다.
복합 인덱스의 칼럼 순서
복합 인덱스(Multi-column Index)는 왼쪽 접두사 규칙(Leftmost Prefix Rule) 을 따릅니다. (A, B, C) 순서의 복합 인덱스가 있을 때, 아래와 같이 사용할 수 있습니다.

복합 인덱스를 설계할 때는 선택도가 높은 칼럼을 앞에 두는 것이 일반적인 원칙입니다. 단, 특정 쿼리 패턴이 정해져 있다면 해당 패턴에 맞게 순서를 결정하는 것이 더 중요합니다.
인덱스 통계 정보
MySQL 옵티마이저는 실행 계획을 세울 때 인덱스 통계(Statistics) 를 참조합니다. 대량 데이터 변경 후에는 통계가 실제와 달라질 수 있으므로, 아래 명령으로 통계를 갱신해 주는 것이 좋습니다.
ANALYZE TABLE employees;
4. 인덱스를 통한 데이터 읽기
MySQL이 인덱스를 활용하는 방식은 하나가 아닙니다. 쿼리 조건과 인덱스 구조에 따라 네 가지 스캔 방식 중 하나가 선택됩니다. 각 방식의 동작 원리와 EXPLAIN 출력을 함께 이해하면 실행 계획을 읽는 눈이 생깁니다.
인덱스 레인지 스캔 (Index Range Scan)
인덱스를 이용하는 가장 일반적인 방식입니다. 루트 노드에서 출발해 조건에 맞는 시작 지점을 찾은 뒤, 리프 노드를 순서대로 읽어나갑니다.

SELECT * FROM employees WHERE age BETWEEN 30 AND 40;
EXPLAIN SELECT * FROM employees WHERE age BETWEEN 30 AND 40;
-- type: range
-- key: idx_age
EXPLAIN의 type이 range이면 인덱스 레인지 스캔입니다. 보조 인덱스를 사용하는 경우 매 레코드마다 클러스터형 인덱스로 북마크 룩업이 발생합니다. 반환 행이 많아질수록 랜덤 I/O 비용이 누적되므로, rows 칼럼을 함께 확인하는 것이 중요합니다.
인덱스 풀 스캔 (Index Full Scan)
인덱스의 처음부터 끝까지 전부 읽는 방식입니다. 인덱스는 테이블보다 크기가 훨씬 작기 때문에, 테이블 풀 스캔에 비해 읽는 데이터 양이 크게 줄어듭니다.
-- (name) 인덱스가 있을 때, WHERE 없이 name 기준 정렬
SELECT name FROM employees ORDER BY name;
EXPLAIN SELECT name FROM employees ORDER BY name;
-- type: index
type이 index이면 인덱스 풀 스캔입니다. WHERE 조건이 없거나 인덱스 범위를 특정할 수 없지만, 인덱스만으로 결과를 만들 수 있을 때 나타납니다.
루즈 인덱스 스캔 (Loose Index Scan)
복합 인덱스에서 필요한 키 값만 띄엄띄엄 읽는 방식입니다. 레인지 스캔이 지정된 범위의 모든 레코드를 순서대로 읽는다면, 루즈 인덱스 스캔은 그룹별 대표 값만 골라 읽습니다.

(department, salary) 복합 인덱스가 있을 때 부서별 최고 연봉을 구하는 쿼리를 예로 들어 보겠습니다.
SELECT department, MAX(salary)
FROM employees
GROUP BY department;
EXPLAIN SELECT department, MAX(salary) FROM employees GROUP BY department;
-- type: range
-- Extra: Using index for group-by ← 루즈 인덱스 스캔
인덱스는 (department, salary) 순서로 정렬되어 있습니다. 루즈 인덱스 스캔은 각 department 그룹의 첫 번째 키에서 곧바로 마지막 키(MAX)로 점프해 읽고, 다음 그룹으로 넘어갑니다. 중간의 불필요한 레코드를 건너뛰므로 스캔량이 대폭 줄어듭니다.
루즈 인덱스 스캔은 GROUP BY + 집계 함수(MIN, MAX) 조합에서 주로 활성화됩니다. Extra에 Using index for group-by가 표시되면 이 방식이 선택된 것입니다.
인덱스 스킵 스캔 (Index Skip Scan)
MySQL 8.0에서 도입된 방식으로, 복합 인덱스의 선두 칼럼 조건이 없어도 인덱스를 활용할 수 있게 해줍니다.
단, 스킵 스캔이 동작하려면 커버링 인덱스 조건이 반드시 성립해야 합니다. 선두 칼럼을 건너뛰고 내부적으로 범위를 나눠 스캔하는 특성상, 인덱스 밖으로 나가는 북마크 룩업이 발생하면 옵티마이저가 비용을 통제할 수 없기 때문입니다. 즉, SELECT 절과 WHERE 절의 모든 칼럼이 인덱스 안에 포함되어 있어야 스킵 스캔이 활성화됩니다.
(gender, age) 복합 인덱스가 있을 때, 왼쪽 접두사 규칙에 따르면 gender 조건 없이 age만으로는 이 인덱스를 쓸 수 없습니다. 그러나 gender의 카디널리티가 낮고(예: M, F 두 값) 쿼리가 인덱스 칼럼만 조회한다면, 옵티마이저가 내부적으로 아래처럼 쿼리를 분리해 실행합니다.

-- 실제 쿼리 (SELECT 칼럼이 모두 인덱스 안에 있어야 스킵 스캔 동작)
SELECT gender, age FROM employees WHERE age = 30;
-- 옵티마이저 내부 동작 (개념적 표현)
SELECT gender, age FROM employees WHERE gender = 'M' AND age = 30
UNION ALL
SELECT gender, age FROM employees WHERE gender = 'F' AND age = 30;
EXPLAIN SELECT gender, age FROM employees WHERE age = 30;
-- type: range
-- Extra: Using index for skip scan ← 스킵 스캔
-- SELECT *처럼 인덱스에 없는 칼럼을 조회하면 스킵 스캔 미동작
EXPLAIN SELECT * FROM employees WHERE age = 30;
-- type: ALL ← 풀 테이블 스캔
선두 칼럼의 유니크한 값 수만큼 내부적으로 레인지 스캔을 반복하므로, 선두 칼럼의 카디널리티가 낮을수록 효과적입니다. 카디널리티가 높으면 오히려 불필요한 스캔이 늘어나 역효과가 납니다. 스킵 스캔을 명시적으로 제어하려면 옵티마이저 힌트를 사용합니다.
SELECT /*+ SKIP_SCAN(employees idx_gender_age) */ gender, age FROM employees WHERE age = 30;
SELECT /*+ NO_SKIP_SCAN(employees idx_gender_age) */ gender, age FROM employees WHERE age = 30;
스캔 방식 비교
| 스캔 방식 | EXPLAIN type | EXPLAIN Extra | 주요 사용 조건 |
| 인덱스 레인지 스캔 | range |
— | 범위 조건 (BETWEEN, >, <, IN) |
| 인덱스 풀 스캔 | index |
Using index (커버링 인덱스 시) |
범위 특정 불가, 인덱스로만 결과 구성 |
| 루즈 인덱스 스캔 | range |
Using index for group-by |
GROUP BY + MIN/MAX |
| 인덱스 스킵 스캔 | range |
Using index for skip scan |
선두 칼럼 조건 없음, 선두 칼럼 저카디널리티, 커버링 인덱스 필수 |
커버링 인덱스 (Covering Index)
스캔 방식과 직교하는 개념으로, 쿼리에 필요한 모든 칼럼이 인덱스 안에 포함된 경우를 커버링 인덱스라고 합니다. 어떤 스캔 방식을 쓰든 커버링 인덱스가 성립하면 클러스터형 인덱스로의 북마크 룩업이 생략됩니다.

-- (name, salary) 복합 인덱스가 있을 때
SELECT name, salary FROM employees WHERE name = '홍길동';
EXPLAIN SELECT name, salary FROM employees WHERE name = '홍길동';
-- Extra: Using index ← 커버링 인덱스 (북마크 룩업 없음)
랜덤 I/O를 완전히 제거하기 때문에 성능 향상 효과가 가장 큽니다. 핵심 쿼리가 커버링 인덱스를 활용하도록 인덱스를 설계하는 것이 인덱스 최적화의 최종 목표라고 볼 수 있습니다.
5. 인덱스의 정렬 및 스캔 방향
B-Tree의 리프 노드는 이중 연결 리스트로 연결되어 있습니다. 옵티마이저는 쿼리의 ORDER BY 방향에 따라 인덱스를 정방향(Forward) 또는 역방향(Backward) 으로 스캔합니다.
역방향 스캔은 정방향에 비해 약간의 성능 오버헤드가 있습니다. 페이지 잠금 경합이 더 자주 발생할 수 있고, InnoDB 내부 구조상 정방향 스캔에 최적화되어 있기 때문입니다.
MySQL 8.0 이전에는 내림차순 인덱스가 없어 ORDER BY col DESC 쿼리에서 항상 역방향 스캔이 발생했습니다. MySQL 8.0부터는 칼럼별로 정렬 방향을 지정한 진정한 내림차순 인덱스를 만들 수 있어, 역방향 스캔 오버헤드를 피할 수 있습니다.
-- MySQL 8.0 이상: 내림차순 인덱스
CREATE INDEX idx_salary_desc ON employees (salary DESC);
-- 복합 인덱스에서 방향 혼합 (department 오름차순 조회 후 salary 내림차순 정렬 패턴에 최적)
CREATE INDEX idx_dept_salary ON employees (department ASC, salary DESC);
역방향 스캔이 잦은 쿼리가 있다면 내림차순 인덱스를 따로 생성하는 것이 유리합니다.
6. 인덱스의 가용성과 효율성
인덱스가 존재하더라도 실제로 사용되지 않는 경우가 있습니다. 쿼리를 작성할 때 아래 패턴을 주의해야 합니다.
칼럼을 가공한 경우
인덱스 칼럼에 함수나 연산을 적용하면 옵티마이저가 인덱스를 사용하지 못합니다.
-- ❌ 인덱스 사용 불가 (칼럼에 함수 적용)
SELECT * FROM employees WHERE YEAR(created_at) = 2024;
-- ✅ 인덱스 사용 가능 (칼럼을 그대로 유지)
SELECT * FROM employees WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31';
타입 불일치
칼럼 타입과 비교값의 타입이 다르면 묵시적 형 변환이 발생하고, 인덱스를 사용하지 못할 수 있습니다.
-- phone_number 칼럼이 VARCHAR인 경우
-- ❌ 타입 불일치 (숫자를 문자열 칼럼과 비교 → 형 변환 발생)
SELECT * FROM users WHERE phone_number = 01012345678;
-- ✅ 타입 일치
SELECT * FROM users WHERE phone_number = '01012345678';
부정 조건
NOT IN, !=, <>, NOT LIKE 등의 부정 조건은 인덱스를 효과적으로 사용하기 어렵습니다.
-- ⚠️ 인덱스 효율 낮음
SELECT * FROM employees WHERE department != 'Engineering';
-- ✅ 가능하면 긍정 조건으로 재작성
SELECT * FROM employees WHERE department IN ('Sales', 'Marketing', 'HR');
OR 조건
OR 조건은 각 조건이 모두 인덱스를 사용할 수 있어야 효과적으로 활용됩니다. 하나라도 인덱스가 없으면 풀 테이블 스캔이 발생합니다.
-- department에만 인덱스가 있고 age에는 없는 경우
-- ❌ age 조건 때문에 풀 테이블 스캔
SELECT * FROM employees WHERE department = 'Engineering' OR age = 30;
옵티마이저가 인덱스를 무시하는 경우
위 패턴과 달리, 인덱스도 있고 조건도 적합한데 옵티마이저가 스스로 인덱스를 포기하는 경우도 있습니다. 3절에서 설명한 것처럼, 읽어야 하는 레코드 건수가 임계치를 넘으면 북마크 룩업 비용이 풀 스캔보다 커지기 때문입니다.
이 경우 FORCE INDEX로 인덱스를 강제 지정할 수 있습니다. 다만 데이터 분포가 바뀌면 오히려 역효과가 날 수 있으므로, 통계 갱신(ANALYZE TABLE)을 먼저 시도한 뒤에도 문제가 지속될 때 사용하는 것이 바람직합니다.
SELECT * FROM employees FORCE INDEX (idx_department) WHERE department = 'Engineering';
인덱스 설계 시 고려 사항
인덱스 무력화 패턴을 피하는 것만큼, 처음부터 올바르게 설계하는 것도 중요합니다. 인덱스는 읽기를 빠르게 하지만 쓰기를 느리게 합니다. 모든 INSERT, UPDATE, DELETE마다 관련 인덱스를 함께 갱신해야 하기 때문입니다.
| 고려 사항 | 설명 |
| 쿼리 패턴 분석 | 실제로 자주 실행되는 쿼리를 기준으로 인덱스를 설계한다 |
| 인덱스 수 최소화 | 인덱스가 많을수록 쓰기 성능과 저장 공간 비용이 증가한다 |
| 복합 인덱스 우선 | 여러 단일 인덱스보다 하나의 잘 설계된 복합 인덱스가 효율적일 때가 많다 |
| 커버링 인덱스 목표 | 핵심 쿼리가 북마크 룩업 없이 실행될 수 있도록 인덱스를 구성한다 |
| 실행 계획 확인 | EXPLAIN으로 옵티마이저가 의도한 대로 인덱스를 사용하는지 반드시 확인한다 |
정리
이번 편에서는 MySQL B-Tree 인덱스의 내부 구조부터 실전 활용까지를 살펴보았습니다.
- InnoDB의 클러스터형 인덱스는 테이블 자체이며, 기본 키 범위 검색에 유리하지만 기본 키 크기와 변경 비용에 주의해야 한다.
- 보조 인덱스는 기본 키를 거쳐 두 번 탐색하지만, 보조 인덱스 리프에 PK가 항상 저장되므로 PK 칼럼을 포함한 쿼리는 자동으로 커버링 인덱스로 처리된다.
- 키 삽입 시 페이지 분리, 삭제 시 지연 정리(Purge)라는 방식으로 B-Tree를 유지한다.
- 선택도와 읽어야 하는 레코드 건수가 옵티마이저의 인덱스 사용 여부를 결정짓는 핵심 요소다.
- 복합 인덱스는 왼쪽 접두사 규칙을 따르며, 선택도가 높은 칼럼을 앞에 두는 것이 원칙이다.
- 레인지 스캔, 풀 스캔, 루즈 인덱스 스캔, 스킵 스캔 등 상황에 따라 다른 스캔 방식이 선택된다.
- 커버링 인덱스로 북마크 룩업을 제거하면 가장 큰 성능 이득을 얻을 수 있다.
- 칼럼 가공, 타입 불일치, 부정 조건은 인덱스를 무력화시킨다.
인덱스는 강력한 도구이지만, 잘못 설계하면 오히려 성능을 떨어뜨립니다. EXPLAIN을 습관화하고, 실제 쿼리 패턴에 기반해 인덱스를 설계하는 것이 핵심입니다.
'Data > MySQL' 카테고리의 다른 글
| [MySQL] 트랜잭션과 잠금 2편 — 격리 수준과 MVCC (0) | 2026.06.07 |
|---|---|
| [MySQL] 트랜잭션과 잠금 1편 — 락 메커니즘 (0) | 2026.06.07 |
| [MySQL] 아키텍처 2편 — InnoDB 스토리지 엔진 (0) | 2026.06.07 |
| [MySQL] 아키텍처 1편 — 엔진 아키텍처 (0) | 2026.06.07 |