UPDATE会锁整张表是因为WHERE条件未命中索引,导致InnoDB退化为表级锁;正确使用索引、避免隐式转换、优化事务粒度可避免该问题。
为什么
会锁整张表而不是只锁行
MySQL 的 InnoDB 引擎默认走行级锁,但前提是必须命中索引。如果
或
语句的
条件没走索引(比如用了函数、隐式类型转换、或字段无索引),InnoDB 就会退化为锁全表——这时并发写入直接排队,看着像卡死。
实操建议:
用
看执行计划,确认
是
/
而非
,且
列显示实际使用的索引
避免在
里对索引列做运算:
→ 改成
检查字段类型是否一致:比如
是
,但传了字符串
,会触发隐式转换导致索引失效
锁范围比你想的更大
很多人以为加了主键条件就只锁那一行,其实 InnoDB 的间隙锁(Gap Lock)和临键锁(Next-Key Lock)会让锁覆盖“值之间的空隙”。比如表里有
值 1、5、10,执行
,可能锁住 (3,5)、(5,10)、(10,+∞) 这几个间隙,阻塞其他事务插入 4、6、11 等值。
实操建议:
业务允许时,把
换成
原子操作,减少锁持有时间
确认隔离级别:如果业务能接受可重复读以外的一致性,可设为
,它不启用间隙锁(但要注意幻读风险)
高并发更新单条记录时,优先用带主键等值条件的
,避免范围条件
事务太长是并发杀手,不是锁粒度问题
锁粒度再小,如果事务从开连接、查数据、算逻辑、再更新,耗时 200ms,那这 200ms 内锁就一直占着。很多“锁竞争”本质是事务臃肿,不是 SQL 没优化。
MySQL(Linux)
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
下载
实操建议:
把非数据库操作(比如调外部 API、格式化字符串)全挪到事务外,
和
之间只留必要 SQL
批量写入别用循环单条
,改用
一条语句搞定
查+改场景慎用“先查后更”:如果只是计数器自增,直接
更安全
唯一索引冲突会引发额外的插入意向锁
当多个事务同时
同一个唯一键(比如重复注册邮箱),即使还没提交,InnoDB 也会加插入意向锁(Insert Intention Lock),它和间隙锁冲突,导致事务互相等待——现象是
里看到
。
实操建议:
应用层提前校验:插入前先
一次(不加锁),命中则直接返回,避免撞唯一索引
用
或
替代普通
,失败不抛异常,减少事务回滚开销
如果业务允许,把唯一约束从数据库下推到应用缓存(如 Redis
)做第一道过滤
锁竞争真正的复杂点不在“怎么加锁”,而在于“锁什么时候释放”和“锁住了什么”。很多问题表面看是并发低,实际是某条慢查询拖住了整个事务,或者一个没注意的全表扫描让行锁变表锁。盯住
和
,比盲目调参数有用得多。
UPDATEUPDATEDELETEWHEREEXPLAINtyperefrangeALLkeyWHEREWHERE YEAR(create_time) = 2024WHERE create_time >= '2024-01-01' AND create_time user_idBIGINT'123'SELECT ... FOR UPDATEidSELECT * FROM t WHERE id > 3 FOR UPDATESELECT ... FOR UPDATEUPDATE ... WHEREREAD COMMITTEDUPDATEBEGINCOMMITINSERTINSERT INTO ... VALUES (...), (...), (...)UPDATE t SET cnt = cnt + 1 WHERE id = ?INSERTSHOW ENGINE INNODB STATUSlock_mode X insert intention waitingSELECTINSERT IGNOREON DUPLICATE KEY UPDATEINSERTSETNXINFORMATION_SCHEMA.INNODB_TRXINNODB_LOCK_WAITS