innodb行锁常被误认为“锁表”,实则多因使用myisam引擎或where条件未走索引导致行锁退化为表锁;需确认引擎为innodb、确保查询命中索引、避免隐式类型转换、遵守事务及时提交原则,并合理管控间隙锁与隔离级别。

确认是否真在用InnoDB行锁
很多“锁表”问题根本不是锁表,而是误用了MyISAM或没走索引导致行锁退化成表锁。先执行SHOW CREATE TABLE table_name;,确认ENGINE=InnoDB。如果不是,ALTER TABLE table_name ENGINE=InnoDB;迁移过去——MyISAM的表级锁在写密集场景下毫无并发可言。
UPDATE/DELETE没走索引就等于锁全表
这是最常踩的坑:WHERE条件字段没索引,InnoDB被迫全表扫描,所有行都加X锁,效果等同于锁表。比如UPDATE orders SET status = 'done' WHERE user_id = 123;,如果user_id没索引,哪怕只改1行,也会锁住上万行甚至整张表。
- 用
EXPLAIN SELECT * FROM orders WHERE user_id = 123;验证是否命中索引(type字段必须是ref或const,不能是ALL) - 避免隐式转换:
WHERE phone = 13800138000(phone是VARCHAR)会失效索引,改成WHERE phone = '13800138000' - 复合索引要注意最左前缀,
INDEX(a,b,c)对WHERE b = ?无效
事务别拖着不提交,尤其是带FOR UPDATE的
SELECT ... FOR UPDATE本身不慢,慢的是人忘了COMMIT。一个未提交事务持有的X锁,会让后续所有冲突查询卡在Waiting for row lock状态。
- 应用层必须保证:开启事务 → 执行业务逻辑 → 立即COMMIT或ROLLBACK,中间不能穿插HTTP调用、日志打印等耗时操作
- 批量更新拆成小事务,比如分页处理:
UPDATE t SET flag=1 WHERE id BETWEEN 1000 AND 1999;,每次500~1000行 - 监控长期未结束事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(timediff(NOW(), trx_started)) > 60;(超60秒的事务)
RR隔离级别下间隙锁容易被忽略
MySQL默认的REPEATABLE READ级别会启用间隙锁(Gap Lock),它锁住的是索引值之间的“空隙”,不是数据行本身。这会导致看似无关的INSERT被阻塞。
例如表users(id PK, name)有记录(1,'a'), (5,'b'),执行SELECT * FROM users WHERE id BETWEEN 2 AND 4 FOR UPDATE;,虽然没查到数据,但id=3的INSERT会被锁住——这就是间隙锁在起作用。
- 若业务能容忍幻读,可降级为
READ COMMITTED:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;,该级别下InnoDB不使用间隙锁 - 加唯一索引能消除部分间隙锁场景,因为唯一键查找能精确定位到行,无需锁定间隙
- 避免在RR级别下用范围条件+FOR UPDATE,除非明确需要防止幻读
真正卡住系统的往往不是锁机制本身,而是索引缺失、事务失控、隔离级别误用这三者的组合。优化时优先查EXPLAIN和INNODB_TRX,而不是一上来就调参数。











