尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MySQL存储引擎选型实战:InnoDB、MyISAM与Memory对比

MySQL存储引擎选型实战:InnoDB、MyISAM与Memory对比 做后端开发和数据库维护这些年我最怕听到的一句话是“这张表就是慢你帮我看下能不能换个存储引擎”说这句话的人往往默认“换引擎性能变好”。但真实情况是同样的表结构、同样的 SQL、同样的数据量放到不同引擎上结果可以天差地别——有的是快慢问题有的是能不能跑的问题有的甚至是数据会不会丢的问题。MyISAM 时代一条慢 UPDATE 能锁住整张表让所有读请求排队InnoDB 的间隙锁会在你以为只改一行的时候悄悄锁住一片区间Memory 表则可能在你重启实例的那一刻让你辛苦攒的数据瞬间蒸发。这篇文章不打算给你背文档而是把我踩过的坑、排查过的锁等待、做过的迁移结合 InnoDB、MyISAM、Memory 三个引擎的原理和选型一次性讲透。文章会从引擎的分工讲起逐步深入到事务、MVCC、聚簇索引、锁机制、索引失效这些实战话题最后给出一份可以直接照做的选型决策清单。不管是刚入门、正在被慢查询折磨还是准备做引擎迁移的同学这篇应该都能给出你想要的答案。1. 存储引擎到底在管什么一张表背后的分工与文件真相1.1 Server 层与引擎层的边界谁在解析 SQL谁在落地数据MySQL 的逻辑架构可以粗暴地切成两层上面是 Server 层负责连接管理、SQL 词法语法解析、优化器生成执行计划、权限校验以及 8.0 之前的内置查询缓存下面是存储引擎层真正负责数据怎么存、怎么读、怎么加锁、怎么保证事务不出错。打个比方Server 层像餐厅的前厅负责接单、安排座位、解释菜单存储引擎层是后厨不同后厨有不同的做菜风格——有的厨师每道菜都记台账事务日志有的厨师一口大锅从头锁到尾表锁有的厨师压根不点火数据全在内存。你点的还是同一道菜SQL但后厨的风格决定了上菜速度、能不能加急、以及厨房万一停电要不要紧。理解这个分层特别重要因为很多“引擎换完之后 SQL 变慢了”的案例根子其实在 Server 层——执行计划没变、优化器选错了路径、统计信息过期跟引擎关系不大。换引擎之前先确认问题到底出在哪一层否则很容易白折腾。1.2 引擎是表级属性三条指令看清引擎的“开关”存储引擎不是 MySQL 实例级别的概念也不是库级别的概念而是表级别的属性。你在建表语句里写ENGINEInnoDB或者直接省略会走default_storage_engine参数默认就是 InnoDB。一张表想查当前引擎很简单SHOW CREATE TABLE user_info\G -- 或者 SELECT TABLE_NAME, ENGINE, ROW_FORMAT, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA your_db;改引擎一行 SQL 就行但这一行 SQL 背后的代价一点都不小ALTER TABLE user_info ENGINEInnoDB;这个操作的本质是新建一张目标引擎的表把原表数据一行行拷进去再重建索引期间会长时间持有元数据锁。10 万行的小表无所谓几千万行的表在业务高峰期执行轻则 IO 打满重则把后面所有 DML 和查询全堵住。我在生产环境做引擎迁移从来不直接ALTER TABLE要么走 pt-online-schema-change要么用 gh-ost要么干脆在低峰期停机窗口操作。1.3 从数据目录看引擎本质一份文件清单我判断一张表是什么引擎习惯直接看数据目录。不同引擎在磁盘上的落盘形态完全不同引擎对应文件数据与索引关系表结构存储InnoDB每张表一个 .ibd独立表空间数据、索引都在同一个表空间内主键索引叶子节点直接存整行数据MySQL 8.0 之前是 .frm8.0 之后进数据字典MyISAM.MYD数据、.MYI索引两份数据和索引分离索引文件里存的是指向数据记录的指针同上Memory没有磁盘文件全部在内存不落盘仅表结构存在于数据字典看到.MYI这种文件基本可以断定是 MyISAM如果你发现某张表在磁盘上连文件都没有但SHOW TABLES里还在那多半就是 Memory 引擎或者某次异常后只留下了空的表结构。1.4 引擎变更的本质先想清楚再动手这里多说一句ALTER TABLE ... ENGINE...还有一个隐藏行为——即使你改成和原来一样的引擎MySQL 也会认为表结构变了照样执行全表拷贝和索引重建。很多人用它来“整理碎片”效果跟OPTIMIZE TABLE差不多。但代价一样很大操作前务必评估表大小和 IO 情况。碎片整理我一般推荐在低峰期做并且先看information_schema.TABLES里的DATA_FREE字段碎片小就别折腾。2. InnoDB 凭什么当默认事务、MVCC、聚簇索引与行锁2.1 redo log 与 undo log崩溃恢复和安全感的来源InnoDB 从 MySQL 5.5 开始成为默认引擎不是因为名字好听而是因为它把“数据不丢”这件事做得最扎实。核心机制是 WALWrite-Ahead Logging预写日志任何修改发生之前先写 redo log再改缓冲池里的数据页最后异步把脏页刷到磁盘。事务提交时只要 redo log 落盘成功这个事务就算持久化了。这就好比做饭前先在便签上写下完整菜谱哪怕中途停电第二天照着便签也能把菜做出来。redo log 保证的是“持久性”和“崩溃恢复”undo log 负责的是“回滚”和“MVCC 读旧版本”。当你执行 UPDATE 或 DELETE旧版本数据会留在 undo log 里其他事务需要读修改前的快照时就从这里捞。一个事务长时间不提交undo log 只增不减这就是我后面要提的“长事务导致磁盘暴涨”的根源。2.2 隔离级别与 MVCC快照读和当前读的博弈InnoDB 默认的隔离级别是 REPEATABLE READ可重复读它能做到这一点主要靠 MVCC多版本并发控制。普通SELECT走的是“快照读”直接读取事务开始那一刻的已提交版本不加锁也不影响别人写UPDATE、DELETE、SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE走的是“当前读”必须读最新版本并且要加锁。这解释了一个新手的经典困惑为什么一个事务里两次SELECT结果一样但另一个事务明明改了数据因为快照读用的是同一个历史版本。为什么UPDATE又明明能读到别的事务刚提交的数据因为当前读要的是最新值。弄清楚这两种读的边界很多“数据怎么对不上”的诡异问题就迎刃而解了。四个隔离级别我再压成一张表隔离级别脏读不可重复读幻读实现方式READ UNCOMMITTED可能可能可能读最新未提交版本READ COMMITTED不会可能可能每条语句新建快照REPEATABLE READ默认不会不会基本不会间隙锁兜底事务开始建快照SERIALIZABLE不会不会不会所有读都变当前读加锁线上我一般建议就保持默认的 REPEATABLE READ别轻易调低到 READ COMMITTED 去“提高并发”。可重复读不是性能瓶颈真正拖垮并发的是长事务和索引没走对的锁范围。2.3 聚簇索引与二级索引主键选择决定写入上限InnoDB 的数据组织方式有一个和 MyISAM 本质不同的点它是聚簇索引表。主键索引的 B 树叶子节点上直接挂着完整的数据行也就是说“主键 数据的存放位置”。二级索引普通索引、唯一索引的叶子节点不存数据只存主键值查询时先通过二级索引找到主键再回主键索引里捞整行这个过程叫“回表”。这个机制直接带来一个反直觉的结论主键怎么设计能决定你的写入能跑多快。自增主键是严格递增的新行永远追加在 B 树最右侧磁盘写入基本是顺序的不太触发页分裂而 UUID、随机业务号这类“无序主键”每次插入都可能落在已有页的中间触发页分裂、产生碎片、增加随机 IO写入性能会肉眼可见地下降。我踩过最大的一个坑订单表用 UUID 做主键单表 2000 万行之后批量导入从每秒几千条掉到几百条。排查半天的结论很尴尬——不是 SQL 问题不是机器问题就是主键太随机导致页分裂频繁。改成自增主键、再用唯一索引去兜底业务唯一性之后写入速度立刻回来。所以请记住主键值好不好看无所谓递增性优先。2.4 主键索引和唯一索引的区别别再混为一谈这个话题在面试里出现频率很高在实战中也容易踩。两者的区别有这么几条主键索引是聚簇索引叶子节点存整行数据唯一索引是二级索引叶子节点存主键值查询可能需要回表一张表只能有一个主键但可以有多个唯一索引主键列不允许为 NULL唯一索引列允许有多个 NULL。这一点很多人会记反MySQL 里唯一索引允许多个 NULL因为 NULL 和 NULL 互不相等不违反唯一约束主键通常还是外键引用和复制binlog 回放、从库应用的基础尽量选择一个稳定、简短、递增的值。设计表的时候业务上唯一但可能为空的字段比如用户邮箱、订单号就应该用唯一索引而不是主键主键就老老实实选一个无意义的自增代理键业务安全性由唯一索引保证。这套组合在绝大多数业务模型里都是最优解。2.5 行锁、间隙锁与死锁经典案例拆解InnoDB 的行锁不是只有一种“锁住一行”那么简单。它分三类记录锁Record Lock锁住索引上的一条具体记录间隙锁Gap Lock锁住两个记录之间的“空隙”防止别的事务往这个区间插入数据临键锁Next-Key Lock记录锁 间隙锁的组合左开右闭是 REPEATABLE READ 下防幻读的主要手段。这就是为什么有时你明明只 UPDATE 了一行却感觉把整个范围都堵了——因为范围扫描命中了别的事务持有的间隙锁。还有一个非常常见的坑UPDATE 的 WHERE 条件没走索引InnoDB 只能全表扫描定位记录每一行都加锁最终等于锁住了整张表。表面上你是行锁引擎实际干出了表锁的活。死锁是最有意思的部分。两个事务互相持有对方的下一把锁就会死锁经典代码如下-- 事务 A START TRANSACTION; UPDATE account SET balance balance - 100 WHERE id 1; UPDATE account SET balance balance 100 WHERE id 2; COMMIT; -- 事务 B并发执行 START TRANSACTION; UPDATE account SET balance balance - 100 WHERE id 2; UPDATE account SET balance balance 100 WHERE id 1; COMMIT;事务 A 持有 id1 的行锁、等待 id2事务 B 持有 id2 的行锁、等待 id1两边都不撒手死锁就形成了。MySQL 的innodb_deadlock_detect机制会立刻检测到并回滚其中一方让你的一条 SQL 报错“Deadlock found”。死锁不是 bug是锁博弈的正常结果解决办法是让所有事务按相同顺序访问资源——先把 id 从小到大更新完再更新大的。高并发场景下如果死锁频繁除了检查加锁顺序还要看是不是范围条件锁了太多间隙收窄扫描范围往往立竿见影。3. MyISAM 的看家本事与时代局限3.1 表级锁的并发天花板一个 UPDATE 卡住全表读MyISAM 的锁模型极其简单读读共享、读写互斥、写写互斥而且所有操作都是表级锁。一个会话对 MyISAM 表执行 UPDATE其他会话想 SELECT 都会被堵在“Waiting for table level lock”更麻烦的是 MyISAM 的写锁优先于读锁一旦有写操作持续没结束读请求可能被饿死。早年论坛、博客、小流量网站大量用 MyISAM因为典型的访问模型就是读多写少跑起来确实轻松。但今天的业务系统读写密集且要求秒级响应MyISAM 这种“一个写拖垮全表读”的特性已经很难接受了。我处理过的线上事故里有相当一部分慢查询现象其实是“表锁排队”本身那条 SQL 并不慢是前面有一张 MyISAM 表被卡住后面所有请求层层堆积。遇到这种先看SHOW PROCESSLIST里的 State看到Waiting for table level lock立刻能锁定元凶。3.2 COUNT(*) 秒回与压缩表仅存的“爽点”MyISAM 的.MYI文件里保存了表的总行数所以不带 WHERE 条件的COUNT(*)可以直接读这个数字O(1) 时间返回。InnoDB 做不到这一点只能走索引统计或扫描这也是很多人舍不得换掉 MyISAM 的唯一理由。但要注意只要COUNT(*)带了 WHEREMyISAM 同样要全表扫描优势瞬间归零。我的建议是如果只是想要“大表总数秒回”完全可以用 InnoDB 的覆盖索引来做到代价是维护一张统计表或用 Redis 计数没必要为此牺牲事务和崩溃安全。MyISAM 还有一个实用功能是压缩表用myisampack工具可以把只读表压缩能省不少磁盘空间查询时自动解压。缺点是一旦压缩表就只读修改前得先解压。归档历史数据、冷数据存储时这招挺好用但只适合那种“写完就不碰”的表。3.3 没有 redo log 的后果crashed 表和 40 分钟的 REPAIRMyISAM 最大的硬伤是没有任何事务日志和崩溃恢复机制。正常运行没问题一旦 MySQL 非正常退出断电、kill -9、磁盘异常索引数据和文件数据可能对不上表被标记为 crashed报错长这样Table xxx is marked as crashed and should be repaired恢复手段是CHECK TABLE检查 REPAIR TABLE重建索引。小表几秒钟大表可能就是几十分钟期间这张表完全不可用。我在 MySQL 5.6 时代经历过一次一张 5000 万行的 MyISAM 日志表机房断电后 REPAIR 花了 40 分钟期间整个报表系统全线超时用户投诉电话不断。那次之后我立下规矩任何核心表一律 InnoDBMyISAM 只能在明确只读、丢了能重建、崩了能等的场景出现。3.4 现在还适合用 MyISAM 的场景只读归档与报表所以 MyISAM 是不是完全没用了也不是。我现在的使用场景就非常收敛历史归档表数据写完就不再修改只做查询数据仓库的维度表、统计分析表数据来源是 ETL 任务全量刷新期间可以锁表极端的只读备份库主库是 InnoDB备库或分析库为了查 COUNT(*) 方便用 MyISAM。还有一个老理由是全文索引MySQL 5.6 之前全文索引只在 MyISAM 上可用。但 5.6 之后 InnoDB 原生支持全文索引这个理由早就失效了。如果你还在用 MyISAM 只是因为“听人说全文索引快”建议先确认一下自己用的 MySQL 版本——大概率可以直接换 InnoDB。4. Memory 引擎最快的读写和最贵的气泡4.1 内存里的数据与 HASH 索引等值查询秒回范围查询抓瞎Memory 引擎老版本叫 HEAP把所有数据放进内存读写不碰磁盘单看延迟是三个引擎里最低的。它的索引默认是 HASH 索引适合、IN这类等值查询不支持范围查询走索引如果你需要范围条件得在建索引时显式指定USING BTREE。这里有个比较容易误导的点Memory 表建了 BTREE 索引后范围查询确实可以借索引定位但数据本身依然是内存里的无序结构性能跟 InnoDB 那种天然有序的 B 树还是有差距别指望它能干亏大查询的活。还有一个硬性限制Memory 引擎不支持 TEXT/BLOB 字段。建表时一旦出现这些大字段直接报错“The BLOB/TEXT column xxx cant be used in key specification”或者引擎压根不支持。想用 Memory 放长文本你得先把字段改成 VARCHAR 并限制长度。4.2 临时表的隐形依赖GROUP BY、ORDER BY、JOIN 背后的内存表很多人以为 Memory 引擎离自己很远其实它一直在后台默默干活。MySQL 执行GROUP BY、ORDER BY、多表 JOIN、UNION时如果需要中间结果会在内存里创建内部临时表——5.7 用的是 MEMORY 引擎8.0 换成了专用的 TempTable 引擎底层同样是内存实现。一旦中间结果集超过了tmp_table_size和max_heap_table_size两者中的较小值MySQL 会把临时表转成磁盘临时表5.7 默认 MyISAM8.0 默认 InnoDB性能断崖式下降。排查这个问题的百试百灵方法在 MySQL 里看状态变量。SHOW GLOBAL STATUS LIKE Created_tmp_disk_tables; SHOW GLOBAL STATUS LIKE Created_tmp_tables;如果Created_tmp_disk_tables / Created_tmp_tables的比例持续偏高说明你的 SQL 经常在生成大临时表。这时候不要去调引擎优先优化 SQL让GROUP BY走索引、给 JOIN 字段加索引、避免SELECT *带大字段进临时表。调大 tmp 阈值只是治标SQL 本身不走索引才是治本。4.3 三个致命坑重启丢数据、表级锁、内存上限Memory 表看着快坑比想象中的多三个坑每一个都能让线上出事数据易失MySQL 重启、异常退出Memory 表数据全部清空只剩一张空表壳。拿它当业务数据的临时承接点等于把数据安全交给运气表级锁数据在内存不代表并发就好Memory 引擎依然是表锁并发写一样排队写多读多照样卡内存上限每张 Memory 表受max_heap_table_size限制默认一般 16MB 或 32MB。超过限制要么报“Table is full”要么你手贱调大了上限结果内存被吃光触发系统 swap 或 OOM把整个 MySQL 实例拖垮。我见过一个同学把 max_heap_table_size 调到 2GB然后往 Memory 表里灌了 1.5GB 数据服务器直接卡死连 SSH 都进不去。4.4 正确用法小字典表、会话级中间结果而不是缓存替代品Memory 引擎不是不能用而是要严格限制使用场景。我的经验是只放“一次性、可重建、量小”的数据例如跑批脚本里的中间结果表任务结束后立刻清理会话级的去重表、计数表启动时从 InnoDB 表预加载到内存的静态字典表地区表、代码表、配置表都行几千行数据放 Memory 里等值查询确实快。如果你是想解决“热点数据查询快”的问题我更推荐用 Redis 或本地的 Caffeine 这类专业缓存组件把缓存和数据源分开管理至少不会因为 MySQL 重启就把缓存层的数据全搞没。专业的事交给专业的组件Memory 引擎更像是应急工具箱里的存在而不是常规武器。5. 选型决策从业务需求反推引擎5.1 一张表看清三个引擎的差异与选型结论选型不能靠“听说”得靠业务需求反推。我把三个引擎的核心差异浓缩成一张表维度InnoDBMyISAMMemory事务支持完整 ACID无无崩溃恢复redo log 重放已提交数据基本不丢无日志崩溃后可能 crashed需 REPAIR重启数据全丢锁粒度行锁 间隙锁表锁表锁索引结构聚簇索引 二级索引B 树非聚簇索引数据与索引分离默认 HASH可建 BTREE外键支持不支持不支持全文索引支持5.6支持不支持COUNT(*) 无 WHERE走索引扫描较慢秒回保存精确行数秒回行数在内存数据存储磁盘 .ibd磁盘 .MYD .MYI纯内存推荐场景一切核心业务、读写混合只读归档、报表、冷数据小字典表、会话中间结果我的选型结论可以浓缩成一句话默认 InnoDB唯一合理的例外是“明确只读且能接受手动 REPAIR 的表”可以选 MyISAMMemory 只做临时数据承接。这里要特别提醒一句不要因为“查询快”选 MyISAM也不要因为“快”选 Memory这两个理由在今天的业务场景里基本都不成立。数据读写安全是第一位的快慢问题完全可以通过索引、缓存和架构解决。选型还要考虑运维。InnoDB 能用 Xtrabackup 做在线物理备份备份期间业务基本无感MyISAM 想拿一致性快照通常得配合FLUSH TABLES WITH READ LOCK锁全库流量稍大就根本没机会执行。我当年用 Xtrabackup 备份混合引擎库时最痛苦的就是 MyISAM 表每次都要小心翼翼地在锁窗口内完成切换和恢复也比 InnoDB 麻烦得多。从这个角度说InnoDB 对运维友好度是碾压级的。5.2 多引擎混用允许但不等于随便用一个 MySQL 实例、甚至一个库里同时存在多种引擎的表技术上完全支持但有几个纪律要守住涉及事务的更新操作必须只发生在 InnoDB 表上。MyISAM 和 Memory 表没有事务概念一旦 SQL 里跨引擎更新MyISAM 那部分操作失败也不会回滚会出现“InnoDB 回滚了、MyISAM 更新成功了”这种数据不一致的灾难跨引擎 JOIN 没问题但查询计划可能因为两张表的统计信息、索引结构差异而变得不可控能避免就避免尽量把同一业务域的表统一引擎改引擎必须评估大表影响。直接ALTER TABLE ... ENGINE...会拷贝全表数据并重建索引请结合在线改表工具、在低峰期操作并提前准备好回滚方案混用场景下监控要分别看MyISAM 关心Key_reads/Key_read_requests键缓存命中InnoDB 关心Buffer pool hit rate和锁等待别用一套指标管所有表。5.3 MyISAM 迁 InnoDB一次完整迁移的操作清单如果你已经决定把存量 MyISAM 表迁到 InnoDB我建议照这个顺序来我迁移过几十张表的流程基本是这样盘点全部表确认哪些是核心读写表、哪些可以继续留在 MyISAM纯归档只读表可以不动检查每张表的建表语句确保都有合理主键。InnoDB 没有主键时会选用第一个非空唯一索引否则用隐藏 ROW_ID会导致回表和复制效率低下务必提前补主键处理大字段TEXT/BLOB 在 MyISAM 下可能埋下行溢出的坑迁到 InnoDB 后建议把大字段拆到独立表避免主键索引页膨胀评估表大小和迁移窗口小表直接低峰期ALTER TABLE大表用 pt-online-schema-change 在线执行迁移完成后跑一遍ANALYZE TABLE刷新优化器统计信息避免迁移后执行计划突变观察一段时间慢查询、锁等待、死锁日志尤其关注长事务导致的 undo log 膨胀以及原本依赖 MyISAM COUNT(*) 秒回的 SQL 是否需要在应用层改造。整个迁移期间最有价值的技巧先在测试环境用同数据量复现一遍记录迁移前后的大 SQL 耗时别上线当天才发现有一条COUNT(*)查询从 0.01 秒变成了 3 秒那就尴尬了。6. 选完引擎只是开始锁分类、索引失效与高并发调优6.1 MySQL 锁分类全景从全局锁、MDL 到 InnoDB 行锁引擎选完了真正决定线上稳定性的是锁和索引的使用细节。MySQL 的锁可以从粒度分成几层全局锁FLUSH TABLES WITH READ LOCK让整个实例只读主要用于全库备份的一致性快照。有了 Xtrabackup 以后我基本不手动用除非要备份 MyISAM 表表级锁包括显式表锁LOCK TABLES ... READ/WRITE、元数据锁MDLDDL 和 DML 之间互斥、以及 MyISAM 自身的读写锁。MDL 是很多线上事故的隐形元凶——一个会话长时间未提交事务握着 MDL 写锁后续所有 DDL 和查询都会排队表现就是“数据库突然一片超时”行级锁InnoDB 的记录锁、间隙锁、临键锁之前已经展开过。还有意向锁IS/IX是 InnoDB 内部用来协调表级意向和行锁的不需要我们手动管但理解它能帮你读懂SHOW ENGINE INNODB STATUS里的输出。看锁等待最常用的命令-- 看死锁和锁等待详情 SHOW ENGINE INNODB STATUS\G -- 从 performance_schema 查锁的持有与等待关系 SELECT * FROM performance_schema.data_lock_waits;我的排查习惯先SHOW PROCESSLIST看 State凡是卡在Waiting for table metadata lock的先杀掉长事务或 DDL凡是Waiting for table level lock的先找 MyISAM 写操作凡是Lock wait timeout exceeded的再看 InnoDB 锁等待视图。把问题定位到“哪一类锁”再动手比瞎调参数高效得多。6.2 索引失效的经典场景为什么引擎会让后果被放大引擎再强索引没用对也白搭。索引失效的经典场景我列一份清单每一个都亲自踩过对索引列使用函数或表达式WHERE YEAR(create_time) 2024会让 create_time 索引失效应改成create_time 2024-01-01 AND create_time 2025-01-01隐式类型转换手机号字段是 VARCHAR 却传入数字MySQL 会把索引列转成数字再比较索引失效。排查时看 EXPLAIN 的 type 从 const/ref 退化到 ALL大概率就是这类问题左模糊LIKE %abc用不了普通 B 树索引LIKE abc%没问题。真需要后模糊搜索考虑全文索引或 ESOR 条件OR 连接的条件里只要有一个不走索引优化器为了结果正确可能整条走全表扫描。拆成 UNION ALL 通常能救回来联合索引最左前缀建了(a,b,c)却直接查 b 或 c索引无法使用优化器“闹脾气”统计信息失真或数据量太小优化器可能放弃索引。这时候ANALYZE TABLE刷新统计信息比硬塞索引更管用。这些都是通用 SQL 层面的问题但和引擎绑在一起时后果差异很大在 MyISAM 上索引失效意味着整表扫描加表锁所有并发请求遭殃在 InnoDB 上索引失效则意味着本来应该锁一行的 UPDATE 变成锁全表扫描路径上的所有记录甚至触发间隙锁大范围冲突。所以我才说“选引擎是打地基索引和锁的用法才是上层建筑”地基选对了上层偷懒照样塌。6.3 高并发下 InnoDB 的核心参数调优如果你的表已经全部是 InnoDB高并发压测时建议优先盯这几个参数参数建议值说明innodb_buffer_pool_size物理内存的 50%~70%专用实例热点数据能不能留在内存直接影响读性能innodb_flush_log_at_trx_commit1 最安全2 性能好1每事务刷盘2每秒刷盘崩溃最多丢 1 秒数据innodb_io_capacitySSD 建议 1000~2000告诉 InnoDB 刷脏页时有能力用多少 IO别让它保守拖慢innodb_lock_wait_timeout5~10 秒行锁等太久不如快速失败业务侧加重试innodb_deadlock_detect默认开死锁频繁时结合加锁顺序优化别贸然关针对高并发我的经验排序是先保证 buffer pool 命中率再看刷盘策略能否接受“最多丢 1 秒”的窗口然后解决慢查询和索引问题最后才谈分库分表。很多团队一上来就拆库拆表结果单表压测时发现 InnoDB 参数全是默认的buffer pool 才 128M那才是真正的浪费。再说一遍刷盘参数innodb_flush_log_at_trx_commit 2时事务提交不强制刷盘由后台每秒统一刷性能能提升一大截代价是实例崩溃时最多丢最近 1 秒的已提交事务。金融、订单这类场景我劝你老实保持 1普通 Web 应用如果业务能接受“秒级数据丢失窗口”用 2 换取吞吐是非常划算的。这是典型的“正确认识取舍”。文章写到这原理和实操都覆盖得差不多了。最后用我自己的经历收尾吧。几年前我维护过一个后台报表系统当时为了查询快和 COUNT() 秒回把几十张统计表全部设成了 MyISAM跑了半年确实挺爽。直到一次磁盘迁移时操作失误导致实例非正常关闭开机后三张大表全部 crashedREPAIR 期间报表系统全线瘫痪。后来我把所有表迁到 InnoDB把大表的 COUNT() 改成走覆盖索引的写法性能没有明显下降可靠性却完全是两个档次。那次之后我给自己定了一条铁规矩默认只用 InnoDB除非能明确说出“这张表不需要事务、不需要崩溃安全、能接受表锁加 REPAIR”三个条件才考虑 MyISAMMemory 只在会话级临时数据里短暂出现。你也别嫌这个标准保守——数据库这行活得久比跑得快重要得多。
返回列表