MyISAM不保证数据完整性,因其无事务、无UNDO/REDO日志、表级锁且崩溃后无法检测或修复数据错位;InnoDB则通过ACID四大机制硬性保障完整性。

为什么 MyISAM 不保证数据完整性
MyISAM 本质上是“裸写文件”,它把表结构和数据直接映射到磁盘文件(.MYD 和 .MYI),不做事务、不记录回滚日志、不校验写入一致性。一旦写入中途崩溃(比如断电或 KILL 进程),很可能留下半截更新的行,甚至索引与数据错位——而 MyISAM 自己完全无法察觉或修复。
常见错误现象:Table is marked as crashed and should be repaired 频繁出现;用 SELECT COUNT(*) 和 SELECT * FROM t WHERE id > X 得到矛盾结果;主从复制因某条语句在从库执行后数据不一致却无报错。
- 没有
UNDO LOG,无法回滚部分语句 - 没有
REDO LOG,崩溃后无法前滚恢复未刷盘的修改 - 表级锁导致并发写入时“中间态”暴露时间更长,加剧不一致风险
-
INSERT ... SELECT或批量UPDATE中断后,无法原子性地撤回已写入的行
InnoDB 怎么靠 ACID 实现完整性保护
InnoDB 的数据完整性不是靠“谨慎编码”撑起来的,而是由四个机制硬性兜底:原子性靠 UNDO LOG,一致性靠约束+隔离级别+回滚逻辑,隔离性靠 READ VIEW 和锁,持久性靠 REDO LOG 刷盘策略。这四者缺一不可,且全部默认启用。
关键实操点:
-
autocommit=1是默认行为,意味着每条 DML 都是独立事务——别误以为“没写 BEGIN 就没事务” -
innodb_flush_log_at_trx_commit=1(默认)确保每次COMMIT都强制刷REDO LOG到磁盘,崩溃后可完整恢复 - 外键约束(
FOREIGN KEY)由 InnoDB 引擎原生检查,MyISAM 完全忽略FOREIGN KEY语法(只当注释) - 唯一索引冲突、
NOT NULL违反等,在语句执行期就报错ERROR 1062或ERROR 1364,不会让脏数据落盘
什么时候 MyISAM 看似“够用”,其实是埋雷
典型场景是低并发、只读为主、定期全量导出的统计表(比如日志归档表)。这时 MyISAM 的读性能略高、存储体积略小,容易让人放松警惕。但只要涉及以下任一条件,MyISAM 就不可控:
- 有
INSERT ... ON DUPLICATE KEY UPDATE或REPLACE INTO—— MyISAM 会先删后插,主键自增 ID 跳变,且删除与插入无法原子化 - 需要
SELECT ... FOR UPDATE或任何行级加锁逻辑 —— MyISAM 只支持表锁,FOR UPDATE无效,还可能阻塞整张表 - 使用
mysqldump --single-transaction备份 —— MyISAM 表会被加上全局读锁,备份期间所有写入阻塞 - 开启 GTID 或基于 row 格式的主从复制 —— MyISAM 的非事务性会导致从库应用事件时状态漂移,且无法跳过错误
迁移 MyISAM 到 InnoDB 的真实代价
执行 ALTER TABLE t ENGINE=InnoDB 看似简单,但实际要面对三类隐性成本:
- 空间翻倍:InnoDB 默认启用
innodb_file_per_table=ON,转换后表空间文件(.ibd)通常比原.MYD大 20%–50%,因为要存事务ID、隐藏列、页分裂冗余 - 锁等待升级:MyISAM 表锁在高并发写入时反而“钝感”,而 InnoDB 行锁会暴露原本被掩盖的死锁问题(
ERROR 1213) - 全文索引行为变化:MyISAM 的
FULLTEXT支持布尔模式和自然语言模式,InnoDB 的FULLTEXT在 MySQL 5.6+ 才支持,且分词器、停用词配置不完全兼容 - 如果原表有
AUTO_INCREMENT列,InnoDB 要求该列必须是索引的一部分(哪怕只是单列索引),否则ALTER会失败并报错ERROR 1075
最常被忽略的一点:MyISAM 允许对空值做 GROUP BY 或 ORDER BY 时不报错,而严格模式下的 InnoDB 可能因 sql_mode 包含 STRICT_TRANS_TABLES 直接拒绝执行——这不是引擎问题,是模式切换带来的语义收紧。


















