MyISAM表写操作会阻塞读和写,因写需独占表级X锁,而SELECT默认加表级读锁且不释放直至执行完;读多写少尚可,高并发写场景应迁至InnoDB。

MyISAM 表没有“读阻塞写”的死锁逻辑,但确实会发生 SELECT 阻塞 INSERT/UPDATE 的现象——根本原因是默认的读操作会申请表级读锁,而写操作必须等所有读锁释放才能拿到独占写锁。这不是 bug,是 MyISAM 的设计事实。
为什么普通 SELECT 会让写操作排队
MyISAM 的读锁(READ)允许多个并发读,但会阻止任何写;写锁(WRITE)则要求表上没有任何活跃读锁。所以当一个慢 SELECT(比如没走索引、扫描百万行)正在执行时,后续所有写请求都卡在 Waiting for table level lock 状态。
- 查证方式:
SHOW PROCESSLIST中看 State 列是否出现Locked或Waiting for table level lock -
SELECT不加修饰就是普通读锁,不是“无锁”;它不阻塞其他SELECT,但会拖住INSERT、UPDATE、ALTER TABLE等所有写操作 - 即使查询本身很快,如果并发读太多,锁队列也会堆积——因为 MyISAM 没有锁升级或超时自动释放机制
用 LOW_PRIORITY 让写操作优先抢到锁
给读操作显式降级,是缓解读写冲突最直接的手段。它让 SELECT 主动让出表锁,等写操作完成后再执行。
- 语法:
SELECT LOW_PRIORITY * FROM myisam_table WHERE ... - 效果:该
SELECT不会获取读锁,而是进入等待队列,直到表上没有写锁才开始执行 - 适用场景:报表类、后台统计类查询,对实时性要求不高
- 注意:
LOW_PRIORITY对INSERT/UPDATE同样有效,但不能混用——比如INSERT LOW_PRIORITY是让写让位于读,和这里目标相反
避免全表扫描触发隐式长读锁
真正拖慢写操作的往往不是读本身,而是没走索引导致的全表扫描。MyISAM 在执行这类 SELECT 时,会持锁扫完整张表,期间写完全不可进。
- 检查执行计划:
EXPLAIN SELECT ...,确认type字段不是ALL(即未全表扫描) - 给常用查询条件字段补索引,尤其是
WHERE和JOIN字段;MyISAM 的索引是 B-Tree,不支持覆盖索引优化,所以索引设计要更保守 - 避免在大表上执行
SELECT *+LIMIT组合——MySQL 仍可能先锁全表再截断结果 - 如果只是取随机几条,考虑用
SELECT ... ORDER BY RAND() LIMIT 10前先加WHERE id IN (SELECT id FROM ... ORDER BY RAND() LIMIT 100)缩小扫描范围
终极方案:别用 MyISAM,迁移到 InnoDB
所有基于 MyISAM 表锁的“优化”,本质都是在修裂缝;而 InnoDB 的 MVCC + 行锁机制,能让绝大多数读写并行无感。除非你明确依赖 MyISAM 的压缩表(myisampack)或旧版全文索引(5.6+ InnoDB 已支持),否则迁移是唯一根治路径。
- 迁移命令示例:
ALTER TABLE myisam_table ENGINE=InnoDB; - 注意点:MyISAM 允许空字符串作为主键,InnoDB 要求主键非空;迁移前需校验主键/唯一索引字段是否含 NULL 或空串
- 迁移后记得重建索引、更新统计信息:
ANALYZE TABLE myisam_table; - 最关键的忽略点:很多人只改了表引擎,却忘了调整事务隔离级别和连接池配置——InnoDB 默认可重复读(
REPEATABLE-READ),而 MyISAM 根本不认事务,应用层若手动BEGIN/COMMIT就可能出错


















