PostgreSQL 在 MyBatis 中执行正常 SQL 失效:一次 DELETE USING 踩坑记录

最近在处理计资名单账期中间表数据时,遇到一个比较隐蔽的问题:SQL 在 PostgreSQL 客户端中可以正常执行,但放到 MyBatis XML 里执行却失效或报错。

问题 SQL 大致如下:

ini 复制代码
<delete id="deleteLockedLeaveJobs" parameterType="java.util.Map">
    DELETE FROM xxx.salary_list_middle_monthly t1
    USING xxx.salary_list t2
    WHERE t1.task_unique_id = #{taskUniqueId}
      AND t2.ytenant_id = #{ytenantId}
      AND t1.staff_id = t2.staff_id
      AND t1.period = t2.period
      AND t2.dr = 0
      AND t1.depart_date IS NOT NULL
      AND t1.depart_date <![CDATA[ < ]]> #{today}
</delete>

这段 SQL 的业务含义是:

删除账期中间表中,已经离职且离职时间小于当前日期,并且计资名单中已经存在对应离职账期数据的人员记录。


一、现象

这段 SQL 直接放到 PostgreSQL 数据库工具里执行是正常的。

但是放到项目 MyBatis XML 中,通过程序执行时,可能出现:

sql 复制代码
SQL 执行失败
SQL 被框架改写
SQL 结构被重新排序
DELETE USING 语法失效

一开始容易误以为是 PostgreSQL SQL 写法有问题。

但实际排查后发现,问题不在数据库本身,而更可能出在项目里的 SQL 解析、租户翻译、数据权限、SQL 改写等框架层。


二、原因分析

PostgreSQL 支持这种写法:

sql 复制代码
DELETE FROM table1 t1
USING table2 t2
WHERE t1.id = t2.id;

这是 PostgreSQL 中常用的关联删除语法。

但是在某些项目框架里,MyBatis 执行 SQL 前,SQL 可能会经过一层或多层处理,例如:

sql 复制代码
租户字段注入
SQL 翻译
数据权限拦截
SQL 解析器重写
多数据源适配

这些组件有时对 PostgreSQL 的 DELETE ... USING ... 语法支持不完整。

尤其是遇到下面这种结构时:

sql 复制代码
DELETE FROM schema.table t1
USING schema.table2 t2
WHERE ...

框架可能会错误识别:

sql 复制代码
表别名
USING 语法
DELETE 目标表
关联表
WHERE 条件归属

最后导致原本正确的 PostgreSQL SQL,在进入数据库前已经被框架改坏了。

所以这个问题的关键不是:

sql 复制代码
PostgreSQL 不支持 DELETE USING

而是:

sql 复制代码
项目框架中的 SQL 翻译/拦截逻辑没有正确处理 PostgreSQL 的 DELETE USING 语法

三、解决方式

最终处理方式是在 SQL 前加上跳过翻译的标记,并把别名写得更明确:

ini 复制代码
<delete id="deleteLockedLeaveJobs" parameterType="java.util.Map">
    /*!sql_no_translate*/
    DELETE FROM xxx.salary_list_middle_monthly t1
    USING xxx.salary_list AS t2
    WHERE t1.task_unique_id = #{taskUniqueId}
      AND t2.ytenant_id = #{ytenantId}
      AND t1.staff_id = t2.staff_id
      AND t1.period = t2.period
      AND t2.dr = 0
      AND t1.depart_date IS NOT NULL
      AND t1.depart_date <![CDATA[ < ]]> #{today}
</delete>

关键改动有两个:

markdown 复制代码
1. 增加 /*!sql_no_translate*/
2. USING 表别名改成显式 AS t2

其中最核心的是:

arduino 复制代码
/*!sql_no_translate*/

它的作用是告诉项目框架:

这段 SQL 不要再进行自动翻译或改写,直接按原 SQL 执行。

AS t2 不是必须解决点,但可以让 SQL 语义更清晰,减少部分 SQL 解析器对别名的误判。


四、为什么数据库里能执行,项目里不行?

这个问题很典型。

数据库客户端执行时,链路是:

sql 复制代码
SQL → PostgreSQL

而项目中执行时,链路可能是:

sql 复制代码
MyBatis XML
  → MyBatis 参数替换
  → 框架 SQL 拦截器
  → 租户/权限/翻译处理
  → JDBC
  → PostgreSQL

所以你在数据库里验证通过,只能说明:

sql 复制代码
SQL 本身符合 PostgreSQL 语法

但不能说明:

sql 复制代码
这段 SQL 在项目框架链路中不会被改写

如果项目里有 SQL 拦截器、租户插件、数据权限插件、SQL 翻译组件,就要特别注意这种差异。


五、排查建议

遇到这类问题时,不要只盯着 SQL 本身,可以按下面顺序排查:

1. 先确认原 SQL 是否能在数据库中执行

如果数据库中都不能执行,那就是 SQL 写法问题。

如果数据库中可以执行,但项目中失败,继续往下查。

2. 打印最终执行 SQL

重点看程序最终发给数据库的 SQL,是否和 XML 里写的一致。

如果发现 SQL 被改写、排序、加条件、换别名,就说明中间有框架处理。

3. 检查是否存在 SQL 翻译/租户/权限拦截器

常见影响点包括:

sql 复制代码
租户字段自动注入
数据权限 SQL 改写
分页插件
多数据库方言转换
SQL 翻译器
自定义 MyBatis Interceptor

4. 对 PostgreSQL 特有语法加跳过翻译标记

例如:

arduino 复制代码
/*!sql_no_translate*/

尤其是这类 PostgreSQL 语法:

sql 复制代码
DELETE ... USING ...
INSERT ... RETURNING ...
UPDATE ... FROM ...
DISTINCT ON (...)
LATERAL JOIN
IS DISTINCT FROM

这些语法在 PostgreSQL 中很常见,但某些通用 SQL 解析器未必支持完整。


六、踩坑总结

这次问题的核心结论是:

SQL 在数据库里能正常执行,不代表它在 MyBatis 项目中一定能正常执行。中间如果存在 SQL 翻译、租户注入、权限拦截等框架逻辑,SQL 可能在真正到达数据库前已经被改写。

对于 PostgreSQL 的特殊语法,尤其是:

sql 复制代码
DELETE ... USING ...

如果项目框架存在 SQL 改写能力,建议:

markdown 复制代码
1. 优先打印最终 SQL;
2. 确认 SQL 是否被框架改写;
3. 必要时使用 sql_no_translate 跳过翻译;
4. 表别名尽量写完整,例如 USING table AS alias;
5. 不要只用数据库客户端执行结果判断项目一定没问题。

这类问题表面看是 SQL 报错,实际往往是框架链路问题。排查时要区分清楚:

复制代码
数据库语法问题

sql 复制代码
项目框架 SQL 改写问题

否则很容易在 SQL 本身上反复修改,浪费大量时间。

相关推荐
颜酱几秒前
07 | 把字段与指标同步到 Qdrant(生成阶段)
前端·人工智能·后端
Larcher20 分钟前
从“加载模型”界面到端侧推理:拆解一个 React + WebGPU 大模型 Demo
javascript·后端
极客先躯21 分钟前
高级java每日一道面试题-2026年05月03日-实战篇[Docker]-如何实现容器化环境的数据加密?
java·运维·docker·容器·金融·加解密·高级面试
xqqxqxxq1 小时前
Java Socket 多人聊天室(私聊+群聊)技术笔记(V4版本)
java·网络·笔记
Larcher1 小时前
从状态快照到惰性初始化:读懂 React useState 的三个关键场景
javascript·人工智能·后端
lazy H1 小时前
Git clone 怎么用?克隆项目及常见问题完整教程
大数据·git·后端·学习·搜索引擎·github
wang09071 小时前
自己动手写一个spring之aop_1
java·后端·spring
神奇小汤圆1 小时前
IDEA 运行报 Command line is too long?别慌,两招搞定(附原理)
后端
SelectDB2 小时前
Apache Doris 4.1 全面增强 Iceberg:支持 UPDATE、MERGE INTO 与 Iceberg V3
后端
国科安芯2 小时前
FreeRTOS RISC-V 浮点上下文切换移植:在 IAR 工程中完整保存 FPU 寄存器
java·开发语言·单片机·嵌入式硬件·算法·系统架构·risc-v