DEBUG.WIKI
丽萨主机;为跨境业务选好 IP;原生 / 住宅 IP · 按套餐选择;查看 IP →
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证

jOOQ 查询副本加了 NOWAIT,原查询也跟着变了

通过QOM复制带行锁的查询后,再修改副本的NOWAIT或锁表范围,可能同时改到原查询。问题在共享的ForLock对象,优先独立构建查询。

原查询只设置 FOR UPDATE,通过 QOM 方法得到副本,再给副本设置 NOWAIT,结果原查询的 SQL 也出现了 NOWAIT。这种变化发生在执行之前,不能先归因于 PostgreSQL 的锁等待策略。

在受影响路径中,查询外层对象虽然不同,里面的锁配置对象仍可能是同一个。

复制查询不一定复制锁对象

下面的片段展示这一条件,q 已经设置了行锁:

SelectQuery<?> copy = (SelectQuery<?>) q.$where(DSL.trueCondition());
copy.setForUpdateNoWait();

正常意图是只改变 copy。但正式3.21.9的 SelectQueryImpl.copyBetween() 直接赋值 result.forLock = forLock,没有为副本复制独立的 ForLock。后续 setForUpdateNoWait() 取得已有对象并修改等待模式,就可能同时影响 q。

范围也不只 NOWAIT。等待时间、SKIP LOCKED、锁定字段或表等可变锁属性,都需要核对是否走到共享对象。反方向修改原查询,也可能改变副本。

先在应用已有的 SQL 渲染检查中比较两份查询:保存修改前的原查询 SQL,修改副本后再渲染原查询。若原查询的锁子句已经变化,就已有客户端构造层的证据,无须到生产库里主动制造锁冲突。

分别构建带锁查询

暂时规避时,为两条操作分别调用同一个查询构建方法,让每次调用生成新的查询对象和锁配置。可以复用不变的表、字段和业务条件定义,但不要把已带锁的可变查询复制后,再把副本当成完全独立的对象修改。

构建方法必须保留各自真正需要的筛选条件、投影、事务上下文和锁范围。不要为消除串改直接删掉 FOR UPDATE:若业务靠行锁保护“读取后更新”,去掉锁可能引入并发覆盖。也不能给全部查询一律加 NOWAIT,它会改变等待和错误处理行为。

不要用同一查询对象在多个线程间反复切换锁模式。即使加了外层引用或重新声明局部变量,内部共享关系仍未改变。

检查锁行为和错误处理

先核对分别构建后的两条 SQL,确认改动一条时另一条保持原值。随后在获准的测试环境中,以两条独立连接验证原有等待行为、NOWAIT 的失败路径、事务结束后的释放,以及应用的重试规则。

客户端构造串改修复后,真实锁竞争仍然可能存在。如果报错只在 SQL 执行阶段发生,且两份 SQL 始终正确,应转向检查事务持有时间与锁冲突。

该缺陷已有后续版本修复目标,但截至2026年10月25日可核对的正式tag仍到3.21.9。版本号出现在修复计划里,不等于部署的依赖已经含有深拷贝处理;升级前仍需核对正式发布物。