MyBatis 中 SELECT ... FOR UPDATE 未锁住行,根本原因是事务未开启或隔离级别不支持;必须配合 @Transactional、InnoDB 引擎及合适隔离级别,并确保 SQL 正确书写且条件命中索引。
MyBatis 中
为什么没锁住行?
根本原因通常是事务未开启或隔离级别不支持。MyBatis 本身不控制事务边界,
只在数据库事务内生效,且依赖存储引擎(如 InnoDB)和隔离级别(推荐
或
)。
Spring 项目中必须确保方法被
注解包裹,且传播行为不是
或
MySQL 5.7+ 默认隔离级别是
,但若应用显式设为
,
会失效
MyBatis 的
语句需明确写
,不能靠拦截器或注解自动加锁
注意:若查询条件未命中索引(比如用了函数、模糊前缀
),InnoDB 可能升级为表锁,反而拖慢并发
乐观锁用
字段时,MyBatis 更新失败却不报错?
这是设计使然——乐观锁的本质是“检测冲突后放弃更新”,不是抛异常。MyBatis 的
方法返回值是
(影响行数),必须主动判断是否为 0。
SQL 需写成:
Java 层必须检查返回值:
别把
字段设为
默认值,否则首次更新时
永远不成立(要用
,但和自增逻辑冲突)
如果用 MyBatis-Plus,
的
是安全的,但记得调用
或手动维护
和
方案性能差异在哪?
不是“谁更快”,而是“谁更扛得住”。悲观锁靠数据库行锁阻塞并发,乐观锁靠重试应对冲突,二者适用场景完全不同。
高冲突低频写(如秒杀库存扣减):
更稳,但连接池容易耗尽;需配合超时(
)和快速释放事务
低冲突高频写(如用户资料修改):
几乎无锁开销,但若真发生冲突(比如两人同时编辑),得重试或提示用户刷新
MyBatis 中混合使用常见:查详情用
锁住关键行,再校验业务规则;非核心字段更新走
避免长事务
注意:MySQL 的
在 RR 级别下可能触发间隙锁,导致无关记录也被锁住,测试时务必用真实数据分布验证
MyBatis 动态 SQL 里拼
容易出什么问题?
最典型的是语法错误或锁范围失控——因为
必须紧贴
末尾,不能被
拆开,也不能出现在子查询里。
错误写法:
→ 生成
是对的,但若
,结尾多出空格或换行可能让 SQL 解析失败
正确做法:整个 SQL 语句按需生成,用
或直接两个独立
标签(
/
)
切勿在
循环中对多 ID 加锁:
会锁住所有匹配行,但若部分 ID 不存在,InnoDB 仍可能锁住间隙,引发意外阻塞
MyBatis 3.4+ 支持
和
共存,但 Oracle 不支持,跨库时别默认套用
真正难的从来不是写对一行
或加个
字段,而是想清楚这笔更新背后有没有其他服务也在改同一行、事务里混没混业务查询、下游会不会因锁等待超时而降级失败——这些没法靠框架自动兜底。
SELECT ... FOR UPDATEFOR UPDATEREPEATABLE READREAD COMMITTED@TransactionalSUPPORTSNOT_SUPPORTEDREPEATABLE READREAD UNCOMMITTEDFOR UPDATESELECT * FROM user WHERE id = #{id} FOR UPDATELIKE '%xxx'versionupdateintUPDATE order SET status = #{status}, version = version + 1 WHERE id = #{id} AND version = #{version}if (mapper.updateOrder(order) == 0) { throw new OptimisticLockException("版本冲突"); }versionNULLWHERE version = NULLIS NULLUpdateWrappereq("version", entity.getVersion())setEntityVersion()FOR UPDATEversionFOR UPDATElock_wait_timeoutversionFOR UPDATEversionFOR UPDATEFOR UPDATEFOR UPDATESELECTFOR UPDATE SELECT ... WHERE ... FOR UPDATEforUpdate=falseselectForUpdateselectNormalSELECT ... WHERE id IN (...) FOR UPDATEuseGeneratedKeysFOR UPDATEFOR UPDATEversion