UPDATE t1 JOIN t2会锁住t2整段索引范围,因t2出现在ON条件中需扫描并加S锁,若t2.t1_id无索引则全表扫描+全范围间隙锁,锁持续至事务提交;EXPLAIN显示type为ALL或index即预示大面积加锁。

UPDATE ... JOIN 本身不直接“锁表”,但它会触发大量行锁甚至间隙锁,当条件没索引、驱动表选错或事务夹杂外部调用时,InnoDB 就会退化成准表级阻塞——看起来像锁表。
为什么UPDATE t1 JOIN t2会锁住t2的整段索引范围?
哪怕你只写 UPDATE t1 SET x=1,只要 t2 出现在 ON 条件里,InnoDB 就必须扫描 t2 并对所有匹配行(含间隙)加 S 锁。这些锁不会在语句结束就释放,而是要等到整个事务提交。
- 如果
t2.t1_id没索引,JOIN会全表扫描t2→ 对所有行加S锁 → 等效于锁住整个t2的主键范围 - 即使有索引,若匹配结果分散(比如
t2中 1000 行分布在 50 个页中),也会锁 50 个页的间隙,其他事务更新这些页内任意行都会被阻塞 -
EXPLAIN FORMAT=TRADITIONAL显示t2的type是ALL或index,基本等于宣布“准备锁大片”
为什么WHERE t1.id IN (SELECT ...)比JOIN更安全?
子查询先执行、结果确定、只对 t1 加 X 锁;t2 上的读操作走快照读(MVCC),不加锁。
-
SELECT id FROM t2 WHERE ... ORDER BY id必须加ORDER BY id—— 不是为了排序,是让后续IN更新按物理顺序触发行,避免不同事务加锁顺序错位 - 返回的 ID 列表必须升序分批,每批 ≤ 500 个(避开
max_allowed_packet限制) - 若
t2的过滤字段无索引,这条SELECT自身就会全表扫描 + 锁间隙,得先建索引再跑
事务里混了HTTP/Redis调用,为什么会让锁“看起来像表锁”?
一个本该 20ms 完成的 UPDATE,因为等外部服务响应卡了 3 秒,t1 和 t2 的锁就多持有了 3 秒 —— 这期间所有想更新同一行或附近行的请求全部排队,监控上看就是“锁表”。
- 检查
SHOW PROCESSLIST中状态为Updating或Waiting for table metadata lock的线程,结合Lock_time字段(来自慢日志)确认是否真卡在锁上 - 死锁日志里出现
Lock wait timeout exceeded,大概率不是 SQL 慢,而是事务边界外有同步依赖 - 禁止在事务内做
curl、redis.get()、file_put_contents()等阻塞操作
UPDATE 语句本身,而是它背后隐式锁住的索引范围、未被察觉的全表扫描,以及事务里那个安静等待 8 秒 API 响应的 curl_exec()。


















