update没走索引就会锁全表,因innodb行锁依赖索引,未命中索引时触发全表扫描并为每行加next-key锁,等效锁全表;须用explain验证type非all、key非null,并避免隐式转换和函数操作。
update没走索引就会锁全表
mysql 的 update 默认是行锁,但前提是 where 条件能命中索引。一旦条件列没索引、或用了函数/隐式类型转换(比如 where name = 123 而 name 是字符串),优化器就会退化为全表扫描——innodb 没法判断哪些行要改,只能把所有行都加上排它锁,结果就是整张表卡住。
实操建议:
- 执行前先
EXPLAIN看执行计划:重点确认type是ref/range,不是ALL;key字段显示实际用到的索引名 - 检查 WHERE 字段是否建了索引:主键、唯一键自动有索引;普通字段如
status、created_at需手动加ALTER TABLE t ADD INDEX idx_status (status); - 避免在 WHERE 中对字段做运算:
WHERE YEAR(created_at) = 2025会失效索引,改成WHERE created_at >= '2025-01-01' AND created_at
Navicat里误删WHERE条件导致锁表
在 Navicat 查询编辑器里写 UPDATE,最容易犯的错是漏写 WHERE 或写成 WHERE 1。这种语句没有过滤逻辑,MySQL 只能逐行扫描+加锁,哪怕表只有 10 行,也会锁全表,且事务不提交就一直挂着。
实操建议:
- 强制自己写 UPDATE 前先写 SELECT:比如要改
UPDATE users SET status='active',先跑SELECT id FROM users WHERE status='inactive' LIMIT 10;确认范围和索引是否生效 - Navicat 连接设置里关掉自动提交:
SET AUTOCOMMIT = 0;,这样即使误执行也能ROLLBACK; - 别信“只改几行就没事”:只要没索引,哪怕你加了
LIMIT 10,MySQL 仍会锁住扫描过程中遇到的所有行(不是最终更新的那几行)
长事务让行锁升级成表级阻塞
锁本身是行级的,但若一个事务长时间没提交(比如 Navicat 里执行完 UPDATE 卡住了、人走开了、或代码里忘了 commit),其他需要修改同一行的语句就会排队等待。等的人多了,看起来就像“表被锁死”,其实只是某一行被占着不放。
实操建议:
- 在 Navicat 中执行完
UPDATE后立刻COMMIT;或ROLLBACK;,别让它悬在那儿 - 查谁卡住了:
SELECT * FROM information_schema.INNODB_TRX ORDER BY trx_started DESC LIMIT 5;,看trx_state是不是LOCK WAIT或运行时间超 300 秒的RUNNING - 别直接
KILL:先确认该线程没被其他业务依赖,尤其是看到trx_query是BEGIN或空值时,大概率是应用层没正确结束事务
UPDATE ... FOR UPDATE 在Navicat里容易踩的坑
FOR UPDATE 是显式加排它锁,本意是防止并发修改,但在 Navicat 里常被滥用:比如在查询页面随手加了它,又忘记执行后续 UPDATE 或 COMMIT,结果锁一直挂着,别人连 SELECT … FOR UPDATE 都等不到。
实操建议:
- 只在真正需要“读-改-写”原子性时才用:
SELECT balance FROM account WHERE id=123 FOR UPDATE;→ 算出新余额 →UPDATE account SET balance = ? WHERE id=123;→COMMIT; - Navicat 中不要在只读查询里加
FOR UPDATE:比如你只是想看看数据,加了它反而制造人为阻塞 - 注意隔离级别影响:在
REPEATABLE READ下,FOR UPDATE可能锁住间隙(gap lock),导致插入也被阻塞,这不是 bug,是 MVCC 的正常行为
真正难防的不是锁本身,而是“锁住了还不知道”。Navicat 不报错、不弹窗,只让你看着进度条转圈——这时候得靠 INNODB_TRX 和 INNODB_LOCK_WAITS 主动去翻,而不是等它自己好。











