没走索引的UPDATE或DELETE会锁全表,因InnoDB行锁加在索引记录上,无索引时需全表扫描并逐行加X锁;用EXPLAIN查type(非ALL)、key(非NULL)、rows(远小于总行数)可快速判断。

没走索引的 UPDATE 或 DELETE 会锁住所有扫描到的行,在高并发下等效于锁表——这不是夸张,是 InnoDB 行锁退化的标准行为。
怎么一眼看出 UPDATE 没走索引?
别靠感觉,直接用 EXPLAIN 看执行计划,盯死三个字段:
-
type是ALL或index→ 全表或全索引扫描,肯定没走有效索引 -
key是NULL→ 没命中任何索引 -
rows接近表总行数(比如表有 80 万行,rows显示 79 万)→ 实际在扫绝大部分数据
常见诱因包括:WHERE created_at > DATE_SUB(NOW(), INTERVAL 7 DAY)(函数包裹字段)、user_id = '123'(字段是 BIGINT,传了字符串)、status LIKE '%pending%'(前导通配符)。
建什么索引才算真正生效?
不是“加个索引”就完事,得匹配查询条件的结构和顺序:
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
- 单列等值条件(如
WHERE user_id = ?)→ 建单列索引:ALTER TABLE orders ADD INDEX idx_user_id (user_id) - 多列组合条件(如
WHERE status = ? AND created_at > ?)→ 必须建联合索引,且顺序严格:等值字段在前、范围字段在后 →ALTER TABLE orders ADD INDEX idx_status_created (status, created_at) - 千万别反着建:
(created_at, status)对上面那个 WHERE 基本无效 - 如果语句带
ORDER BY id LIMIT 100,考虑覆盖索引,避免回表加重锁范围
加了索引为什么还是锁一堆行?
索引建对了 ≠ 锁就少了。InnoDB 加锁范围取决于「实际扫描并判断是否满足条件的行数」,而不是最终 UPDATE 成功的行数:
- 非唯一索引 + 范围查询(如
WHERE status = 'pending')会触发 Gap Lock 和 Next-Key Lock,锁住所有匹配值的行 + 间隙,可能远超预期 - 若
status = 'pending'占全表 95%,即使有索引,也会锁住几十万行 - 此时更稳妥的做法是分批:先
SELECT id FROM orders WHERE status = 'pending' LIMIT 500拿 ID 列表,再UPDATE ... WHERE id IN (…) - READ COMMITTED 隔离级别下间隙锁被禁用,能大幅减少锁范围,但需确认业务能否容忍幻读
怎么验证锁真的变少了?
别只看执行时间变快,要实测锁行为:
- 开两个会话:会话 A 执行
UPDATE后不提交;会话 B 立即执行SELECT * FROM information_schema.INNODB_TRX,看trx_rows_locked字段值 - 对比加索引前后该值:从 50 万降到 300,才算真正见效
- 同时观察
SHOW PROCESSLIST中是否有大量Waiting for table metadata lock或Updating卡住的状态
最容易被忽略的一点:索引生效的前提是事务足够短。一个持续 10 秒的长事务,哪怕只锁 10 行,也会让其他操作排队等待——锁的“危害”不只在数量,更在持有时间。










