innodb中无索引的update/delete会锁全表,本质是行锁退化为全表行锁而非真正升级;根本原因是锁加在索引上,无索引则被迫聚簇索引全扫描并加x锁,rr级别下还触发gap/next-key锁,逻辑等效表锁。

直接说结论:表锁导致请求堆积,本质是锁粒度太大 + 锁持有时间太长,优先切到 InnoDB + 加索引 + 避免显式 LOCK TABLES,三者缺一不可。
为什么 SHOW PROCESSLIST 里总看到 Waiting for table lock
这不是“卡了”,而是某个线程正拿着整张表的写锁(或读锁),其他所有访问这张表的请求全被堵在门口。常见原因有:
- MyISAM 表执行 UPDATE/DELETE —— 强制表锁
- InnoDB 表执行没走索引的 WHERE 条件(比如 UPDATE user SET status=1 WHERE name='xxx',name 无索引)—— 全表扫描触发锁升级
- 显式用了 LOCK TABLES users WRITE 但没配 UNLOCK TABLES —— 锁永远不释放
- DDL 操作如 ALTER TABLE(尤其旧版本)正在执行 —— 隐式加表级排他锁
怎么快速定位当前谁在 hold 表锁
别猜,直接查:
- 看谁卡着不放:SHOW PROCESSLIST; 找状态为 Waiting for table lock 或 Locked 的线程
- 看哪张表被锁死:SHOW OPEN TABLES WHERE In_use > 0; 输出里 In_use 值大于 0 的表就是问题源
- 查更细的元数据(MySQL 5.7+):SELECT * FROM performance_schema.metadata_locks WHERE OBJECT_SCHEMA = 'your_db' AND LOCK_STATUS = 'GRANTED';
- 如果是 InnoDB 引擎,再补一句:SELECT * FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 5; 看有没有长时间未提交的事务
InnoDB 下 UPDATE/DELETE 触发表锁的坑
很多人以为用了 InnoDB 就绝对安全,其实不是。只要 DML 没命中索引,InnoDB 会放弃行锁,直接升级为表锁:
- UPDATE orders SET paid=1 WHERE created_at —— <code>created_at 没索引 → 全表扫 → 表锁
- DELETE FROM logs WHERE level = 'DEBUG' —— level 区分度低且无索引 → 大概率锁表
- 即使有索引,但用了函数或类型隐式转换也会失效:WHERE DATE(create_time) = '2024-01-01' 或 WHERE mobile = 13800138000(mobile 是 varchar)
- 解决办法只有一条:用 EXPLAIN 确认 type 是 ref/range,不是 ALL
显式 LOCK TABLES 的风险与替代方案
除非你明确知道自己在做什么,否则别碰 LOCK TABLES:
- 它不兼容事务 —— START TRANSACTION 后执行 LOCK TABLES 会自动提交当前事务
- 它锁的是“当前连接能访问的所有表”,不是单个表;UNLOCK TABLES 会释放该连接持有的全部表锁
- 更致命的是:如果程序异常退出、网络中断、或忘记写 UNLOCK TABLES,锁就一直挂着,连接数慢慢耗尽
- 真需要串行化写入?用 SELECT ... FOR UPDATE(InnoDB 行锁)或应用层分布式锁,而不是表级硬锁
真正麻烦的不是“怎么加锁”,而是“锁为什么没及时释放”——尤其是那些没走索引的语句,它们在慢查询日志里安静得像不存在,却在生产环境悄悄拖垮整个库。











