笔记 | 实战避坑|从根源解决 MySQL/PG 迁移 KES LEFT JOIN 数据异常问题

开发过程中有些细节容易被忽略,今天挑几个重点聊一聊。

目录

前言

一、现场现象:迁移后 LEFT JOIN 查询诡异丢数据

1. 实际业务场景

2. 沿用源库的错误 SQL 写法

业务预期结果

KES 实际运行结果

执行计划真实佐证

二、底层原理:为什么 KES 会改写 LEFT JOIN 为内连接?

2.1 SQL 执行顺序导致的隐性过滤

2.2 KES 优化器的性能优化逻辑

三、特殊豁免场景:不会触发外连接消除的情况

安全无坑示例 SQL

核心内核逻辑

四、KES 迁移实战避坑方案

方案一:右表过滤条件全部下移至 ON 子句(通用最优解)

方案二:Oracle(+) 兼容语法专属处理方式

方案三:左表 WHERE 过滤无任何丢数据风险

方案四:通过执行计划快速排查隐患 SQL

五、迁移落地总结与团队开发规范

1. 核心开发准则

2. 迁移落地优先级建议


前言

最近在负责多套 MySQL、PostgreSQL 业务系统迁移金仓 KES 的落地工作,过程中碰到了一个挺隐蔽且折磨人的问题:挺多原本在源库运行正常的 LEFT JOIN 查询,迁移到 KES 后,业务数据莫名少了一大半。

最让人头疼的是,程序完全没有报错,SQL 语句一行没改、业务逻辑也完全没变,单纯换了数据库,结果集就对不上了。初期排查时,我们反复核对代码、校验表数据、比对业务逻辑,始终找不到问题根源,一度怀疑是数据库迁移同步数据出了问题。

后续拆解执行计划才发现,这根本不是数据同步问题,也不是 KES 数据库 Bug,而是 KES 优化器的外连接消除机制在悄悄生效。相比于 MySQL、PostgreSQL,KES 的查询优化更加激进,会自动改写外连接语句,将 LEFT JOIN 转为 INNER JOIN,最终导致业务数据丢失。

这是异构数据库迁移中挺典型的兼容性坑,大量开发、运维、DBA 都会中招。我结合本次落地踩坑经验,从现场现象、底层原因、特殊场景、实操解决方案全方位梳理,给正在做 KES 迁移的小伙伴避坑。

一、现场现象:迁移后 LEFT JOIN 查询诡异丢数据

1. 实际业务场景

为了方便大家复现和搞懂,我简化了线上业务模型,核心就是两张最普通的关联表,也是日常开发中最常用的一对多关联场景:

  • t1:主业务表(左表),存储核心业务数据,业务需求要求务必保留全量数据,无论关联附表是否有数据,主表记录都不能丢失。
  • t2:附属信息表(右表),存储主表的拓展数据,属于可选关联,无匹配数据时,字段展示 NULL 即可。
本次业务需求很简单:查询 t1 所有数据,仅关联匹配 t2 中 name2 等于「cc」的记录,不匹配的 t1 数据正常保留,附表字段置空。

这套逻辑在 MySQL、PostgreSQL 中运行完全正常,结果符合业务预期,但迁移至 KES 后,直接出现数据缺失。

2. 沿用源库的错误 SQL 写法

挺多人迁移时会直接复用原有 SQL,这也是踩坑的核心原因,下面是典型的错误写法:

-- 危险写法:将右表过滤条件写在 WHERE 子句中
SELECT * FROM t1
LEFT JOIN t2 ON t1.id1 = t2.id2
WHERE t2.name2 = 'cc';

业务预期结果

完整返回 t1 全部数据行;t2 匹配成功且 name2 为 cc 的展示对应附表数据,未匹配的 t1 数据保留,t2 相关字段显示 NULL。

KES 实际运行结果

只返回 t1 和 t2 完全匹配、且满足 name2='cc' 的交集数据,所有无附表匹配的主表数据全部被过滤掉,相当于彻底丢失了一部分业务数据。

执行计划真实佐证

我们通过 KES 执行计划排查后发现,语句中明明写的是 LEFT JOIN,但执行计划里已经没有了左外连接算子,被自动替换成了普通的 Hash Join、Nested Loop 内连接执行逻辑。这就是 KES 优化器自动触发的外连接消除,也是数据丢失的真正原因。

二、底层原理:为什么 KES 会改写 LEFT JOIN 为内连接?

挺多开发同学疑惑:明明写了左连接保数据,数据库为什么会自作主张改写法?其实这是由 SQL 执行规则和 KES 优化器的智能等价变换共同决定的,并非数据库异常。

2.1 SQL 执行顺序导致的隐性过滤

标准 SQL 的执行逻辑是 先关联 JOIN,再执行 WHERE 过滤,这个顺序是所有数据库通用的,也是问题的根源:

第一步,执行 LEFT JOIN 关联后,所有 t1 数据都会保留,没有匹配 t2 的数据行,t2 所有字段都会填充为 NULL; 第二步,执行 WHERE t2.name2 = 'cc' 过滤条件,数据库判断 NULL = 'cc' 属于未知不匹配结果,会直接过滤掉这部分空值行; 最终结果就是:所有不匹配的主表数据全部被剔除,和直接使用 INNER JOIN 的查询结果完全一样。

2.2 KES 优化器的性能优化逻辑

相比于 MySQL、PG 的宽松优化策略,KES 优化器的等价校验更加严格。优化器在解析 SQL 时,会自动判断语句的逻辑等价性:如果「LEFT JOIN + WHERE 右表常规过滤」的最终结果和 INNER JOIN 完全一致,那外连接就没有任何存在意义。

为了降低查询开销、减少数据扫描和关联计算成本,KES 会主动做优化改写,直接将外连接降级为效率更高的内连接。站在数据库性能角度,这个优化是合理的,但对于迁移业务来说,就会直接打破原有业务逻辑,造成数据丢失。

三、特殊豁免场景:不会触发外连接消除的情况

这里重点提醒大家:不是所有写在 WHERE 里的右表条件都会触发优化改写,有且仅有一种特殊场景是豁免的,也是我们日常查询空数据常用的写法。

如果 WHERE 条件是 右表 IS NULL 判空,KES 绝对不会执行外连接消除。

安全无坑示例 SQL

-- 安全写法:查询主表中无附表匹配的数据,不会触发外连接消除
SELECT * FROM t1
LEFT JOIN t2 ON t1.id1 = t2.id2
WHERE t2.name2 IS NULL;

核心内核逻辑

IS NULL 的语义本身就是为了筛选 LEFT JOIN 产生的空值数据。如果优化器把这条语句改成内连接,就彻底查不到「主表无关联数据」的记录,直接导致业务功能失效。

KES 优化器能够精准识别这种业务语义,为了保证查询结果绝对准确,会主动跳过优化改写逻辑,完整保留 LEFT JOIN 特性。这也是我们排查问题、编写空值查询时需要牢记的关键点。

四、KES 迁移实战避坑方案

结合本次迁移踩坑经验,我整理了四套高频实用的解决方案,覆盖标准 SQL 写法、Oracle 兼容语法、条件区分、问题排查,适配绝大多数迁移场景。

方案一:右表过滤条件全部下移至 ON 子句(通用最优解)

这是最稳妥、兼容性最好、全数据库通用的修复方式,也是官方推荐的开发规范。

大家只要记住一个简单的判断逻辑:想要保留左表全量数据,所有针对右表的筛选条件,一律写在 ON 后面,不要写在 WHERE 后面

调整后执行逻辑会完全改变:数据库先过滤右表有效数据,再和左表做外连接,从根源上避免空值行被过滤,彻底杜绝外连接消除问题。

-- 修复后标准写法,KES、MySQL、PG 全兼容,无数据丢失问题
SELECT * FROM t1
LEFT JOIN t2 ON t1.id1 = t2.id2
AND t2.name2 = 'cc'; -- 附表筛选条件放入ON子句

方案二:Oracle(+) 兼容语法专属处理方式

很多老旧业务是从 Oracle 迁移至 KES,会沿用 KES 兼容的 Oracle(+) 隐式外连接语法,这也是高频踩坑点,很多人因为写法不规范导致隐性丢数据。

核心规则挺关键:使用(+)语法时,WHERE 子句中所有右表的过滤条件,务必全部带上(+),否则一定会触发外连接消除。

错误示范(丢数据):

SELECT * FROM t1, t2
WHERE t1.id1 = t2.id2(+)
AND t2.name2 = 'cc'; -- 右表条件未加(+),触发优化改写,数据丢失

正确示范(无风险):

SELECT * FROM t1, t2
WHERE t1.id1 = t2.id2(+)
AND t2.name2(+) = 'cc'; -- 右表条件统一加(+),等价ON子句过滤

补充说明:(+)语法仅适配 Oracle 兼容模式,新项目建议直接使用标准 LEFT JOIN 写法,可读性和兼容性更好。

方案三:左表 WHERE 过滤无任何丢数据风险

很多同学容易混淆左右表条件的区别,这里明确说明:只有右表的常规 WHERE 过滤会触发外连接消除,针对左主表的 WHERE 过滤是绝对安全的

左表是外连接的驱动表,对左表的过滤属于业务前置筛选,只会剔除不符合条件的主表数据,不会改变 LEFT JOIN 的外连接属性,KES 不会做任何优化改写。

-- 左表过滤安全写法,无外连接消除风险
SELECT * FROM t1
LEFT JOIN t2 ON t1.id1 = t2.id2
WHERE t1.name1 = 'a';

方案四:通过执行计划快速排查隐患 SQL

在批量迁移上线阶段,不可能逐条人工校验逻辑,最高效的方式就是通过 KES 执行计划批量筛查问题 SQL。

给大家整理了快速判断标准:

  • 正常 LEFT JOIN:执行计划包含 Left Join / Left Outer 算子
  • 异常被优化改写:算子变为 Hash Join / Nested Loop,无 Left 标识,同时结果集偏少
日常排查可直接使用以下命令,快速定位异常语句:

EXPLAIN ANALYZE SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2 WHERE t2.name2 = 'cc';

五、迁移落地总结与团队开发规范

经过本次批量迁移踩坑,我们团队统一整理了适配 MySQL、PG 迁移 KES 的开发规范,规避后续批量出现同类问题,非常适合团队落地执行。

1. 核心开发准则

  • 分清语义边界:ON 子句只用来定义表与表的关联规则,WHERE 子句只用来做最终结果的全局过滤;
  • 规范附表过滤:除 IS NULL 空值查询外,所有右表筛选条件,统一迁移至 ON 子句;
  • 老旧语法规范:使用 Oracle(+) 语法时,右表所有过滤条件务必统一追加(+)符号。

2. 迁移落地优先级建议

异构数据库迁移,数据一致性永远优先于性能优化。KES 的外连接消除是优秀的性能优化机制,但在迁移初期,我们必须优先对齐 MySQL、PG 的原有业务逻辑,保证数据准确无误,后续再根据业务场景做性能调优。

同时建议将 JOIN 类 SQL 纳入上线强制审计范围,只要是含 LEFT/RIGHT JOIN 的语句,必须查看执行计划,确认连接类型未被自动改写。批量迁移场景下,可通过脚本批量扫描 WHERE 子句中的右表过滤条件,统一批量整改,提升迁移效率。


这篇笔记就先到这里,后面用到新的思路或者发现有问题再补充。

评论 (0)

暂无评论