myisam 确实不支持事务,因其未实现 undo log、redo log、事务 id 及行级锁;执行 rollback 或部分失败时数据仍保留,仅依赖表级锁与 myisamchk 恢复。

MyISAM 真的不支持事务?不是“不支持”,是压根没实现
MyISAM 表引擎在 MySQL 中从设计上就放弃了事务能力——它没有 undo log、没有 redo log、没有事务 ID 分配机制,也没有行级锁支持。你执行 BEGIN 或 COMMIT,MySQL 会静默忽略(除非启用了 autocommit=0 且你在非事务引擎上手动调用,此时会报错 ERROR 1196: Some non-transactional changed tables couldn't be rolled back)。
常见错误现象:
– 执行 ROLLBACK 后发现数据还在
– 多条 INSERT 中某条失败,前面的已写入无法撤回
– 并发写入时出现部分更新、脏读或崩溃后数据不一致
- MyISAM 只提供表级锁,
INSERT和UPDATE会锁整张表,事务需要的并发控制基础不存在 - 它的崩溃恢复靠的是
.MYI索引文件和.MYD数据文件的校验与修复工具myisamchk,不是基于日志的原子性保障 - 如果你的应用依赖
SERIALIZABLE隔离级别或回滚语义,MyISAM 不是“不够好”,而是完全不可用
InnoDB 怎么保证一条 UPDATE 的原子性
InnoDB 的原子性不靠魔法,靠三样东西协同:内存中的 buffer pool、磁盘上的 undo log 和 redo log。当你执行 UPDATE t SET x = x + 1 WHERE id = 1:
- 先查到该行,加
record lock(如果是唯一索引查找,就是行锁;否则可能是间隙锁) - 把旧值写进
undo log(用于回滚),新值写进buffer pool - 把这次修改的操作(物理页变更)记进
redo log(用于崩溃恢复) -
COMMIT时只需确保redo log刷盘(innodb_flush_log_at_trx_commit=1),不必等数据页落盘
关键点:原子性体现在「要么全部生效,要么全部不生效」,而 InnoDB 把这个判断权交给 undo log 是否可逆 + redo log 是否持久。如果事务中途崩溃,重启后 MySQL 会重放 redo log 恢复未刷盘的修改,再根据 undo log 回滚未提交的事务。
为什么换引擎不能只改 ENGINE=InnoDB
直接对 MyISAM 表执行 ALTER TABLE t ENGINE=InnoDB 看似简单,但容易踩几个硬坑:
- 大表转换期间会锁表(5.7 及以前默认行为),线上业务可能卡住;8.0 虽支持
ALGORITHM=INPLACE,但仍需注意tmpdir空间是否够存临时拷贝 - MyISAM 允许
NULL值在PRIMARY KEY上(虽然不合理),InnoDB 不允许——转换会直接报错ERROR 1171: Primary key must not contain NULL values - 全文索引语法兼容但行为不同:MyISAM 的
FULLTEXT支持布尔模式和自然语言模式,InnoDB 的分词器、停用词表、最小词长(innodb_ft_min_token_size)都得重新确认 - 自增主键行为差异:MyISAM 的
AUTO_INCREMENT值存在内存中,重启会丢失;InnoDB 存在聚簇索引末尾,更可靠,但首次插入前需执行SELECT MAX(id)初始化
事务里混用 MyISAM 和 InnoDB 表会发生什么
这是生产环境真实存在的陷阱:在一个 BEGIN…COMMIT 块里,同时更新 MyISAM 表和 InnoDB 表。
- InnoDB 部分能回滚,MyISAM 部分**永远不能回滚**——哪怕整个事务因异常中断,MyISAM 的修改已立即生效
- MySQL 不会阻止这种操作,也不会自动降级隔离级别;它只是让事务变成“伪原子”:看起来是一个单元,实际只有一半受控
- 典型场景如订单表(InnoDB)+ 统计汇总表(MyISAM),一旦事务失败,订单撤销了但统计数还多了一条,数据对不上
- 解决办法只有两个:全换成 InnoDB;或把 MyISAM 表的操作拆出事务,用应用层补偿(比如记录操作日志、异步重试)
最麻烦的不是技术实现难,而是问题往往延迟暴露——你测的时候一切正常,上线跑一周后才发现报表总数对不上,而日志里找不到明确错误。











