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

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

问题 SQL 大致如下:

复制代码

<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 结构被重新排序 DELETE USING 语法失效

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

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


二、原因分析

PostgreSQL 支持这种写法:

复制代码

DELETE FROM table1 t1 USING table2 t2 WHERE t1.id = t2.id;

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

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

复制代码

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

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

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

复制代码

DELETE FROM schema.table t1 USING schema.table2 t2 WHERE ...

框架可能会错误识别:

复制代码

表别名 USING 语法 DELETE 目标表 关联表 WHERE 条件归属

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

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

复制代码

PostgreSQL 不支持 DELETE USING

而是:

复制代码

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


三、解决方式

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

复制代码

<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>

关键改动有两个:

复制代码

1. 增加 /*!sql_no_translate*/ 2. USING 表别名改成显式 AS t2

其中最核心的是:

复制代码

/*!sql_no_translate*/

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

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

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


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

这个问题很典型。

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

复制代码

SQL → PostgreSQL

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

复制代码

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

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

复制代码

SQL 本身符合 PostgreSQL 语法

但不能说明:

复制代码

这段 SQL 在项目框架链路中不会被改写

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


五、排查建议

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

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

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

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

2. 打印最终执行 SQL

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

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

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

常见影响点包括:

复制代码

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

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

例如:

复制代码

/*!sql_no_translate*/

尤其是这类 PostgreSQL 语法:

复制代码

DELETE ... USING ... INSERT ... RETURNING ... UPDATE ... FROM ... DISTINCT ON (...) LATERAL JOIN IS DISTINCT FROM

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


六、踩坑总结

这次问题的核心结论是:

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

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

复制代码

DELETE ... USING ...

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

复制代码

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

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

复制代码

数据库语法问题

复制代码

项目框架 SQL 改写问题