mysql行锁仅在where条件走索引时生效;否则退化为表级锁或锁全表扫描行,高并发易堆积。需用explain检查执行计划,避免隐式类型转换、违反最左前缀、子查询及函数导致索引失效。

为什么 UPDATE 语句锁住整张表而不是只锁行
MySQL 的行锁(InnoDB)只在能用上索引的条件下生效;如果 WHERE 条件没走索引,会退化为表级锁或锁全表扫描涉及的所有行,高并发下极易堆积。
- 检查执行计划:用
EXPLAIN看type是否为ref/range,避免ALL或index - 字符串字段注意隐式类型转换:比如
user_id是VARCHAR,但写成WHERE user_id = 123(数字),会导致索引失效 - 联合索引要遵循最左前缀:
INDEX (a, b, c),WHERE b = ?不走索引,WHERE a = ? AND c = ?可用但跳过b时c不参与范围查找
UPDATE 里别写子查询或函数计算
子查询和函数会让优化器难以估算扫描行数,也可能导致无法使用索引,进而拉长加锁范围和时间。
- 避免:
UPDATE t SET status = 1 WHERE id IN (SELECT id FROM log WHERE ts > NOW() - INTERVAL 1 HOUR)—— 子查询结果不确定,可能锁多行甚至触发临时表 - 改用 JOIN 或先查 ID 再批量更新:
SELECT id FROM log WHERE ...得到明确 ID 列表,再UPDATE t SET ... WHERE id IN (1,2,3) - 别在
WHERE里用DATE(created_at)、UPPER(name)这类函数,会绕过索引;改用范围写法:created_at >= '2024-01-01' AND created_at
批量更新时拆成小事务,别一口气 UPDATE 5000 行
InnoDB 行锁是事务级的,锁持续到事务结束;一次大更新意味着锁持有时间长、阻塞多、还容易触发锁升级或死锁。
- 单次 UPDATE 控制在 100–500 行以内,用
LIMIT分批:UPDATE t SET status = 2 WHERE status = 1 ORDER BY id LIMIT 200 - 每次更新后显式
COMMIT,不要依赖自动提交(尤其在应用层手动开启事务时) - 注意
ORDER BY+LIMIT组合必须有确定性排序依据(如主键),否则分页可能漏行或重复 - 避免在循环里反复执行
SELECT ... FOR UPDATE后再UPDATE,直接UPDATE ... WHERE ... LIMIT N更轻量
READ-COMMITTED 隔离级别比 REPEATABLE-READ 更适合高并发写
默认的 REPEATABLE-READ 在 UPDATE 时会使用间隙锁(Gap Lock)防止幻读,扩大锁范围;而 READ-COMMITTED 只锁实际命中的记录,不锁间隙,冲突更少。
- 确认业务能接受 RC 级别的“不可重复读”:比如两次 SELECT 同一条件结果可能不同,但多数写密集场景不依赖该特性
- 修改方式:连接级设置
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED,或在应用连接池配置中统一指定 - 注意:RC 下唯一索引冲突检测仍会加间隙锁(防插入重复),这点不变
真正难处理的是“先查后更”的逻辑,比如库存扣减;这类必须用 SELECT ... FOR UPDATE 加锁,但锁范围很容易被忽视——哪怕只查一条,若没走索引,照样锁全表。所以索引设计和执行计划验证,永远是第一道关卡。











