运维知识
悠悠
2026年8月4日

MySQL存储引擎选错一次,线上崩了三次:我把全网最细的对比讲透了

凌晨 1 点多,监控告警群里弹出一条消息:"订单库 CPU 飙到 95%,慢查询堆积"。

我揉着眼睛从床上爬起来,连上 VPN 一看,好家伙,一张本应该只做历史归档的日志表,被业务方用 MyISAM 建了,每天 200 万条 INSERT 进去,磁盘 IO 直接打满,整个库卡成 PPT。

后来复盘的时候我才意识到:很多兄弟对 MySQL 存储引擎的理解,就停留在"默认 InnoDB 就完事了"这一句话上。但真到生产环境,一张表选错引擎,轻则性能拉胯,重则数据丢失、凌晨起床。

今天这篇文章,不整虚的,我把 MySQL 常见存储引擎的底层原理、适用场景、生产踩坑,一次性给你掰扯清楚。看完之后,至少在你建表的时候,不会再拍脑袋选 ENGINE。


你以为的存储引擎,可能只是个名字

先说个扎心的事实:很多干了好几年 MySQL 的朋友,被问到"你们库里有几张 MyISAM 表"的时候,答不上来。

我之前带过一个新人,排查慢查询的时候发现一张 800G 的表用的 MyISAM,他跟我说:"师父,InnoDB 不是默认的吗?" 我说:"是啊,但你建表的时候没指定,又不是所有历史表都是默认的"。

所以到底啥是存储引擎?说人话,它就是 MySQL 用来"管数据怎么存、怎么取、怎么加锁、怎么恢复"的一套底层模块。MySQL 跟 Oracle、PostgreSQL 不太一样,它把"存储引擎"这一层设计成了插件式——你可以理解为,MySQL 的 SQL 解析、优化器是上层建筑,而存储引擎是地基,地基可以换。

登录 MySQL 敲一行 SHOW ENGINES,你会看到一堆名字 [1]:

Engine             Support  Comment
InnoDB             DEFAULT  Supports transactions, row-level locking, and foreign keys
MRG_MYISAM         YES      Collection of identical MyISAM tables
MEMORY             YES      Hash based, stored in memory, useful for temporary tables
BLACKHOLE          YES      /dev/null storage engine
MyISAM             YES      MyISAM storage engine
CSV                YES      CSV storage engine
ARCHIVE            YES      Archive storage engine
PERFORMANCE_SCHEMA YES      Performance Schema

Support 列显示 YES 的才是你的 MySQL 版本真正支持的。DEFAULT 那行告诉你当前默认是哪个——5.5 之后基本都是 InnoDB 了 1。

想确认默认引擎,也可以:

SHOW VARIABLES LIKE 'default_storage_engine';

接下来重点聊聊我们生产里真正会用到的那几个。


InnoDB:现在的事实标准,但你真的懂它吗

先说结论:90% 的业务场景,闭眼选 InnoDB 都不会错。但"会用"和"懂"是两码事。

InnoDB 是 MySQL 5.5 之后的默认引擎,核心特性就八个字:事务安全、行级锁、崩溃恢复 1。听起来简单,背后其实是一套相当复杂的机制。

它的事务是怎么玩的

InnoDB 完整支持 ACID 事务,这个大家都知道。但你知道它是怎么实现的吗?靠两套日志:Redo Log 和 Undo Log,外加一个 Buffer Pool [3]。

简单说:你的数据先写进内存的 Buffer Pool,同时把"我要改什么"记到 Redo Log 里,事务提交的时候,Redo Log 落盘就完事了,真正的数据页可以慢慢刷回磁盘。这个就是 WAL(Write Ahead Logging)机制 [3]。

那 Undo Log 干嘛的?回滚用的。比如你执行一条 UPDATE,InnoDB 会先把旧版本存到 Undo Log 里,万一事务要回滚,或者别人要读老数据(MVCC),就从这里拿。

这套组合拳下来,InnoDB 才能做到"崩了不丢数据"、"读不阻塞写、写不阻塞读"。

行级锁的真相

很多人以为"InnoDB 就是行锁",其实不然。InnoDB 的行锁是基于索引实现的 [4]。

举个生产里特别常见的坑:

UPDATE order SET status=1 WHERE user_name LIKE '%张三%';

这条 SQL 如果 user_name 没建索引,或者用了 LIKE 模糊查询在前面,InnoDB 不知道要锁哪几行,索性就把整张表锁了 [4]。我之前见过有人写 UPDATE table SET col=1 WHERE 1=1,全表更新直接锁了 30 分钟,整个库的写全挂。

所以记住一句话:InnoDB 的行锁,是有条件的行锁,条件就是你的 SQL 能不能精准定位到行。

聚簇索引的隐藏陷阱

InnoDB 的数据文件是聚簇索引结构——主键索引的叶子节点直接就是数据行本身 3。这意味着:

  1. 表必须有主键(没显式建?InnoDB 帮你生成一个 6 字节的 row_id [1])
  2. 主键最好用自增 INT/BIGINT,别用 UUID 或者手机号

为啥?因为聚簇索引的物理存储是按主键顺序的,你用自增 ID 插入,新数据永远追加在 B+ 树最后,写入是顺序 IO。但你用 UUID?每次插入都要插到 B+ 树中间去,频繁页分裂,性能直接掉一半 [4]。

我之前接手过一个项目,主键用的手机号,500 万数据量的时候 INSERT 已经慢到 200ms 一次。换成自增 ID 之后直接干到 5ms。

数据文件长啥样

如果你 innodb_file_per_table=ON(8.0 默认开),每张 InnoDB 表对应一个 .ibd 文件 [1]。注意在 MySQL 8.0 之前还有一个共享表空间 ibdata1,里面存着 undo 日志、系统表等。8.0 之后 undo 也独立成表了,整个结构清爽很多。


MyISAM:老炮儿,但别再用它扛生产了

MyISAM 是 5.5 之前的默认引擎,土生土长的 MySQL 原住民。它的特点也很鲜明:读快、写慢、不安全。

文件结构一目了然

一张 MyISAM 表在磁盘上对应三个文件 [1]:

  • 表名.frm:表结构定义(8.0 之后被 SDI 文件替代了 [1])
  • 表名.MYD:数据文件
  • 表名.MYI:索引文件

数据和索引是分开的,这是非聚簇索引 [3]。这意味着查一条数据,索引找到物理地址,再去 MYD 里读数据。听起来没差,但因为是分开存储,MyISAM 缓存在 key_buffer 里的只有索引页,数据要靠操作系统的 Page Cache [3]。

它的优点,恰恰是它被淘汰的原因

MyISAM 有几个当年很亮眼的能力:

  • COUNT(*) 极快:因为它内部维护了一个行数计数器,直接读出来就行,O(1) 复杂度 1[4]
  • 支持 FULLTEXT 全文索引:5.6 之前 InnoDB 不支持全文索引,要做搜索只能选 MyISAM 1
  • 表级锁,读不阻塞读:纯读场景下并发还行 [3]

但坑也非常明显:

  • 不支持事务:写一半崩了?数据文件直接坏掉,要 myisamchk 修复
  • 表级锁:写的时候整个表锁死,并发写直接歇菜
  • 崩溃后数据丢失风险高:没 Redo Log,全靠操作系统刷盘
  • DELETE 不释放空间:TRUNCATE 才能真正释放

我开头说的那个归档表的事故就是典型——业务方用 MyISAM 建了一张日志表,每天 200 万条写入,表级锁直接让所有写入排队。换成 InnoDB 之后,CPU 立刻降到 30% 以下。

现在除非是非常特殊的纯读场景(比如内网的 BI 报表库、对历史数据做只读分析),不建议再用 MyISAM 了。InnoDB 的 FULLTEXT 从 5.6 开始也支持了,5.7 之后还支持 GIS 空间索引 [3],真没必要再用 MyISAM。


Memory 引擎:快是真的快,挂也是真的挂

Memory 引擎也叫 HEAP 引擎,数据全存在内存里 2。重启即丢,一断电就没了。

它的几个特征

  • 默认用 HASH 索引,等值查询 O(1),但范围查询就废了 [3]
  • 也支持 BTREE 索引,需要在建表时指定
  • 表级锁,并发写性能差
  • 不支持 TEXT、BLOB 这种大字段(因为要存到磁盘临时文件)
  • 适合临时表、高速缓存

什么场景用它

我记得之前做用户会话管理,session 数据用 Redis 又觉得太重,单独建一张 Memory 表来存就很合适。访问速度极快,session 过期直接删表。

还有一种典型场景:复杂查询的中间结果集。比如你要做 SELECT ... FROM A JOIN B ON ... GROUP BY ...,结果集几万行,但又需要后续的子查询再 JOIN 几次。这种情况用 Memory 引擎建个临时表,比反复扫大表快多了。

但有三个坑你必须知道:

  1. 服务器重启 = 数据全丢:生产环境绝对不能把关键数据放 Memory
  2. 表大小受限于 max_heap_table_size:默认 16MB,写满了就报错
  3. MySQL 8.0 之后变数了:Memory 引擎的 BTREE 索引有了新限制,部分场景下会出问题

能用 Redis 就别用 Memory 表,毕竟专业的缓存中间件功能丰富得多。


Archive:归档专用,只进不出

Archive 引擎的设计思路特别纯粹:海量数据压缩存储,几乎不查询 [2]。

它的几个硬性约束:

  • 只支持 INSERTSELECT不支持 UPDATE 和 DELETE [2]
  • 不支持索引(5.1 之后支持主键索引,但作用不大)
  • 采用 zlib 压缩,磁盘占用比 MyISAM 还能省 80% [3]
  • 行级锁,支持并发插入

适用场景非常明确:日志归档、历史数据备份、合规审计数据的长期存储。

比如运营商系统里的通话记录,半年之前的数据基本没人查,但法规要求保留 5 年。这种数据塞 Archive 表里,1 亿条也就几个 G,查的时候慢点但能用。

但要小心:Archive 表的查询性能是灾难级别的,没有索引支持,所有查询都是全表扫描。所以千万别把它当成"压缩版 MyISAM"用。


CSV 引擎:把数据当 Excel 用

CSV 引擎直接把表存成 CSV 文件,每行一条记录,逗号分隔 [2]。

这种引擎基本就是个工具人:

  • 可以直接用文本编辑器打开看数据
  • 适合做数据交换,比如从 MySQL 导出 CSV 给 Python 脚本处理
  • 不支持索引
  • 所有列都得是 NOT NULL

我在做 ETL 的时候会用它——把上游系统的数据通过 CSV 落到 MySQL,再用 LOAD DATA INFILE 灌进业务库,中间过程清晰可追溯。

但生产核心业务表千万别用,纯属给自己找麻烦。


Blackhole:啥也不存的主从复制利器

Blackhole 引擎的英文直译是"黑洞"——你写进去的数据,全部被吞掉,SELECT 永远返回空集 [2]。

听起来这玩意儿有啥用?还真有。

经典用法是级联复制。比如你要从 A 库同步数据到 B、C、D 三个从库,但 B 又要同步给 E、F。这种星型+链式结构中,中间节点用 Blackhole 表,只传 binlog 不存数据,能大幅节省磁盘 IO [2]。

还有一种用法:审计。你想知道某个应用的 SQL 都写了啥,又不想让它真存数据,Blackhole 配合 binlog 就是天然的审计日志。

我之前在一个金融项目里就用过,把所有"敏感操作"通过 Blackhole 表转 binlog 到专门的审计库,业务库本身干干净净。


Federated:跨库访问,但已基本废弃

Federated 引擎允许你创建一张"远程表",实际数据存在另一个 MySQL 实例上 [2]。本地查 Federated 表,MySQL 会去远端拉数据。

理论上很美:不用 ETL 就能跨库 JOIN。但实际上:

  • 性能极差,每行数据都要走网络
  • 不支持事务
  • 单点故障,远程库挂了就崩
  • 8.0 之后基本不推荐

现在跨库查询都用 MySQL 8.0 的 FEDERATED 替代品,或者直接上数据中台、Presto、ClickHouse。Federated 除非维护老系统,否则别碰。


Merge 引擎:把一堆 MyISAM 合成一张大表

Merge 引擎是 MyISAM 的特殊用法,把多个结构相同的 MyISAM 表聚合成一个逻辑表 [2]。

这个功能主要是当年做水平拆分用的——按月分表的日志,可以用 Merge 引擎合成一个虚拟的大表,统一查询。

但说实话,MySQL 8.0 之后大家基本都用分库分表中间件了,Merge 引擎越来越少见到。如果你维护的是老系统,可以了解一下,新项目直接上 Sharding-JDBC 或者 MyCAT。


选引擎的实战方法论

聊完每个引擎的特点,最后说说生产里怎么选。

这张表你背下来

引擎事务锁粒度崩溃恢复适用场景
InnoDB行级99% 的业务系统
MyISAM表级纯读历史库(越来越少)
Memory表级❌(重启丢)临时表、高速缓存
Archive行级日志归档、海量冷数据
CSV表级数据交换、ETL 中转
Blackhole//级联复制、审计

三条实战建议

第一,默认 InnoDB,但每张表心里要有数。建表的时候多敲一个 ENGINE=InnoDB,让后人看得明白,也防止某些老代码生成 MyISAM。

第二,临时表看场景选。MySQL 自己执行复杂查询时生成的内部临时表,可以用 internal_tmp_disk_storage_engine 控制用 InnoDB 还是 MyISAM(8.0 之后默认 InnoDB,强烈建议别改)。

第三,混合使用是常态。一个生产库里有 InnoDB 业务表、Memory 临时表、Archive 历史表、Blackhole 审计表,这些都是合理的。关键是每张表你都知道为什么选它。


那些年我在存储引擎上栽过的跟头

聊点真实案例,可能对你更有帮助。

案例 1:主键用 UUID,INSERT 慢成翔

2019 年我接手一个电商项目,主键用的 VARCHAR(36) 存 UUID。表里 800 万数据,新订单写入平均 80ms,每到促销直接飙到 300ms+。换成 BIGINT AUTO_INCREMENT 之后,写入降到 8ms。老板问我为啥优化了 10 倍,我说就改了个主键类型。

案例 2:MyISAM 表的"假 COUNT"

某 BI 报表库,MyISAM 存储,好几亿条数据。开发同学写 SELECT COUNT(*) FROM huge_table WHERE xxx='yyy',跑了 40 分钟没出来。问题就是:MyISAM 的快 COUNT 只在没 WHERE 条件时才快,带条件一样全表扫 1。后来换成 InnoDB + 合适索引,3 秒出结果。

案例 3:Memory 表被 OOM 杀

某个 session 管理服务用 Memory 表存用户会话,没设 max_heap_table_size 限制。结果某天大促,几百万用户同时登录,Memory 表占用直接冲到 32G,触发 Linux OOM,MySQL 被 kill,session 全丢,用户集体掉线。血的教训告诉我们,Memory 表一定要监控大小,要么就别用它。

案例 4:Archive 表的 UPDATE 报错

有个哥们用 Archive 表存日志,某天业务方要求更新某批数据的状态,跑 UPDATE 直接报错。查了半天才想起来 Archive 不支持 UPDATE,最后只能先 INSERT 新数据再 SELECT 出来。生产环境用任何"专用引擎"之前,先把它的限制列出来贴在显示器上。


写在最后

存储引擎这事,说大不大说小不小。

说小,因为它就那么几种,背下来一上午就够。说不小,因为选错了轻则性能拉胯,重则数据丢失、凌晨起床救火。

我现在建表的习惯是:先想清楚这张表是干什么的、读写比例多大、生命周期多长、对事务有没有要求。这几问下来,引擎自然就选出来了。

很多时候,技术的差距不在于你懂多少花里胡哨的高级特性,而在于这些最基础的部分,你有没有真正吃透。

希望能帮你少踩几个坑,少加几个班。


如果你在工作中也遇到过 MySQL 存储引擎相关的"灵异事件",欢迎在评论区聊聊,咱们一起避坑。觉得有帮助的话,点个在看、转发给身边搞数据库的朋友,你的每一次支持都是我继续写下去的动力。

更多实战踩坑、运维干货、效率工具,欢迎关注我的公众号「耕云躬行录」,持续输出能直接落地的经验总结。

也可以逛逛我的个人博客「躬行笔记」,里面有一系列生产实践的总结文章,都是从真实故障里爬出来的经验。

下期打算写一篇 InnoDB 锁机制详解,从死锁排查到锁等待分析,把线上最常碰到的几类锁问题一次性讲透。感兴趣的话记得点个关注,我们下篇见。


参考来源:
[1] 腾讯云开发者社区 - 详解MySQL两种存储引擎MyISAM和InnoDB的区别与优缺点
[2] CSDN - MySQL 常见存储引擎全解析:InnoDB、MyISAM、Memory 等对比与实战
[3] 博客园 - MySQL 存储引擎对比(InnoDB vs MyISAM vs Memory)
[4] 菜鸟教程 - MySQL存储引擎InnoDB与Myisam的六大区别

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码