首页 游戏 软件 排行 专题

MyISAM换成InnoDB那晚,200万行表重建跑了40分钟

来源: 本站整理 日期:2026/11/4 2:16:35

接手一个跑了六年的老系统时,我第一件事不是看慢查询日志,而是打开 SHOW TABLE STATUS 扫了一眼存储引擎整库 87 张表,从头到尾全是 MyISAM。看着眼熟,但真要动它的时候才发现,这玩意儿埋的雷比想象中多。

如果你也在处理 MySQL 存储引擎迁移这类具体问题,或者需要一台磁盘 IO 稳定、内存充裕的机器先把测试环境跑起来再动生产,可以先看看这两家的配置和技术说明:saltyfish(海外 VPS 与独立服务器,含 Premium/Elite 节点及服务器硬件方案),另外dataonline(越南数据中心,云服务器与服务器产品线) 也可以对照着看配置再决定。

一、MyISAM 最要命的不是慢,是「整表排队」

InnoDB 默认用行级锁,改一行数据只锁那一行。MyISAM 不一样,它只有表级锁不管你 WHERE 条件写得再精确,只要进了写操作,整张表就被独占。

我那套老系统里有一张 200 万行的订单表,后台有个「导出上月账单」的功能,导出逻辑先 SELECT 一大段,再往临时表写数据。业务低峰期跑没事,一旦有用户同时改了订单状态,两边就撞上了:导出这边占着表级写锁,改订单的请求全部排队等着。

表现到应用层就是接口大面积超时,但数据库里 CPU 才 20%、磁盘 IO 也闲着因为大家都在等锁。看 SHOW PROCESSLIST 会看到一堆 State 是 Waiting for table metadata lock 或 updating 的会话,堆积成一条长龙。这种问题最容易被误判成「服务器不够」,最后加机器加内存,钱花了,锁还是在。

还有一点容易被忽略:MyISAM 没有事务。写一半崩了,状态就是改了一半,也没法回滚。系统跑得越久,这种「偶发但无法复现」的数据不一致越多,等到要查的时候往往已经无从下手。

ALTER TABLE 转换存储引擎重建大表的过程示意

二、转换引擎不是改个字段,是把整张表重抄一遍

执行 ALTER TABLE xxx ENGINE=InnoDB 的时候,MySQL 会把旧表的数据逐行读出来,按新引擎的格式重新写一份新表,然后把原表改名让路。整个过程 IO 密集,且需要额外的一份表空间200 万行的表,重建前后的数据目录大小可能翻倍。

我那张 200 万行的表,在一台普通配置的云主机上跑了 40 分钟。更关键的是,这 40 分钟里那张表基本处于不可用状态:DDL 语句是带元数据锁的,后面的读写请求会排队等它。

所以这个操作不能放在业务高峰期干。我的做法是:先在从库或备份机上做一遍演练,量出真实耗时,再挑一个业务基本停摆的时间窗口动手。另外一个细节重建之前确认 innodb_file_per_table 是开的,不然所有表都挤在共享表空间里,删表之后空间不还回来,后面越用越别扭。

如果磁盘慢,40 分钟可能会变成几个小时。这也是为什么我一开始就把测试环境换成了磁盘 IO 更稳的机器迁移这种纯 IO 活儿,卡在磁盘上比卡在 CPU 上难受得多。

MySQL 进程列表中等待元数据锁的会话队列

三、转换完之后,这几处行为差异必须逐条核对

引擎换完不代表结束。我把踩到的差异列出来,这些都不是「改配置」能绕过去的,是代码和习惯要跟着调整的地方。

1. 自增主键会被重置

MyISAM 表转成 InnoDB 之后,AUTO_INCREMENT 的计数会按当前最大主键重新计算。如果表里存在已删除的最大值行,或者你用过 TRUNCATE,接着又灌过手工指定 ID 的数据,计数就可能对不上。表现是插入时直接撞主键冲突。转换完第一件事,看一眼下一个可用值跟你预期是否一致。

2. 行数对得上,索引可能没建全

MyISAM 允许同一列上有重复值和 NULL,InnoDB 对主键和唯一索引的要求更严。如果原表里有重复值导致唯一索引建不出来,转换会直接报错中断表停在中间状态,DDL 又是原子性比较脆弱的那种,现场会比较难看。转换前先用 SELECT 检查一遍唯一列有没有脏数据,比转换到一半失败强。

3. 排序规则和字符集要单独看

MyISAM 的索引是按字节比较的,InnoDB 是按排序规则比较。字符集和排序规则在转换过程中可能被重新规范化一遍,索引体积变化、查询计划变化都有可能。我遇到过转换后原本能走索引的查询突然变成全表扫,因为列的 collation 被改成了大小写不敏感,索引没法用了。转换完记得重新 ANALYZE TABLE,然后对核心查询做一遍 EXPLAIN 对比。

4. 崩溃恢复能力终于有了,但要注意 redo log 体积

这也是转 InnoDB 最实在的收益:断电重启后能靠 redo log 回滚到一致状态,不再需要 REPAIR TABLE 那套折腾。但相应地,innodb_log_file_size、innodb_buffer_pool_size 这些参数要跟着机器的内存和磁盘重新算一遍,不然容易出现「日志文件偏大导致启动慢」或者「缓冲池不够导致频繁刷盘」。

InnoDB 聚簇索引与行锁的内部结构示意

四、如果不能一次切完,还有条折中路

有些老系统不能停,也不能一次性重建所有表。可以按表分批来:先挑那张被锁得最狠、写最频繁的核心表先转,转完观察几天,确认业务没有异常,再动下一批。MyISAM 和 InnoDB 可以混存在同一个库里,不同表用不同引擎不影响互相访问,所以这条路是走得通的。

不过要提前接受一个事实:混合期间,同一个事务里的操作如果落在不同引擎的表上,语义会变得微妙。老老实实一张一张转、每张都留足观察期,比一把梭安全得多。

五、动手前的检查清单

我给自己留的清单大概是这些:先用 SHOW TABLE STATUS 摸清全库有多少张表、分别多大;确认磁盘剩余空间至少是最大那张表的 2 倍以上;查唯一列有没有重复值;挑一张小表先试跑,把重建耗时和 IO 曲线记下来;确认 innodb_file_per_table=ON;准备好回滚方案转换前的 mysqldump 别删。

最关键的一条:一定要有可用的备份,而且验证过能恢复。备份文件躺在磁盘上不算备份,只有真的恢复成功过一次,你才敢对着一张两百万行的老表下手。

整个迁移做完,最大的感受不是「InnoDB 更好」这种结论,而是:老系统里那些「偶发卡一下」「偶尔数据不对」的问题,很多在换引擎之后自己就没了。行级锁把并发写入的排队削平了,事务把数据一致性交回给数据库自己管。对一套跑了六年的老系统来说,这比任何调参都管用。

MyISAM 老库迁移到 InnoDB 完成后的服务器机房特写

玩家留言 跟帖评论
查看更多评论
相关文章
猜你喜欢
同类下载