直接结论:锁竞争不会因“转成innodb”自动消失,反而可能暴露行锁冲突、死锁、缓冲池不足等新问题;本质是并发模型改变,必须同步调优配置与代码。

直接结论:锁竞争不会因“转成InnoDB”自动消失,反而可能暴露新问题——行锁冲突、死锁、缓冲池不足导致的隐性排队,本质是并发模型变了,不能只改引擎不调配置、不修代码。
ALTER TABLE ENGINE=InnoDB 为什么卡住或失败?
这不是慢,而是被阻塞或校验失败。常见原因有三个:
- 外键约束未对齐:MyISAM 不校验外键,
ALTER TABLE切到 InnoDB 时会全表扫描验证,若上下游表结构不一致(比如缺失索引、列类型不匹配),命令会挂起或报ERROR 1005 (HY000): Can't create table - 含 NULL 的 UNIQUE 索引:MyISAM 允许
UNIQUE(col)中col IS NULL多次,InnoDB 在部分旧版本(如 MySQL 5.6)会卡在索引重建阶段;先跑SELECT COUNT(*) FROM t WHERE col IS NULL,确认后改用普通索引或补默认值 - 没主键却依赖隐藏 ROW_ID:InnoDB 会为无主键表生成隐藏
ROW_ID,导致二级索引体积膨胀、写放大;转换前必须加显式主键,且自增列需在联合主键最左位,否则ALTER可能静默失败
转完发现 UPDATE/INSERT 更卡,第一件事查什么?
不是看慢查询日志,而是立刻检查 innodb_buffer_pool_size。MyISAM 时代设的 key_buffer_size = 256M 没问题,但 InnoDB 下这个值若还是 128M 或 256M,90% 的请求都在等磁盘 IO。
- Linux 上建议设为物理内存的 50%–75%,但别超 80%,留内存给 OS 缓存和连接线程
- 改完必须重启 MySQL,然后执行
SHOW ENGINE INNODB STATUS\G,重点看Buffer pool hit rate—— 低于 99% 就说明热数据根本没缓进来 - 同步调低
key_buffer_size(比如从 256M 改成 32M),避免两套缓存争内存
为什么 SHOW PROCESSLIST 里一堆 “Sending data” 或 “updating”?
这大概率不是锁等待,而是 InnoDB 行锁升级成了间隙锁(gap lock)或临键锁(next-key lock),尤其在 WHERE 条件没走索引、或用了范围查询(BETWEEN、>)时。MyISAM 不会这样,它直接锁全表,反而“看起来快”。
- 用
EXPLAIN确认所有写操作是否命中索引;没索引的UPDATE会锁整张聚簇索引,等价于表级锁 - 避免在事务里做非必要查询或 sleep;长事务会拖住锁不放,
innodb_lock_wait_timeout默认 50 秒只是抛错时间,不是锁粒度开关 - 高频更新同一行(如计数器)时,InnoDB 行锁虽细,但竞争仍高;可考虑拆成多行分片更新,或改用应用层 Redis 计数 + 定期落库
死锁日志里出现 “lock_mode X locks rec but not gap” 怎么办?
这是典型二级索引更新引发的死锁:两个事务按不同顺序访问同一组记录,比如事务 A 先查 idx_a 再查 idx_b,事务 B 反过来,就撞上了。MyISAM 不会发生这种事,因为它压根没有行级锁协调逻辑。
- 统一 SQL 访问路径:确保所有业务代码对同一张表的多条件更新,总是按相同字段顺序加锁(比如固定先
WHERE a=?再AND b=?) - 减少事务内操作:把
SELECT ... FOR UPDATE和后续UPDATE尽量靠近,中间别穿插网络调用或计算 - 捕获
Deadlock found when trying to get lock错误后必须重试,InnoDB 不会自动重试,业务层得自己处理
真正容易被忽略的点是:InnoDB 的“行锁优势”完全依赖索引质量、缓冲池大小、事务边界设计。不改这些,光换引擎只会把表级锁问题,换成更难排查的行锁抖动和死锁重试风暴。











