update语句必须加where条件,否则全表扫描更新会引发性能崩溃和并发覆盖;需确保where使用主键或唯一索引,避免模糊条件;select...for update须走索引,否则可能升级为表锁;乐观锁必须校验影响行数是否为1;事务中禁止混合ddl等隐式提交操作。

UPDATE 语句没加 WHERE 条件导致覆盖更新
很多人以为 UPDATE 天然带锁,其实不然——MySQL 默认在 RC(Read Committed)隔离级别下,UPDATE 只对**实际命中并修改的行**加行级排他锁(X 锁),没匹配上的行不锁,更不会锁住“可能被插入的位置”。如果漏写 WHERE,整表扫描+全表更新,不仅性能崩,还可能让并发请求互相覆盖。
- 检查所有业务中的
UPDATE语句,确保每个都带明确主键或唯一索引条件,比如UPDATE users SET balance = ? WHERE id = ? - 避免用模糊条件如
WHERE status = 'pending'做关键字段更新,它可能锁多行,且易因数据倾斜引发锁等待甚至死锁 - 上线前用
EXPLAIN看执行计划,确认type是const或eq_ref,不是ALL或range
用 SELECT ... FOR UPDATE 显式加锁但没走索引
SELECT ... FOR UPDATE 是常用手段,但它只在查询能命中索引时才锁住对应行;如果走全表扫描,会升级为表级锁(尤其在 MySQL 5.7 及以前),并发一高就卡死。
- 必须确保
FOR UPDATE的WHERE子句使用主键或唯一索引字段,例如SELECT * FROM account WHERE user_id = 123 FOR UPDATE(前提是user_id有唯一索引) - 不要在
FOR UPDATE后接LIMIT 1期望“只锁一行”——MySQL 不保证锁哪行,且可能锁住不止一行(如间隙锁) - 注意 InnoDB 的 next-key lock 行为:即使查的是等值,也会锁住索引间隙,防止幻读;这在高并发 insert 场景下容易引发莫名阻塞
乐观锁 version 字段方案在重试逻辑里漏判失败
用 version 字段做 CAS 更新看似简单,但实际中常因重试机制缺失或判断松散,导致“以为更新成功,实则被跳过”。
- 每次更新必须检查
UPDATE ... SET version = version + 1 WHERE id = ? AND version = ?的影响行数,**只认mysql_affected_rows()或 ORM 返回的受影响行数是否为 1**,不能只看 SQL 是否执行成功 - 重试次数建议控制在 3 次以内,超时直接报错,避免长事务拖垮连接池
- version 字段类型推荐
INT UNSIGNED,别用TIMESTAMP——时钟回拨或毫秒精度不足会导致冲突误判
事务中混合 DML 和非事务性操作引发隐式提交
MySQL 遇到某些语句会自动提交当前事务,导致 FOR UPDATE 锁提前释放,后续更新彻底裸奔。典型触发点包括 ALTER TABLE、CREATE INDEX、甚至某些存储过程里的 SELECT(如果启用了 autocommit=1)。
- 在事务块内禁止执行 DDL;DDL 必须单独成事务,且前后做好锁粒度评估
- 确认连接层 autocommit 设置:应用代码里显式调用
START TRANSACTION后,别在中间穿插INSERT INTO log_table这类可能被日志表引擎(如 MyISAM)干扰的操作 - 用
SHOW ENGINE INNODB STATUS查看最近死锁/锁等待时,留意Trx has been waiting后面是否跟着implicit commit提示
真正难处理的不是锁本身,而是锁生效的前提条件:索引是否到位、事务边界是否干净、影响行数是否被认真校验。这三个地方任意一个松动,排他锁就形同虚设。











