涉及资金流水的业务系统绝对不能用MyISAM,因其不支持事务,无法保证多语句操作的原子性;扣款与入账若分属两条独立UPDATE,前者成功后者失败会导致资金丢失;InnoDB通过undo log实现事务级原子性与崩溃恢复,而MyISAM仅具单语句原子性且无回滚机制。

涉及资金流水的业务系统绝对不能用 MyISAM,不是因为“它慢”,而是它根本无法保证一笔转账的原子性——前一条扣款成功、后一条入账失败,钱就消失了,数据库自己还觉得一切正常。
MyISAM 的单语句原子性 ≠ 业务逻辑原子性
MyISAM 所谓“原子性”仅限于单条 SQL(比如一条 UPDATE 不会只改半行),但资金流水必然跨多表、多语句:扣减账户余额、生成交易记录、更新订单状态。这些操作必须全部成功或全部失败。
-
BEGIN; UPDATE account SET balance = balance - 100 WHERE id = 1; UPDATE account SET balance = balance + 100 WHERE id = 2; COMMIT;在 MyISAM 下无效——BEGIN和COMMIT被忽略,两条UPDATE独立提交 - 第一条执行后崩溃,第二条永远不执行,
account表里已扣 100 元,但对方账户没加,资金凭空蒸发 - InnoDB 靠
undo log回滚未完成事务;MyISAM 没有undo log,也没有任何机制能感知“这是一组逻辑”
崩溃后数据错位,连 CHECK TABLE 都可能骗你
MyISAM 把数据写进 .MYD、索引写进 .MYI,两者完全独立,靠文件偏移同步。断电或 KILL -9 后,.MYD 多写了 3 行,.MYI 没来得及更新,索引就指向错误位置。
-
SELECT COUNT(*) FROM trade_log返回 1000,但SELECT * FROM trade_log WHERE id > 990只查出 2 条——数据和索引已不一致 -
CHECK TABLE trade_log可能返回status: OK,因为它只校验文件头和基本结构,不验证索引项是否真实指向有效数据行 -
REPAIR TABLE是暴力重建索引,不校验业务含义;修复后金额对不上、时间戳乱序,但无任何报错
主从复制下,从库“看起来正常,实际已资损”
MyISAM 不支持 binlog_format=ROW,MySQL 8.0 默认开启该模式,遇到 MyISAM 表自动退化为 STATEMENT,导致主从执行上下文不同。
- 主库执行
INSERT INTO trade_log VALUES (UUID(), NOW(), ...),NOW()记录的是主库时间戳 - 从库重放时,
NOW()取的是从库本地时间,两库时间差 5 秒 → 交易时间错乱,对账系统直接失效 -
REPLACE INTO或INSERT ... ON DUPLICATE KEY UPDATE在主从上因锁顺序/执行时机差异,产生不同结果,且SHOW SLAVE STATUS不报错 - 云数据库高可用切换时,若新主库检测到
mysql.user是 MyISAM,部分 8.0.33+ 版本直接拒绝启动,报ER_UNKNOWN_STORAGE_ENGINE
禁用 MyISAM 后,最容易被忽略的隐性依赖
就算你在 my.cnf 加了 disabled_storage_engines=MyISAM,以下情况仍可能偷偷引入 MyISAM 表:
-
CREATE TEMPORARY TABLE tmp_log AS SELECT * FROM myisam_log—— 源表是 MyISAM,临时表默认沿用引擎,不受disabled_storage_engines限制 - 老备份脚本用
mysqldump --compatible=mysql40导出,还原时带ENGINE=MyISAM,服务端照单全收 - 应用代码里写死
CREATE TABLE IF NOT EXISTS audit_log (...) ENGINE=MyISAM,升级后首次运行直接报ERROR 3161,但错误被日志淹没,直到某次资金对账差 1 分钱才暴露 -
information_schema.TABLES查询漏加WHERE engine != 'MyISAM',监控脚本误判“无 MyISAM 表”,实则还有残留
真正危险的不是报错,而是不报错却逻辑错——资金系统里,静默不一致比宕机更致命。


















