一、MyISAM索引结构
1. 核心特点
MyISAM采用非聚簇索引:索引和数据完全分离,索引文件和数据文件独立存储。
文件结构:
users.frm ← 表结构定义
users.MYD ← 数据文件(MyData)
users.MYI ← 索引文件(MyIndex)
2. 数据文件(.MYD)
【数据文件结构】
users.MYD:
+------------------+
| Offset: 0x0000 | ← 第1行数据
| [id=1, name='张三', age=25, city='北京', email='zhang@a.com']
+------------------+
| Offset: 0x0100 | ← 第2行数据
| [id=2, name='李四', age=30, city='上海', email='li@a.com']
+------------------+
| Offset: 0x0200 | ← 第3行数据
| [id=3, name='王五', age=22, city='广州', email='wang@a.com']
+------------------+
| ... |
+------------------+
特点:
- 完整的数据行按插入顺序存储
- 每行有固定的物理地址(Offset)
- 与索引无关,独立存储
- 类似堆文件(Heap File)
3. 主键索引(.MYI)
【主键索引结构】
users.MYI(主键索引部分):
Root
[2, 4, 6, 8]
/ | | \
[1,2] [3,4] [5,6] [7,8,9]
↓ ↓ ↓ ↓
叶子节点(存储:键值 + 指针)
叶子节点内容:
+----------------+
| id=1 | 0x0000 | ← 指向数据文件Offset
+----------------+
| id=2 | 0x0100 |
+----------------+
| id=3 | 0x0200 |
+----------------+
| id=4 | 0x0300 |
+----------------+
特点:
✅ B+树结构(和InnoDB一样)
✅ 叶子节点存储:键值 + 数据文件的物理指针
✅ 主键索引不包含数据,只包含指针
✅ 需要通过指针访问数据文件
4. 二级索引(.MYI)
【二级索引结构】
users.MYI(name索引部分):
Root
[李, 王, 赵]
/ | \
[张,李] [王,吴] [赵,钱]
↓ ↓ ↓
叶子节点(存储:键值 + 指针)
叶子节点内容:
+--------------------+
| name='张三' | 0x0000 | ← 指向数据文件Offset
+--------------------+
| name='李四' | 0x0100 |
+--------------------+
| name='王五' | 0x0200 |
+--------------------+
特点:
✅ 结构和主键索引完全相同
✅ 叶子节点存储:索引列值 + 数据文件的物理指针
✅ 不存储主键值(和InnoDB不同)
✅ 直接指向数据,无需回表到主键索引
5. 查询过程
主键查询
SELECT * FROM users WHERE id = 3;
执行步骤:
1. 在主键索引(.MYI)中查找
Root → 中间节点 → 叶子节点
找到:[id=3, 指针=0x0200]
2. 根据指针访问数据文件(.MYD)
读取 Offset 0x0200
获取完整数据:[id=3, name='王五', age=22, ...]
磁盘IO:
- 索引查询:2-3次IO
- 数据访问:1次IO
- 总计:3-4次IO
对比InnoDB:
- InnoDB聚簇索引:2-3次IO(数据在索引中)
- MyISAM:3-4次IO(需要额外访问数据文件)
- MyISAM略慢
二级索引查询
SELECT * FROM users WHERE name = '张三';
执行步骤:
1. 在二级索引(.MYI)中查找
找到:[name='张三', 指针=0x0000]
2. 根据指针访问数据文件(.MYD)
读取 Offset 0x0000
获取完整数据:[id=1, name='张三', age=25, ...]
磁盘IO:
- 索引查询:2-3次IO
- 数据访问:1次IO
- 总计:3-4次IO
对比InnoDB:
- InnoDB二级索引:
- 二级索引查询:2-3次IO
- 回表到聚簇索引:2-3次IO
- 总计:4-6次IO
- MyISAM:3-4次IO(直接指针访问)
- MyISAM二级索引更快 ✅
6. MyISAM vs InnoDB索引对比
【MyISAM】
主键索引:[id, 指针] → 数据文件
二级索引:[name, 指针] → 数据文件
特点:
- 主键和二级索引地位平等
- 都存储物理指针
- 一次指针跳转访问数据
【InnoDB】
主键索引:[id, 完整数据](聚簇索引)
二级索引:[name, 主键id] → 主键索引 → 完整数据
特点:
- 主键索引特殊(聚簇索引)
- 二级索引存储主键值
- 需要二次索引查询(回表)
查询性能对比:
主键查询:
- InnoDB:稍快(数据在索引中)
- MyISAM:稍慢(需要访问数据文件)
二级索引查询:
- InnoDB:较慢(需要回表)
- MyISAM:较快(直接指针访问)
范围查询:
- InnoDB:快(数据连续存储)
- MyISAM:慢(数据可能分散)
二、MyISAM的优势
1. 二级索引性能好
-- 二级索引查询
SELECT * FROM users WHERE email = 'zhang@a.com';
MyISAM:
- 一次索引查询
- 一次数据文件访问
- 总计:3-4次IO ✅
InnoDB:
- 二级索引查询
- 回表到主键索引
- 总计:4-6次IO ⚠️
在二级索引查询场景,MyISAM快约50%
2. 索引空间小
【对比】100万行数据
InnoDB:
- 聚簇索引(主键):包含完整数据,约500MB
- 二级索引(name):包含name+主键id(8字节),约40MB
- 总计:540MB
MyISAM:
- 数据文件(.MYD):完整数据,约500MB
- 主键索引(.MYI):包含id+指针(6字节),约15MB
- name索引(.MYI):包含name+指针(6字节),约30MB
- 总计:545MB
索引部分:
- InnoDB索引:40MB(二级索引)
- MyISAM索引:30MB(二级索引)
MyISAM索引略小(因为存指针而非主键值)
3. COUNT(*) 极快
SELECT COUNT(*) FROM users;
MyISAM:
- 存储了表的总行数
- 直接返回
- 耗时:0.001秒 ✅
InnoDB:
- 需要扫描索引统计
- 或全表扫描
- 耗时:0.1-10秒 ⚠️
在无WHERE条件的COUNT(*),MyISAM快数千倍
4. 表压缩
-- MyISAM支持表压缩
myisampack users.MYI
特点:
- 只读表
- 压缩比:3:1 到 10:1
- 节省存储空间
- 适合历史数据归档
InnoDB:
- 支持表压缩(COMPRESSED)
- 但压缩效果不如MyISAM
- 有写入性能损耗
三、MyISAM存在的问题
1. 不支持事务(最致命)
-- 问题:无法回滚
START TRANSACTION;
-- 转账操作
UPDATE accounts SET balance = balance - 1000 WHERE id = 1; -- 扣款成功
UPDATE accounts SET balance = balance + 1000 WHERE id = 2; -- 假设失败
ROLLBACK; -- ❌ 无法回滚!
-- 结果:
-- 账户1扣了1000元
-- 账户2没有加钱
-- 数据不一致 ❌
影响:
❌ 无法保证数据一致性
❌ 不适合金融、订单等业务
❌ 批量操作中途失败,部分数据已修改
❌ 无法实现原子性操作
2. 表级锁导致并发性能差
-- 问题:表锁阻塞
-- 会话1
UPDATE users SET age = 26 WHERE id = 1;
-- 锁定整张表 🔒
-- 会话2
UPDATE users SET age = 31 WHERE id = 999; -- 不同的行
-- ⏳ 等待表锁释放(即使操作不同的行)
-- 会话3
SELECT * FROM users WHERE id = 500;
-- ⏳ 读也要等待(如果会话1持有写锁)
影响:
❌ 高并发场景性能极差
❌ 写操作阻塞所有其他操作
❌ 并发度低
❌ 不适合OLTP(在线事务处理)
对比:
InnoDB行锁:
- 会话1锁id=1
- 会话2可以立即修改id=999
- 会话3可以立即读取id=500
- 并发性能高 ✅
3. 崩溃后数据损坏风险
问题:无自动恢复机制
场景:服务器突然断电
MyISAM:
1. 数据文件可能损坏(写入一半)
2. 索引文件可能不一致
3. 表标记为"crashed"
4. 需要手动修复:
CHECK TABLE users;
REPAIR TABLE users;
5. 修复可能失败
6. 可能丢失数据
7. 修复耗时长(大表数小时)
InnoDB:
1. 重启后自动恢复
2. 读取redo log
3. 重做已提交事务
4. 回滚未提交事务
5. 秒级到分钟级恢复
6. 无数据丢失 ✅
7. 无需人工介入 ✅
影响:
❌ 数据可靠性差
❌ 需要人工介入
❌ 可能业务中断
❌ 不适合核心业务
4. 物理指针的维护问题
问题:数据移动时,所有索引失效
场景1:碎片整理
OPTIMIZE TABLE users;
过程:
1. 重新组织数据文件(消除碎片)
2. 数据的物理位置改变
3. 所有索引的指针失效
4. 需要重建所有索引
5. 耗时长,表不可用
InnoDB:
- 数据移动时,主键不变
- 二级索引存储主键值
- 二级索引无需更新 ✅
场景2:页分裂(如果支持的话)
- 数据移动
- 索引指针失效
- 需要更新
影响:
❌ 维护成本高
❌ 碎片整理慢
❌ 影响在线业务
5. 无MVCC,读写阻塞
-- 问题:读写互斥
-- 会话1(写入)
UPDATE users SET age = 26 WHERE id = 1;
-- 持有表级写锁
-- 会话2(读取)
SELECT * FROM users WHERE id = 999; -- 不同的行
-- ⏳ 等待锁释放
-- 原因:
-- 写锁是表级的
-- 读和写互斥
-- 即使访问不同的行
InnoDB:
-- 会话1
UPDATE users SET age = 26 WHERE id = 1;
-- 会话2
SELECT * FROM users WHERE id = 999;
-- ✅ 立即执行(MVCC,读旧版本)
影响:
❌ 读写性能差
❌ 高并发场景不可用
❌ 响应时间长
6. 外键不支持
-- 定义外键(语法通过,但不生效)
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT,
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE CASCADE
) ENGINE=MyISAM;
-- 删除用户
DELETE FROM users WHERE id = 100;
-- 问题:
-- orders表中user_id=100的记录仍然存在
-- 外键约束没有生效
-- 可能产生脏数据(孤儿记录)
影响:
❌ 无法保证参照完整性
❌ 需要应用层手动维护
❌ 容易产生脏数据
❌ 数据一致性差
7. 数据文件碎片化
问题:DELETE后产生空洞
操作序列:
1. INSERT 1000行
2. DELETE 500行(随机)
3. 数据文件产生空洞
数据文件状态:
+----------+
| 数据 |
+----------+
| 空洞 | ← DELETE产生
+----------+
| 数据 |
+----------+
| 空洞 |
+----------+
| 数据 |
+----------+
影响:
- 文件大小不会减小
- 顺序扫描需要跳过空洞
- 性能下降
- 需要定期OPTIMIZE TABLE
-- 碎片整理
OPTIMIZE TABLE users;
问题:
❌ 耗时长(大表数小时)
❌ 需要锁表(阻塞业务)
❌ 需要额外空间(临时表)
InnoDB:
- 页级管理,碎片少
- 在线碎片整理
- 影响较小 ✅
8. 不适合频繁更新
-- 问题:更新产生碎片
-- 场景:频繁更新VARCHAR字段
UPDATE users SET name = 'Very Long Name...' WHERE id = 1;
MyISAM处理:
1. 如果新数据大于原数据
2. 原位置不够
3. 在文件末尾写入新数据
4. 原位置标记为空洞
5. 更新索引指针
影响:
❌ 数据文件碎片化
❌ 文件大小膨胀
❌ 索引需要更新
❌ 性能下降
InnoDB:
- 页内更新
- 或页分裂
- 更新不改变主键
- 二级索引无需更新(存主键值)
- 碎片管理更好 ✅
9. 大表操作问题
问题:大表维护困难
场景:10亿行数据表
问题1:REPAIR TABLE
- 耗时:数小时到数天
- 期间表不可用
- 业务中断
问题2:OPTIMIZE TABLE
- 需要2倍磁盘空间(临时表)
- 耗时极长
- 锁表
问题3:备份恢复
- 文件级备份:不一致
- 逻辑备份:超慢
问题4:在线DDL
- 不支持
- 需要锁表
InnoDB:
- 在线DDL(ALTER TABLE)
- 增量备份
- 自动崩溃恢复
- 维护成本低 ✅
四、为什么MyISAM被淘汰
1. 现代应用需求
2000年代早期:
- 网站流量小
- 并发低
- 读多写少
- MyISAM够用 ✅
2010年代至今:
- 高并发(万级QPS)
- 大量写入
- 事务需求
- 数据可靠性要求高
- MyISAM不适用 ❌
2. 致命缺陷
1. 无事务
→ 数据一致性无法保证
→ 金融、电商等核心业务无法使用
2. 表锁
→ 并发性能差
→ 高并发场景崩溃
3. 崩溃不恢复
→ 数据可靠性差
→ 需要人工修复
→ 业务中断
这三点在现代应用中不可接受
3. InnoDB的崛起
MySQL 5.5(2010年):
- InnoDB成为默认引擎
- 事务、行锁、MVCC
- 自动崩溃恢复
- 性能优化
MySQL 5.6+:
- InnoDB全面优化
- 全文索引支持
- 在线DDL
- 性能超越MyISAM
MySQL 8.0:
- 完全淘汰MyISAM
- 移除大部分MyISAM相关代码
4. 性能对比(现代应用)
测试场景:电商订单系统
并发:100线程
操作:80%读 + 20%写
数据量:1000万行
InnoDB:
- QPS:15000
- TPS:3000
- 响应时间:10-50ms
- 稳定 ✅
MyISAM:
- QPS:2000(写阻塞读)
- TPS:500
- 响应时间:100-5000ms(波动大)
- 不稳定 ❌
结论:InnoDB快7.5倍
五、MyISAM的遗留使用场景
仅剩的适用场景
-- 1. 历史数据归档(只读)
CREATE TABLE archive_logs_2024 (
id BIGINT PRIMARY KEY,
message TEXT,
create_time DATETIME
) ENGINE=MyISAM;
-- 只插入,不修改
INSERT INTO archive_logs_2024 SELECT * FROM logs WHERE YEAR(create_time) = 2024;
-- 只读查询
SELECT * FROM archive_logs_2024 WHERE ...;
优势:
✅ COUNT(*)快
✅ 可以压缩(myisampack)
✅ 节省空间
-- 2. 临时表(中间结果)
CREATE TEMPORARY TABLE temp_stats (
...
) ENGINE=MyISAM;
-- 3. 全文索引(MySQL 5.6之前)
-- 现在InnoDB也支持,不再需要MyISAM
不应使用MyISAM的场景
❌ 订单系统
❌ 支付系统
❌ 用户账户
❌ 库存管理
❌ 任何需要事务的场景
❌ 高并发读写
❌ 核心业务
❌ 频繁更新的数据
结论:新项目不要使用MyISAM
六、迁移建议
从MyISAM迁移到InnoDB
-- 1. 评估影响
SELECT
table_name,
engine,
table_rows,
data_length,
index_length
FROM information_schema.tables
WHERE table_schema = 'your_db'
AND engine = 'MyISAM';
-- 2. 备份
mysqldump -u root -p --single-transaction your_db > backup.sql
-- 3. 转换引擎
ALTER TABLE tablename ENGINE=InnoDB;
-- 批量转换
SELECT CONCAT('ALTER TABLE ', table_name, ' ENGINE=InnoDB;')
FROM information_schema.tables
WHERE table_schema = 'your_db' AND engine = 'MyISAM';
-- 4. 验证
-- 4.1 检查主键
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'your_db'
AND table_name NOT IN (
SELECT table_name FROM information_schema.key_column_usage
WHERE table_schema = 'your_db' AND constraint_name = 'PRIMARY'
);
-- 4.2 添加主键(如果缺失)
ALTER TABLE tablename ADD PRIMARY KEY (id);
-- 4.3 测试COUNT(*)性能
-- 如果变慢,考虑维护计数器表
-- 5. 调整配置
# my.cnf
innodb_buffer_pool_size = 8G
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 1
七、面试要点总结
MyISAM索引结构
特点:
- 非聚簇索引
- 索引和数据分离
- 索引叶子节点存储物理指针
- 主键和二级索引结构相同
文件:
- .frm:表结构
- .MYD:数据文件
- .MYI:索引文件
查询:
- 索引查询获得物理指针
- 根据指针访问数据文件
- 二级索引无需回表(比InnoDB快)
MyISAM的问题
致命缺陷:
- 不支持事务(无法保证数据一致性)
- 表级锁(并发性能差)
- 崩溃不自动恢复(需要手动修复)
其他问题:
- 无MVCC(读写阻塞)
- 不支持外键
- 数据碎片化
- 物理指针维护成本高
- 大表维护困难
为何被淘汰
现代应用需求:
✅ 事务支持
✅ 高并发
✅ 数据可靠性
✅ 自动恢复
MyISAM:
❌ 不支持事务
❌ 表锁,低并发
❌ 崩溃需手动修复
❌ 不适合现代应用
InnoDB:
✅ 完美满足需求
✅ MySQL 5.5+默认引擎
一句话总结
MyISAM采用非聚簇索引,索引和数据分离,索引存储物理指针可以直接访问数据,二级索引无需回表,但其致命缺陷是不支持事务、只有表级锁导致并发性能差、崩溃后无自动恢复机制,这些问题使其无法满足现代高并发事务型应用的需求,已被InnoDB取代,MySQL 5.5+默认使用InnoDB。