update ... where in 会卡住其他查询,因rr隔离级别下innodb对匹配行加行锁、对索引间隙加next-key锁;若in列表过长、字段无索引或条件含函数,将扩大锁范围,导致后续查询阻塞在waiting for x lock状态。

为什么 UPDATE ... WHERE IN 会卡住其他查询
因为 MySQL 在 RR(可重复读)隔离级别下,UPDATE 不仅给匹配到的行加行锁,还会对索引间隙加 next-key lock。如果 WHERE 条件没走索引、或走了范围索引但范围过大,就可能锁住一大片索引区间,导致后续的 SELECT ... FOR UPDATE 或其他 UPDATE 被阻塞在 Waiting for table metadata lock 或 Waiting for X lock on ... 状态。
常见诱因包括:IN 列表过长(比如几千个 ID)、字段未建索引、使用函数包裹条件(如 WHERE YEAR(create_time) = 2024)、或用 OR 拼接多个非覆盖条件。
怎么快速定位正在等锁的会话和源头
直接查 information_schema 是最准的,别只看 SHOW PROCESSLIST —— 它不显示锁等待关系。
- 运行
SELECT * FROM performance_schema.data_locks;查当前所有锁(MySQL 8.0+),重点关注LOCK_TRX_ID和LOCK_DATA - 用
SELECT * FROM performance_schema.data_lock_waits;找出谁在等谁(BLOCKING_TRX_ID→REQUESTING_TRX_ID) - 再通过
SELECT * FROM information_schema.INNODB_TRX WHERE trx_id = 'xxx';查具体事务 SQL 和运行时间 - 如果看到
trx_state = "LOCK WAIT"且trx_wait_started是几分钟前,基本就是它了
UPDATE 批量更新时如何避免锁扩散
核心思路是缩小锁粒度:让每次更新只触达真实需要改的行,且尽可能走主键或唯一索引。
- 确保
WHERE字段有高效索引;若用IN (id1, id2, ...),id必须是主键或唯一索引列 - 拆大
IN列表:单次不超过 500 个值,用循环分批提交(注意应用层控制事务边界) - 避免在
WHERE中用LIKE '%abc'、IS NULL、或函数,这些大概率导致全表扫描+全表加锁 - 考虑用
JOIN替代IN:例如UPDATE t1 JOIN tmp_ids ON t1.id = tmp_ids.id SET t1.status = 1;,临时表需建索引
线上已卡住时,怎么安全“解围”
不要直接 KILL 长事务,尤其当它还在写 binlog 或刷脏页时,可能引发主从延迟甚至数据不一致。
- 先用
SELECT trx_id, trx_mysql_thread_id, trx_query FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 5;找出最早开始的事务 - 确认该事务是否属于你的批量任务(看
trx_query是否含UPDATE ... IN) - 如果确定可中断,用
KILL QUERY xxx(只杀语句,不杀连接),比KILL xxx更安全 - 若已持续超 30 秒且无响应,再考虑
KILL整个连接,同时检查应用层是否有重试逻辑,防止反复触发
真正难的不是发现锁,而是判断那个“等了 12 秒还没拿到锁”的事务,到底是在等一行记录,还是等一个还没生成的间隙——这得结合执行计划和索引结构一起看,光看 data_locks 有时并不够直观。











