MyISAM 只读的核心瓶颈不在事务和锁,而在于索引缓存是否足够大以覆盖所有索引,以及数据文件是否依赖 OS 文件系统缓存。以下方案按优先级排列。
一、MySQL 层:MyISAM 只读的核心优化
1. 把 key_buffer_size 设到足够覆盖所有索引(P0)
MyISAM 只缓存索引(.MYI 文件),数据本身依赖 OS 文件系统缓存。在只读场景下,如果 key_buffer_size 能把所有索引页装进内存,索引相关的磁盘 I/O 几乎可以降为零。
建议值:物理内存的 25%–33%,但必须留出足够空间给 OS 文件系统缓存来缓存数据文件。如果设得过大(如超过内存 50%),系统可能开始换页,反而极慢。
判断是否够用的方法:查看 Key_reads / Key_read_requests 比值,正常应 小于 0.01。如果接近或大于 0.01,说明索引缓存不够,大量索引读取穿透到了磁盘。
bash
SHOW STATUS LIKE 'Key_read%';
# Key_reads / Key_read_requests 比值应 < 0.01
同时用 Key_blocks_unused 判断是否设得过大:如果该值很大,说明缓存空闲过多,可以适当调小,把内存让给 OS 文件系统缓存。
2. 用 myisampack 压缩只读表(P0/P1)
如果表今后只读,用 myisampack 压缩是 MyISAM 只读场景最直接的 I/O 优化手段。压缩后的表占用磁盘空间更小,全表扫描和顺序读取时的磁盘 I/O 量显著降低。
bash
myisampack /path/to/db/table_name
注意:myisampack 后的表只读,不能更新。既然场景本来就是只读,这完全匹配。压缩会牺牲少量解压 CPU 开销,但换来的 I/O 减少在磁盘瓶颈场景下通常收益更大。
3. 将索引文件和数据文件分散到不同磁盘(P1)
MyISAM 允许把 .MYI(索引)和 .MYD(数据)文件通过符号链接放到不同物理磁盘上,从而增加可用磁盘主轴数量,减少寻道争抢。
bash
# 示例:把索引文件移到另一块盘
ln -s /disk2/mysql_indexes/table_name.MYI /var/lib/mysql/db/table_name.MYI
如果使用 RAID,条带化也能达到类似效果。
4. 确认表无“空洞”,保持并发读能力(P1)
MyISAM 虽然只有表级锁,但只读场景下多个 SELECT 可以并行,不会互相阻塞。前提是表中没有因删除/更新产生的“空洞”(中间空闲块)。如果存在空洞,MyISAM 的并发插入能力会被禁用,但纯只读场景不受此影响——空洞只影响读写混合时的并发插入。
如果表曾经有过删除操作,建议执行一次整理,消除碎片,使数据和索引文件更紧凑,减少全表扫描的 I/O:
sql
OPTIMIZE TABLE table_name;
5. 定期 ANALYZE TABLE(P2)
只读表的数据分布不会变化,但优化器需要准确的索引统计信息来生成好的执行计划。在数据加载完成后,以及之后定期执行:
sql
ANALYZE TABLE table_name;
这能帮助 MySQL 选择正确的索引,避免本该走索引的查询退化为全表扫描,从而间接降低 wa。
6. 连接与线程(P2)
MyISAM 只读场景下,max_connections 可以设得比写场景更高,因为只读 SELECT 之间的锁争用极小。但仍需配合应用侧连接池使用,避免频繁建连。
ini
max_connections = 1000
thread_cache_size = 256
二、操作系统层:MyISAM 只读的特殊注意事项
MyISAM 的数据文件完全依赖 OS 文件系统缓存,MySQL 自身不缓存数据块。因此,OS 内存分配策略必须给文件系统缓存留足空间。
1. 不要过度分配 key_buffer_size 挤占文件系统缓存
这是 MyISAM 只读场景最容易犯的错误。把 key_buffer_size 设得过大,看起来“索引都在内存里”,但实际挤掉了 OS 用于缓存 .MYD 数据文件的内存,导致数据读取疯狂穿透到磁盘,wa 反而更高。
平衡原则:key_buffer_size 覆盖所有索引即可,剩余内存全部留给 OS 文件系统缓存来缓存数据文件。
2. I/O 调度器与挂载选项(延续之前的建议)
SSD/NVMe 用 none,HDD 用 deadline
挂载加 noatime,减少只读场景下不必要的元数据写入
vm.swappiness = 10,避免 MyISAM 相关内存被换出
3. 预读优化(P2)
只读场景下,顺序扫描较多,可以适当增大文件系统预读:
bash
blockdev --setra 4096 /dev/sdX # 增大读预取
这对 MyISAM 全表扫描类查询有帮助。
三、Nginx 与 Tomcat 层:之前建议仍然适用
MyISAM 只读不改变 Web 层优化逻辑,之前回答中的以下措施继续有效:
Nginx upstream keepalive:减少与 Tomcat 的 TCP 建连开销
Tomcat NIO2 + 有限 maxQueueSize:避免请求无限堆积
JVM G1GC:降低 GC 停顿
动静分离:静态资源不走 Tomcat
四、只读场景下需要“删除”或“调整”的之前建议
原建议在 MyISAM 只读下的处理
InnoDB 缓冲池调优不适用,MyISAM 无缓冲池
innodb_flush_log_at_trx_commit不适用,MyISAM 无事务日志
读写分离只读场景可考虑多只读副本 + Nginx 负载均衡,但 MyISAM 复制需注意一致性
concurrent_insert 调优只读场景无写入,此参数无影响
DELAY_KEY_WRITE只读场景无索引更新,无意义
五、只读 MyISAM 优化优先级总结
优先级措施核心作用
P0key_buffer_size 覆盖全部索引(Key_reads/Requests < 0.01)消除索引读取的磁盘 I/O
P0确保 OS 文件系统缓存有足够内存缓存 .MYD 数据文件消除数据读取的磁盘 I/O
P1myisampack 压缩只读表减少数据文件 I/O 总量
P1索引/数据文件分散到不同磁盘降低寻道争抢
P1OPTIMIZE TABLE 消除碎片减少全表扫描 I/O
P2I/O 调度器 + noatime + 预读降低 OS 层 I/O 延迟
P2Nginx keepalive + Tomcat NIO2降低 Web 层资源开销
最关键的一步:先查 Key_reads / Key_read_requests 是否小于 0.01。如果远大于 0.01,直接调大 key_buffer_size;如果已经很小但 wa 仍高,说明瓶颈在数据文件的 OS 文件系统缓存不足,需要检查内存分配是否被 key_buffer_size 过度挤占,或考虑增加物理内存。