select ... for update 是锁争用源头,因其执行“查+加行级写锁”,未走索引时会升级为间隙锁或临键锁;应确保命中唯一索引、靠近update使用、优先用唯一约束替代。

为什么 SELECT ... FOR UPDATE 会成为锁争用源头
它不是“查数据”,而是“查+加行级写锁”,哪怕只读一行,也会阻塞其他事务对同一行的 UPDATE 或同类 SELECT ... FOR UPDATE。更隐蔽的是:如果查询没走索引,InnoDB 会升级为间隙锁(Gap Lock)甚至临键锁(Next-Key Lock),锁住整个范围,导致大量无关行被牵连。
实操建议:
- 确认所有带
FOR UPDATE的语句都命中了**唯一索引或主键**;用EXPLAIN验证执行计划是否显示key字段非空 - 避免在长事务中提前加锁,把
SELECT ... FOR UPDATE尽量靠近实际UPDATE操作,缩短锁持有时间 - 若只是防重复提交,优先用唯一索引约束 +
INSERT IGNORE或ON DUPLICATE KEY UPDATE,而非先查后锁
如何判断是不是 autocommit=0 导致隐式长事务
MySQL 默认 autocommit=1,但很多 ORM(如 Django、SQLAlchemy)或连接池配置会显式设为 0,一旦代码忘记 COMMIT 或异常未 ROLLBACK,事务就一直挂着,持有的锁也不释放——这时看 information_schema.INNODB_TRX 里 TRX_STATE='RUNNING' 且 TRX_STARTED 时间极早的记录,基本就是元凶。
实操建议:
- 上线前检查应用层数据库连接配置,禁用全局
SET autocommit=0;改用显式BEGIN/COMMIT控制边界 - 在事务入口加超时控制,比如 Python 中用
with connection.timeout(30):(依驱动支持),或 MySQL 侧设置innodb_lock_wait_timeout=10 - 定期巡检:
SELECT TRX_ID, TRX_STARTED, TRX_STATE, TRX_QUERY FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - TRX_STARTED) > 60;
READ COMMITTED 能不能直接解决幻读争用
可以显著缓解,但不彻底。在 READ COMMITTED 隔离级别下,InnoDB 不使用间隙锁(Gap Lock),只对已存在的匹配行加记录锁,因此范围查询不会锁住“不存在但可能插入的位置”。这大幅降低因间隙锁引发的锁等待,尤其适合高并发 INSERT 场景。
云模块_YunMOK网站管理系统采用PHP+MYSQL为编程语言,搭载自主研发的模块化引擎驱动技术,实现可视化拖拽无技术创建并管理网站!如你所想,无限可能,支持创建任何网站:企业、商城、O2O、门户、论坛、人才等一块儿搞定!永久免费授权,包括商业用途; 默认内置三套免费模板。PC网站+手机网站+适配微信+文章管理+产品管理+SEO优化+组件扩展+NEW Login界面.....目测已经遥遥领先..
但注意:
- 它不能消除所有锁争用——同一行的并发更新仍会冲突,只是不再因“怕幻读”而锁住整段空隙
- 切换前确认业务能否接受“不可重复读”:同一事务内两次
SELECT可能返回不同结果集 - 修改方式是连接级生效:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;,别直接改全局global transaction_isolation,避免影响其他服务
为什么 INSERT ... ON DUPLICATE KEY UPDATE 比“先查再更新”更抗并发
因为它是原子操作:InnoDB 在遇到唯一键冲突时,直接将插入意向锁升级为对应行的排他锁,并执行更新。整个过程无需用户态判断、无两次网络往返、不暴露中间状态——没有窗口期让另一个并发事务插进来做同样事。
对比“先 SELECT 再 INSERT/UPDATE”,后者至少三步:查→判断→执行,中间任何一步都可能被其他事务抢占,还容易因没加锁导致脏读误判。
实操建议:
- 确保冲突字段有唯一索引(
UNIQUE KEY或PRIMARY KEY),否则ON DUPLICATE KEY UPDATE不触发 - 更新字段避免引用旧值做计算(如
counter = counter + 1),除非确定该列无并发写入竞争;否则仍需SELECT ... FOR UPDATE保原子性 - 注意返回的
affected_rows:2 表示更新,1 表示插入,0 表示无变更(值相同),可用于业务逻辑分支
锁争用真正的复杂点不在语法,而在事务边界和索引设计是否与业务意图对齐。一个 WHERE 条件没走索引,或一个 COMMIT 被异常吞掉,就足以让并发性能断崖下跌。










