mysql 5.6+ 中 myisam→innodb 跨引擎转换不走 inplace,需全表拷贝,导致主从阻塞、磁盘爆满;真正无锁需用 pt-osc 或离线 dump 导入,并验证 buffer_pool_size、全文索引及自增锁配置。

直接用 ALTER TABLE t ENGINE=InnoDB 批量改表引擎,在生产环境大概率会锁库、拖垮主从、触发磁盘爆满——这不是脚本写得不对,是没绕开 MySQL DDL 的底层限制。
为什么不能直接循环执行 ALTER TABLE
MySQL 5.6+ 虽支持 ALGORITHM=INPLACE,但 MyISAM → InnoDB 这类跨引擎转换**不走 inplace 流程**,仍需全表拷贝。现象包括:
- 主库执行时持 MDL 写锁,所有对该表的读写全部阻塞
- 从库回放该 DDL 时同样卡住,
Seconds_Behind_Master瞬间飙升到几小时 - 临时需要等量磁盘空间(原表大小 ×2),
innodb_file_per_table=OFF时还会污染系统表空间 -
SHOW PROCESSLIST里卡在copy to tmp table状态,不是卡网络或权限
真正能落地的批量转换方案
必须按环境能力分层选型,没有“一键通用”:
- MySQL ≥ 5.6 且无 FULLTEXT 索引:用
ALTER TABLE t ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE——注意这只是“语法合法”,实际是否免锁取决于引擎类型兼容性,MyISAM→InnoDB 仍 fallback 到 copy - MySQL ≥ 5.7 +
innodb_online_alter_log_max_size足够:配合--lock=none显式声明,可降低锁粒度,但大表仍建议拆分批次 - 线上不能停服:必须用
pt-online-schema-change,它通过影子表+触发器实现无锁,但要求主键存在、无外键、禁用 binlog_format=STATEMENT - 离线窗口充足:用
mysqldump --no-create-info --skip-triggers导出数据,建新表时指定ENGINE=InnoDB,再导入
脚本生成和执行的关键避坑点
用 information_schema.tables 拼 SQL 是主流做法,但以下细节一错就全崩:
- 表名含横线、空格或关键字(如
order):必须用QUOTE(table_name)包裹,否则生成的ALTER TABLE order ENGINE=InnoDB直接报错 - 库名含横线或特殊字符:同理要
QUOTE(table_schema),拼成ALTER TABLE `my-db`.`user_log` ENGINE=InnoDB - 脚本输出后务必人工检查:先
head -20 alter_engine.sql看前几行是否合法,再grep -n "ERROR\|Warning" alter_engine.sql排查生成异常 - 别一次性跑完:按
data_length + index_length分三档(1GB),用sed -n '1,50p'先试前 50 行 - 执行时加超时和重试:例如
mysql --connect-timeout=10 --max-allowed-packet=512M -u root -p -e "source alter_engine.sql"
转换后必须验证的三项
很多人改完就上线,结果第二天发现 COUNT 变慢、全文搜失效、插入报死锁:
-
SELECT COUNT(*) FROM t在 InnoDB 上变慢:不是引擎问题,是 MyISAM 缓存了行数,而 InnoDB 必须扫聚簇索引;若业务强依赖秒级 COUNT,得加汇总表或用information_schema.tables.table_rows(仅估算) - FULLTEXT 索引失效:InnoDB 的全文索引要求 MySQL ≥ 5.6,且建表时必须显式声明
FULLTEXT(col),MyISAM 的全文索引不会自动迁移 - 高并发插入报
Lock wait timeout exceeded:InnoDB 自增锁更严格,需确认innodb_autoinc_lock_mode=2(交错模式),并检查是否有INSERT ... SELECT类语句未加事务控制
最常被跳过的一步是检查 innodb_buffer_pool_size ——如果它只设了 128MB,而数据总大小 20GB,那转换后所有查询都会疯狂刷盘,比 MyISAM 还慢。调这个参数不是“可选项”,是 InnoDB 能否正常工作的前提。











