MySQL 인덱스와 디스크 I/O 이해로 시작하는 쿼리 튜닝 기본

개요 인덱스는 데이터베이스 성능 최적화의 출발점임을 부정하기 어려움 MySQL은 스토리지 엔진에 따라 인덱싱과 검색 방식이 달라질 수 있어 기본 개념을 정확히 이해하는 것이 쿼리 튜닝의 기반이 됨 또한 디스크 접근 특성인 랜덤 I/O와 순차 I/O의 차이는 실무 성능에 직접적인 영향을 주므로 반드시 짚고 가야 함 디스크 I/O가 성능을 좌우하는 이유 CPU와 메모리 성능은 가파르게 개선됐지만 디스크 장치는 물리적 한계로 개선 속도가 상대적으로 느림 DB 성능 튜닝의 많은 부분이 디스크 I/O를 얼마나 줄이느냐로 귀결됨 ...

January 5, 2026

MySQL 클러스터드 인덱스와 논클러스터드 인덱스 이해와 선택 기준

개요 테이블 검색 성능을 끌어올리는 1차 수단은 인덱스 구축임 인덱스는 단일 컬럼 또는 다중 컬럼 기준으로 생성 가능하며, 다음과 같은 기본 생성 규칙이 작동함 Primary Key 지정 시 클러스터드 인덱스 생성 Unique 제약은 보조 인덱스(논클러스터드 인덱스, InnoDB에선 Secondary Index)로 생성 MySQL의 InnoDB는 B+Tree 기반 인덱스를 사용하며, 데이터 저장 구조와 접근 패턴이 인덱스 유형별로 상이함 핵심 개념 정리 클러스터드 인덱스 테이블 데이터가 인덱스 키 순서로 정리되는 구조 테이블당 하나만 존재 리프 노드가 실제 레코드(전체 컬럼)를 보유 InnoDB에서 Primary Key가 곧 클러스터드 인덱스가 됨 PK가 없으면 첫 번째 유니크 not null 인덱스를 사용, 그것도 없으면 내부적으로 보이지 않는 6바이트 Row ID를 생성해 클러스터링 키로 사용 논클러스터드 인덱스(보조 인덱스, Secondary Index) ...

December 14, 2025

VARCHAR(n) 길이 기준 정리 — 글자 수인가 바이트 수인가

개념/배경 VARCHAR(n)에서 n을 글자 수로 볼지 바이트 수로 볼지 혼동 많음 표준 SQL의 character varying(n)은 최대 글자 수 의미이나, 실제 구현은 DBMS와 문자셋 설정에 따라 달라짐 멀티바이트 문자셋에서는 저장 바이트 수와 글자 수가 다름. 길이 제한은 글자 수 기준일 수 있으나 내부 저장은 바이트 단위로 이뤄짐 DBMS별 동작 MySQL VARCHAR(n)에서 n은 글자 수 의미 utf8mb4 사용 시 글자 하나가 최대 4바이트까지 소요. 행 크기 제한 등으로 인해 저장 가능 여부는 바이트 한계에도 영향 받음 PostgreSQL character varying(n)에서 n은 글자 수 의미 저장은 바이트 단위이나 제약은 글자 수 기준으로 평가 SQL Server varchar(n)은 n이 바이트 수. 멀티바이트 문자 사용 시 같은 n이라도 담을 수 있는 글자 수 감소 nvarchar(n)은 n이 글자 수. 유니코드 2바이트 단위 저장. 글자 수 기준 제약 필요 시 nvarchar 사용 권장 Oracle VARCHAR2(n)은 기본이 바이트 기준. 세션/시스템에서 CHAR semantics 또는 컬럼 정의 시 VARCHAR2(n CHAR)로 명시하면 글자 수 기준 실무 팁 한글 100자, 영어 100자 모두 허용 기대라면 글자 수 기준 타입 필요 MySQL VARCHAR(100), PostgreSQL varchar(100), SQL Server에서는 nvarchar(100), Oracle에서는 VARCHAR2(100 CHAR) 선택 저장 바이트 한계 고려 필요. MySQL은 행 크기 한계, Oracle/SQL Server도 페이지 크기 등 제약 존재 길이 함수 차이 주의. 바이트 길이와 글자 길이 함수가 다른 경우 존재. 예를 들어 글자 길이 검증은 문자 길이 함수 사용 권장 이모지, 결합 문자 등 특수 유니코드 조합은 사용자 체감 글자 수와 코드 포인트 수가 다를 수 있음. 제품 요구사항에 맞는 길이 기준 정의 필요 정리 VARCHAR(n)이 항상 바이트 무관이라는 주장은 오해 많은 DBMS에서 n은 글자 수지만, SQL Server의 varchar처럼 바이트 기준인 구현 존재 문자셋과 저장 한계를 함께 고려해야 안정적인 길이 설계 가능 한글도 100글자, 영어도 100글자라는 기대를 보장하려면 글자 수 기준 타입과 설정을 명시적으로 선택할 것 참고자료 https://dev.mysql.com/doc/refman/8.0/en/char.html https://www.postgresql.org/docs/current/datatype-character.html https://learn.microsoft.com/sql/t-sql/data-types/char-and-varchar-transact-sql https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html#GUID-3B0B0A24-FA05-4A1F-902E-2E6D0BF85673

December 5, 2025

InnoDB 복합 인덱스와 Left-most Prefix 원칙 정리

InnoDB에서 여러 컬럼을 묶어 복합 인덱스를 만들면 정의된 순서대로 값을 결합해 B-Tree에 저장됨. 예를 들어 (colA, colB, colC) 인덱스면 colA 기준으로 정렬되고 같은 colA 그룹 내에서 colB, 이어서 colC 순으로 정렬됨. 이 구조 때문에 왼쪽 접두사(Left-most prefix) 원칙이 성립하며, 인덱스 정의의 선두 컬럼부터 조건이 주어질수록 효율이 높아짐 개념과 배경 복합 인덱스는 정의 순서대로 키를 묶어 B-Tree에 저장 InnoDB의 보조 인덱스는 리프에 보조키와 함께 PK를 포함함. PK 길이가 길면 보조 인덱스 크기와 I/O에 직접 영향 left-most prefix 원칙이 핵심. 인덱스의 선두 컬럼부터 연속해서 매칭될 때 탐색 범위가 급격히 줄어듦 동작 방식 예시 (colA, colB) 인덱스 구조 정렬 키 순서: colA → colB → 내부적으로 PK 참조 왼쪽 컬럼 우선 필터링 WHERE colA = ? 또는 WHERE colA = ? AND colB = ? 형태는 인덱스를 온전히 사용 가능 WHERE colB = ?처럼 선두 컬럼 없이 후행 컬럼만 조건일 때는 인덱스 이용이 제한되거나 범위가 매우 넓어짐. 최신 버전에서 옵티마이저가 제한적으로 우회 전략을 쓰는 경우가 있으나 일반적인 기대값으로 두기 어려움 왼쪽 접두사 부분 사용 (colA, colB)에서 colA만 조건으로 사용해도 인덱스 사용 가능 colA와 colB를 모두 조건으로 사용하면 탐색 범위가 더 좁아짐 colB만 조건이면 left-most prefix가 깨져 효과가 급감할 수 있음 정렬과 결합 사용의 기본 인덱스 키 순서와 같은 ORDER BY colA, colB는 추가 정렬 비용 감소에 도움 WHERE 조건이 인덱스 선두를 건너뛰거나 정렬 순서가 키 정의와 다르면 이점이 줄어듦 여러 컬럼을 묶는 이유 다중 조건 검색 성능 향상. 예: WHERE colA = ? AND colB BETWEEN ? AND ?는 (colA, colB)로 빠른 범위 스캔 가능 커버링 인덱스 활용. 예: SELECT colA, colB FROM T WHERE colA = ? AND colB = ?에서 테이블 추가 접근 없이 인덱스만으로 결과 충족 가능 쿼리 패턴 최적화. 실제 WHERE 절에서 자주 함께 쓰는 컬럼을 순서까지 고려해 묶는 설계가 유효. 드물게 쓰이거나 고카디널리티 이점이 없는 컬럼을 무리하게 포함하면 쓰기 비용만 증가 주의할 점 인덱스 정의 순서가 성능을 좌우. 자주 필터링되는 컬럼, 카디널리티가 높은 컬럼을 선두에 두는 것이 일반적 권장 너무 많은 컬럼을 묶으면 인덱스 폭과 관리 비용 증가. 보통 2~3개, 많아도 4개 이하로 목적에 맞게 제한 (A, B, C)일 때 WHERE B = ?처럼 선두가 빠지면 인덱스 사용이 제한. 패턴에 따라 (B, A) 같은 보완 인덱스 검토 InnoDB 보조 인덱스는 PK를 함께 가진다는 점을 고려. PK가 길면 복합 인덱스 크기와 캐시 효율, 쓰기 비용에 불리 ORDER BY, GROUP BY와 결합 시 인덱스 정의 순서와 일치할 때 이점. 중간에 범위 조건이 끼거나 순서가 어긋나면 효과 하락 범위 조건 이후 컬럼 활용 제한 가능. 예를 들어 colA BETWEEN ...가 선두에 오면 그 뒤 colB는 정렬이나 추가 필터에서 기대만큼 활용되지 않을 수 있음 마무리 복합 인덱스는 왼쪽부터 순차적으로 정렬되는 구조이며 left-most prefix 원칙이 적용됨 다중 컬럼 조건, 범위 스캔, 정렬이나 그룹 연산에 유용하지만 인덱스 정의 순서가 쿼리 패턴과 맞지 않으면 이점을 잃음 불필요하게 많은 컬럼을 포함하면 쓰기 부담과 스토리지 사용량만 증가. 실제 트래픽 패턴과 카디널리티를 근거로 최소한의 조합과 올바른 순서를 선택하는 것이 핵심 참고자료 https://dev.mysql.com/doc/refman/8.0/en/multiple-column-indexes.html https://dev.mysql.com/doc/refman/8.0/en/mysql-indexes.html https://dev.mysql.com/doc/refman/8.0/en/innodb-index-types.html https://dev.mysql.com/doc/refman/8.0/en/covering-indexes.html

November 30, 2025

MySQL 테이블 이름 변경 RENAME TABLE vs ALTER TABLE 정리

개요 테이블 이름 변경은 ALTER TABLE로도 가능하지만, RENAME TABLE을 쓰면 다수 테이블을 한 번에 처리 가능하며 같은 서버 내 다른 데이터베이스로 이동까지 가능함 핵심 차이 ALTER TABLE RENAME은 단일 테이블 대상 RENAME TABLE은 여러 테이블을 한 문장으로 변경 가능 스키마 간 이동 지원 current_db.table에서 other_db.table로 변경 가능 동일 트랜잭션처럼 동작하는 원자성 제공, 다중 변경 중 하나라도 실패 시 전체 미적용 권한 요구 사항 존재, 원본 테이블에 ALTER와 DROP, 대상 스키마에 CREATE 권한 필요 트리거와 외래키 메타데이터는 함께 유지되나, 뷰나 저장 프로시저의 하드코딩된 참조는 자동 갱신되지 않음 사용법 단일 테이블 이름 변경 RENAME TABLE old_table TO new_table; 단일 테이블 이름 변경 ALTER 사용 ALTER TABLE old_table RENAME new_table; 다수 테이블 이름 일괄 변경 RENAME TABLE old_table1 TO new_table1, old_table2 TO new_table2, old_table3 TO new_table3; 다른 데이터베이스로 이동 같은 서버 내 RENAME TABLE current_db.table_name TO other_db.table_name;주의 사항 대상 이름이 이미 존재하면 실패 교차 서버 이동 불가, 같은 서버 인스턴스 내 스키마 간 이동만 가능 실행 시 메타데이터 락 획득, 짧은 구간 동안 읽기나 쓰기 대기 가능 배치 변경 전 사전 검증 권장, 이름 충돌 여부와 권한 확인 뷰나 프로시저 참조는 수동 점검 필요, 린트나 간단한 탐색 쿼리로 영향 범위 확인 권장 정리 단일 변경만 필요하면 ALTER로 충분하나, 다수 변경이나 스키마 이동까지 고려하면 RENAME TABLE이 더 실용적 선택 원자적 일괄 변경과 스키마 간 이동을 활용하되, 권한과 의존성 영향 검증을 선행할 것 ...

November 24, 2025

MySQL InnoDB 버퍼 풀 개념과 동작 원리, 크기 설정 가이드

개요 InnoDB 버퍼 풀은 데이터와 인덱스 페이지를 메모리에 캐싱하는 영역임 디스크 I/O를 획기적으로 줄여 지연 시간을 낮추는 게 목적임 InnoDB 스토리지 엔진(트랜잭션, MVCC, 행 단위 락 지원) 성능의 심장부라 할 수 있음 쉽게 말해, 자주 쓰는 데이터와 인덱스를 디스크 대신 메모리에 올려두고 처리하는 구조임 버퍼 풀 구성 요소 버퍼 풀에는 주로 이런 페이지(기본 16KB)가 올라옴 데이터 페이지: 실제 테이블 로우(Row)가 저장된 페이지 인덱스 페이지: B-Tree 인덱스 노드 페이지 (PK 및 세컨더리 인덱스 포함) 기타 관리 페이지: UNDO 페이지, 트랜잭션/MVCC 관리에 필요한 메타데이터 등 핵심 개념 페이지 캐싱 InnoDB는 디스크 데이터를 페이지 단위로 다룸 클라이언트가 특정 로우를 읽고 싶어 하면, 그 로우가 속한 페이지 전체를 버퍼 풀로 가져옴 이후 같은 페이지에 있는 다른 로우를 읽을 때는 디스크를 다시 보지 않고 버퍼 풀(메모리)에서 바로 조회함 ...

October 31, 2025

InnoDB에서 PK 없는 테이블의 동작과 트레이드오프

개념과 배경 InnoDB는 데이터를 클러스터형 인덱스 기준으로 저장하는 스토리지 엔진임 대부분의 테이블에서 이 클러스터형 인덱스는 프라이머리 키가 담당함 사용자가 명시적으로 PK를 정의하지 않은 경우에도 InnoDB는 테이블에 클러스터 키를 반드시 갖도록 함 이때 결정 규칙이 존재함 먼저 NOT NULL 제약을 가진 유니크 인덱스가 있으면 그것을 클러스터형 인덱스로 사용 그런 인덱스가 없으면 내부 6바이트 row_id를 생성해 숨김 PK로 사용 세컨더리 인덱스는 항상 클러스터 키를 포함하여 룩업을 수행함 따라서 명시적 PK가 없더라도 내부적으로는 클러스터 키가 존재하며 저장과 탐색 경로의 기준으로 동작함 ...

October 26, 2025

MySQL EXPLAIN 실행 계획 해석 가이드

개요 EXPLAIN은 SELECT가 어떤 경로로 데이터를 읽는지 드러내는 도구 병목 파악과 인덱스 전략 점검에 사용 핵심 개념 select_type SIMPLE 단순 SELECT, 서브쿼리나 UNION 없음 PRIMARY 가장 바깥쪽 SELECT SUBQUERY 서브쿼리 DERIVED FROM 절의 서브쿼리 UNION UNION의 두 번째 이후 SELECT type 실행 품질 지표, 위에서 아래로 유리 system 테이블에 단 하나의 행 const PRIMARY KEY 또는 UNIQUE 인덱스 단건 조회 eq_ref 조인에서 PRIMARY KEY 또는 UNIQUE로 정확히 1행 매칭 ref 인덱스를 사용한 동등 조건 검색 range 인덱스를 사용한 범위 검색 index 인덱스 전체 스캔 ALL 테이블 전체 스캔, 최악 possible_keys 사용 가능한 인덱스 목록, NULL이면 후보 없음 key 실제 사용된 인덱스, NULL이면 인덱스 미사용 rows 옵티마이저가 읽을 것으로 추정한 행 수, 낮을수록 유리 filtered 조건 후 남는 행 비율 추정치, 높을수록 유리 Extra 추가 단서 Using index 커버링 인덱스 사용, 유리 Using where WHERE 조건으로 필터링 수행 Using filesort 추가 정렬 필요, 비용 큼 Using temporary 임시 테이블 사용, 비용 큼 해석 기준 type이 const, ref, range 범주에 위치 key가 NULL이 아니고 적절한 인덱스 선택 rows 추정치가 작고 filtered 비율이 높음 Extra에 Using filesort, Using temporary 부재 예시와 해석 type: ALL possible_keys: NULL rows: 3527425 Extra: Using where; Using filesort 테이블 전체 스캔으로 많은 행을 읽게 됨 인덱스 후보와 실제 사용 인덱스가 없음 WHERE로 필터링하고 추가 정렬까지 수행하여 비용 상승 현재 계획은 인덱스 설계와 조건식 재검토 필요 주의와 팁 rows와 filtered는 통계 기반 추정치라 실제와 오차 가능 filtered는 대략 rows × filtered%로 남는 행 규모 가늠에 사용 Using filesort가 항상 나쁜 것은 아님, 결과 집합이 매우 작으면 영향 미미할 수 있음 인덱스 생성 시 조건 컬럼의 선택도와 정렬·조인 키 우선 고려 커버링 인덱스 구성 시 Extra의 Using index로 확인 가능 마무리 EXPLAIN은 증상 관찰 도구, 원인은 스키마 설계와 조건식, 인덱스 전략에서 찾기 위 지표를 기준으로 스캔 범위를 줄이고 불필요한 정렬과 임시 테이블을 제거하는 방향으로 개선 시도 권장 ...

October 15, 2025