한 줄 요약: MyISAM 은 MySQL 9.7 에서도
SHOW ENGINES의Support가YES로 살아 있지만, 트랜잭션·외래 키·MVCC·자동 crash recovery 가 모두 없고 잠금 단위가 table 임. 8.0 에서 시스템 테이블이 InnoDB 로 옮겨가면서 default 자리에서 완전히 밀려났고, 9.7 에서는 native partitioning 도 지원하지 않음. 운영 OLTP 에는 부적합하며, 읽기 비중이 압도적이고 트랜잭션이 필요 없는 정적 데이터·로그성 테이블 같은 특수 목적에만 남는 엔진임.
개요
MyISAM 은 MySQL 5.1 까지 default 엔진이었으나 5.5 에서 InnoDB 에 자리를 내줬고, 8.0 부터는 데이터 딕셔너리·시스템 테이블까지 InnoDB 로 이전되면서 운영 환경에서 사실상 빠짐. 9.7 에서도 패키지에 포함돼 있지만 기본값이 아니라 ENGINE=MyISAM 을 명시해야 만들어짐.
이 글은 공식 매뉴얼의 The MyISAM Storage Engine 페이지를 기준으로 MyISAM 의 핵심 특성 — 파일 분리(.MYD / .MYI)·table-level lock·트랜잭션 미지원·외래 키 미지원·crash recovery 수동·partitioning 미지원 — 을 한 페이지로 정리하고, 같은 시나리오를 InnoDB·MyISAM 양쪽에 던져 차이를 9.7 컨테이너에서 직접 확인함.
| 항목 | MyISAM | InnoDB (대조) |
|---|---|---|
| 9.7 default 여부 | NO (Support=YES) | YES (Support=DEFAULT) |
| 트랜잭션 / XA / 세이브포인트 | NO / NO / NO | YES / YES / YES |
| 잠금 단위 | table | row |
| MVCC | NO | YES (undo log 기반) |
| 외래 키 | NO (DDL 문법은 통과되나 silently 사라짐) | YES |
| Crash recovery | 수동 (myisam_recover_options 활성화 필요) | 자동 |
| 데이터 캐시 | NO (인덱스 캐시 key_buffer_size 만) | YES — buffer pool |
| Storage 상한 | 256 TB (테이블 단위) | 64 TB (테이블스페이스 기준) |
| Native partitioning | NO (ERROR 1178) | YES |
| 데이터 파일 | .MYD (data) · .MYI (index) · .sdi (메타) | .ibd (file-per-table) |
| 인덱스 구조 | B+Tree non-clustered (leaf 에 행 포인터) | B+Tree clustered (leaf 에 행 데이터) |
결론
어떤 상황에 적합한가
| 시나리오 | MyISAM 적합성 |
|---|---|
| 일반 OLTP (결제·예약·재고) | 부적합 — 트랜잭션·FK 가 없어 무결성 보장 불가 |
| 동시 쓰기가 많은 워크로드 | 부적합 — table-level lock 으로 직렬화돼 throughput 저하 |
| 읽기 전용에 가까운 정적 데이터 (사전·통계) | 조건부 적합 — myisampack 압축 활용 가능, 트랜잭션 필요 없음 |
| 단순 로그성 append-only 테이블 | 조건부 적합 — concurrent_insert=AUTO 로 SELECT 와 INSERT 가 동시 가능. 다만 운영자가 직접 손봐야 할 경우가 잦음 |
| Full-text 검색만 필요한 테이블 | 굳이 MyISAM 일 필요 없음 — InnoDB 도 5.6 부터 full-text 인덱스 지원 |
| 외래 키·트랜잭션·partitioning 필요 | 부적합 — 모두 지원 안 함 |
핵심은 한 가지 — “트랜잭션·외래 키·자동 복구 중 하나라도 필요하면 MyISAM 을 고르지 말 것”. 위 셋이 모두 필요 없는 경우는 일반 OLTP 에서 거의 없기 때문에, 신규 테이블의 default 선택지에서는 빠짐.
운영 시 기억할 것
ENGINE=MyISAMDDL 에FOREIGN KEY절을 넣어도 파서는 통과시키지만 제약이 적용되지 않음. 같은 DDL 텍스트가 엔진에 따라 동작이 달라지는 함정임.- MyISAM 테이블에
START TRANSACTION; ... ROLLBACK;를 던져도 변경이 그대로 남음. MySQL 은Warning 1196(“Some non-transactional changed tables couldn’t be rolled back”) 만 띄우고 에러는 내지 않으므로, 경고를 점검하지 않는 클라이언트는 트랜잭션이 동작했다고 오해하기 쉬움. - 비정상 종료 후 인덱스 파일 (
.MYI) 이 깨질 수 있음.myisam_recover_options변수가OFF(기본) 이면 다음 기동에서 자동 복구가 시도되지 않으므로 운영 시 명시적으로 켜는 편이 안전함. 깨진 테이블은myisamchk또는mysqlcheck --repair로 복구함. - 9.7 에서 MyISAM 은 native partitioning 을 지원하지 않음 (
ERROR 1178). 5.7 이전에 만든 partitioned MyISAM 테이블은 9.x 에서 사용 불가능하므로 마이그레이션 시 InnoDB 변환이 사실상 강제됨. - MyISAM 테이블이 아직 남아 있는 레거시 시스템에서 InnoDB 로 옮길 때는
ALTER TABLE <t> ENGINE=InnoDB가 가장 단순함. 단, 외래 키 보강·PK 추가·인덱스 재설계는 별도 작업이 됨.
상세
1. 파일 구조 — .MYD / .MYI / .sdi 분리
InnoDB 가 테이블당 .ibd 한 파일에 데이터·인덱스를 같이 담는 것과 달리 MyISAM 은 세 파일로 분리됨.
| 파일 | 내용 |
|---|---|
*.MYD | MyData — 행 데이터 본문 |
*.MYI | MyIndex — B+Tree 인덱스(인덱스 leaf 에 PK 가 아닌 데이터 파일 내 행 포인터가 들어감) |
*.sdi | Serialized Dictionary Information — 8.0 이후 MyISAM 테이블의 메타데이터(.frm 대체) |
8.0 에서 데이터 딕셔너리가 InnoDB 로 옮겨갔지만, MyISAM 자체 메타는 여전히 데이터 디렉터리에 .sdi 라는 직렬화된 JSON 파일로 남음 — .frm 파일이 사라진 8.0+ 환경에서 .MYD/.MYI 만 복사해 다른 인스턴스로 옮기는 시도가 깨지는 이유가 이 메타 파일에 있음.
2. Non-clustered 인덱스 — leaf 가 행 포인터를 가짐
MyISAM 은 InnoDB 와 달리 non-clustered 구조임. 데이터는 .MYD 에 INSERT 순서대로 쌓이고, 인덱스 (.MYI) 의 leaf 노드에는 그 행의 데이터 파일 내 위치(row pointer) 가 들어감. PK 도 보조 인덱스도 동일하게 leaf 가 데이터 자체를 갖지 않음.
| 인덱스 타입 | leaf 에 들어가는 것 | I/O 특성 |
|---|---|---|
| Primary key | 데이터 파일 내 행 포인터 | 인덱스 → 데이터 파일 random access 가 항상 한 번 더 필요 |
| Secondary | 데이터 파일 내 행 포인터 | PK 와 동일한 비용 모델 — InnoDB 처럼 PK lookup 추가 단계가 없음 |
이 구조 덕분에 MyISAM 은 PK 와 보조 인덱스의 비용이 같음 — InnoDB 에서 보조 인덱스가 PK lookup 한 번 더 거치는 부담이 여기에는 없음. 다만 데이터·인덱스 캐시가 분리돼 있고 (MyISAM 은 key_buffer_size 로 인덱스만 캐싱), 데이터 파일 자체는 OS page cache 에 의존함.
3. Table-level lock — 동시성 모델의 한계
MyISAM 은 잠금 단위가 table 임. INSERT/UPDATE/DELETE 가 일어나는 동안 같은 테이블에 대한 다른 쓰기 (그리고 일부 읽기) 가 직렬화됨. 동시 쓰기가 많은 OLTP 에서는 throughput 이 빠르게 무너지는 원인임.
concurrent_insert 변수로 일부 완화는 가능함 — 9.7 기본값 AUTO 에서는 데이터 파일 내부에 free block 이 없는 경우(즉 DELETE 로 빈 공간이 안 생긴 경우) 에 한해 SELECT 와 INSERT 가 동시에 가능함. 그래도 INSERT 끼리, UPDATE/DELETE 와는 여전히 직렬화됨.
4. 트랜잭션 없음 — ROLLBACK 이 no-op
MyISAM 은 ACID 모델을 구현하지 않음. START TRANSACTION; ... ROLLBACK; 구문 자체는 파서가 통과시키지만 변경은 즉시 반영되고 ROLLBACK 으로 되돌릴 수 없음. MySQL 은 Warning 1196 (“Some non-transactional changed tables couldn’t be rolled back”) 을 함께 띄워 주긴 하지만 에러가 아니라 경고 수준이라, 경고를 명시적으로 점검하지 않는 클라이언트는 트랜잭션이 동작했다고 오해하기 쉬움.
이 한계가 외래 키 미지원과 함께 묶여서 MyISAM 을 OLTP 에서 사실상 못 쓰게 만든 핵심 이유임.
5. Crash recovery — 자동 아님
InnoDB 가 redo / undo 로그로 비정상 종료 후 자동 복구하는 반면, MyISAM 은 그런 메커니즘이 없음. 비정상 종료 시 진행 중이던 변경이 데이터 파일에 부분적으로 쓰였거나 인덱스가 데이터와 어긋난 상태로 남을 수 있음.
운영자가 켜야 할 보호장치가 myisam_recover_options 변수임. 이 변수가 활성화돼 있으면 mysqld 는 기동 시 정상 종료 표시가 없는 MyISAM 테이블을 자동 점검·복구함. 9.7 기본값은 OFF 이므로 명시적으로 BACKUP,FORCE 같은 값을 my.cnf 에 박아야 안전함.
[mysqld]
myisam_recover_options = BACKUP,FORCE6. Native partitioning 미지원
5.7 까지는 MyISAM 도 partitioning 이 가능했지만, 8.0 부터 partitioning 이 storage engine 의 native 기능으로 옮겨가면서 InnoDB / NDB 만 지원 대상이 됐음. 9.7 에서 ENGINE=MyISAM PARTITION BY ... 를 시도하면 즉시 실패함.
5.7 이전에 만든 partitioned MyISAM 테이블이 데이터 디렉터리에 남아 있는 채로 8.0+ 로 업그레이드를 시도하면 업그레이드가 거부되므로, 마이그레이션 전에 ALTER TABLE <t> REMOVE PARTITIONING 또는 ALTER TABLE <t> ENGINE=InnoDB 로 정리하는 절차가 필요함.
테스트
테스트 환경: MySQL 9.7.0 (
mysql:9.7이미지). 9.7 은 2026-04-21 출시된 현재 LTS 라인이며, 8.4 LTS 와 병행 지원됨. InnoDB·MyISAM 테이블을 같은 인스턴스에 만들어 동일한 SQL 시나리오로 비교함.
1. SHOW ENGINES — MyISAM 은 살아 있지만 default 가 아님
SHOW ENGINES;Engine Support Comment
InnoDB DEFAULT Supports transactions, row-level locking, and foreign keys
MyISAM YES MyISAM storage engine
MRG_MYISAM YES Collection of identical MyISAM tables
...MyISAM 의 Support 컬럼이 YES (사용 가능) 이지만 DEFAULT 는 InnoDB 가 가지고 있음. ENGINE= 절을 생략하면 InnoDB 로 만들어지므로 MyISAM 을 쓰려면 명시해야 함.
2. ROLLBACK no-op — 트랜잭션 흉내가 통과하나 되돌려지지 않음
같은 시나리오를 InnoDB / MyISAM 양쪽에 던짐.
-- InnoDB
CREATE TABLE innodb_demo (id INT PRIMARY KEY, name VARCHAR(50));
INSERT INTO innodb_demo VALUES (1, 'a'), (2, 'b');
START TRANSACTION;
INSERT INTO innodb_demo VALUES (3, 'c');
ROLLBACK;
SELECT * FROM innodb_demo;
-- MyISAM
CREATE TABLE myisam_demo (id INT PRIMARY KEY, name VARCHAR(50)) ENGINE=MyISAM;
INSERT INTO myisam_demo VALUES (1, 'a'), (2, 'b');
START TRANSACTION;
INSERT INTO myisam_demo VALUES (3, 'c');
ROLLBACK;
SELECT * FROM myisam_demo;
SHOW WARNINGS;-- innodb_demo
id name
1 a
2 b
-- myisam_demo
id name
1 a
2 b
3 c
-- SHOW WARNINGS (MyISAM)
Level Code Message
Warning 1196 Some non-transactional changed tables couldn't be rolled backInnoDB 는 ROLLBACK 으로 id=3 이 사라졌지만 MyISAM 은 row 가 그대로 남음. MySQL 이 Warning 1196 — Some non-transactional changed tables couldn't be rolled back 을 띄워 주긴 하지만 에러가 아닌 경고 수준 이라 클라이언트가 명시적으로 SHOW WARNINGS 를 확인하지 않으면 그대로 흘려보내기 쉬움. ROLLBACK 이 사실상 no-op 으로 통과한 결과는 동일함.
3. 외래 키 — DDL 은 통과, 제약은 사라짐
CREATE TABLE myisam_fk_parent (id INT PRIMARY KEY) ENGINE=MyISAM;
CREATE TABLE myisam_fk_child (id INT PRIMARY KEY, p_id INT,
FOREIGN KEY (p_id) REFERENCES myisam_fk_parent(id)) ENGINE=MyISAM;
INSERT INTO myisam_fk_parent VALUES (1);
INSERT INTO myisam_fk_child VALUES (10, 99); -- 부모에 99 없음
SELECT * FROM myisam_fk_child;
SHOW CREATE TABLE myisam_fk_child;id p_id
10 99
CREATE TABLE `myisam_fk_child` (
`id` int NOT NULL,
`p_id` int DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `p_id` (`p_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ciINSERT 는 거부되지 않고 그대로 들어감. SHOW CREATE TABLE 결과를 보면 FOREIGN KEY 절이 정의에서 사라졌고 보조 인덱스 (KEY p_id) 만 남았음 — MyISAM 은 FK 문법을 받아들이되 제약을 만들지 않고 인덱스만 보존함. 같은 DDL 을 InnoDB 에 넣었을 때 발생하는 ERROR 1452 와 비교됨 (innodb-storage-engine-overview 의 FK 테스트).
4. 파일 구조 — .MYD / .MYI / .sdi
ls -la /var/lib/mysql/testdb/ | grep -E 'demo'innodb_demo.ibd # InnoDB: 단일 파일
myisam_demo.MYD # MyISAM: 데이터
myisam_demo.MYI # MyISAM: 인덱스
myisam_demo_377.sdi # MyISAM: 직렬화된 메타데이터InnoDB 는 innodb_demo.ibd 한 파일에 데이터·인덱스가 같이 담겼고, MyISAM 은 데이터·인덱스·메타가 세 파일로 갈라져 있음. _377 같은 suffix 는 MyISAM 테이블의 데이터 딕셔너리 객체 ID 임.
5. COUNT(*) 실행 계획 — MyISAM 은 메타에서 즉시 답함
MyISAM 은 테이블 전체 row 수를 헤더에 유지하므로 WHERE 없는 COUNT(*) 는 풀 스캔이 필요 없음. InnoDB 는 MVCC 때문에 트랜잭션 시점에 따라 보이는 row 수가 다를 수 있어 매번 세야 함.
EXPLAIN FORMAT=TREE SELECT COUNT(*) FROM cnt_innodb;
EXPLAIN FORMAT=TREE SELECT COUNT(*) FROM cnt_myisam;-- InnoDB
-> Count rows in cnt_innodb
-- MyISAM
-> Rows fetched before execution (cost=0..0 rows=1)InnoDB 의 Count rows in <table> 는 실제로 인덱스를 훑어 row 를 세는 동작이고, MyISAM 의 Rows fetched before execution 은 옵티마이저가 메타 통계로 미리 답을 정해버린 것임. 정적 통계 카운터가 필요한 경우에만 MyISAM 의 이점이 드러나는 좁은 영역임.
6. Native partitioning — 9.7 에서는 거부됨
CREATE TABLE part_myisam (id INT PRIMARY KEY, v INT) ENGINE=MyISAM
PARTITION BY HASH(id) PARTITIONS 4;ERROR 1178 (42000): The storage engine for the table doesn't support native partitioning8.0 에서 partitioning 이 storage engine 책임으로 옮겨가면서 InnoDB / NDB 만 native partitioning 을 구현했고, MyISAM 은 그 대상에 빠짐. 5.7 이전 partitioned MyISAM 테이블을 들고 있는 환경은 9.x 업그레이드 전에 정리해야 함.
참고
- MySQL 9.7 — The MyISAM Storage Engine — MyISAM 기능 표·
.MYD/.MYI파일·table-level lock·storage 상한 - MySQL 9.7 — MyISAM Startup Options —
myisam_recover_options·key_buffer_size·concurrent_insert등 MyISAM 전용 변수 - MySQL 9.7 — MyISAM Table Storage Formats — Static / Dynamic / Compressed 세 가지 row 포맷 차이
- MySQL 9.7 — MyISAM Table Problems — 인덱스 파일 손상 사례·정상 종료 표시·복구 조건
- MySQL 9.7 — Introduction to InnoDB — 비교 대상인 default 엔진 ACID·FK·crash recovery 정의
- MySQL 9.7 —
myisamchk— 깨진 MyISAM 테이블 점검·수동 복구 도구 - MySQL 9.7 —
myisampack— read-only 압축 MyISAM 테이블 생성 도구 - MySQL 9.7 — Partitioning Limitations Relating to Storage Engines — MyISAM 의 native partitioning 미지원 명시