Oracle大表比对禁用MINUS/EXCEPT,因其强制排序去重、不支持CLOB、NULL处理异常;应采用NVL+索引JOIN分三类差异识别,MySQL需用LEFT/RIGHT JOIN+UNION ALL模拟FULL JOIN。

直接在存储过程中用 MINUS 或 EXCEPT 对大表做全量比对,基本等于主动触发超时、临时表空间爆炸或 ORA-00932 报错——它强制排序+去重,字段含 CLOB 就直接崩,NULL 还静默失效。
Oracle 存储过程里别碰 MINUS 做大表比对
这不是写法问题,是执行模型决定的:哪怕两表都有主键索引,SELECT * FROM t1 MINUS SELECT * FROM t2 仍会先全扫、再排序、再去重。千万级数据下,临时表空间动辄几十 GB,执行时间从分钟级跳到小时级。更隐蔽的问题是:NULL = NULL 在 MINUS 中不成立,但也不报错,导致本该标为“值不同”的行被漏掉;字段顺序或类型稍有不一致,立刻 ORA-01789;别名完全无效,实际比的是列表达式本身。
- 仅适用于万行以内、无
CLOB/BLOB、且确认NULL不参与关键逻辑的小表校验 - 若必须用,务必提前
SELECT COUNT(*)确认数据量,避免在生产存储过程中“盲跑” - 永远不要在
WHERE条件里嵌套MINUS子查询——优化器大概率放弃索引,退化为嵌套循环
FULL JOIN + NVL 是 Oracle 大表比对唯一可控方案
核心思路是把“左有右无”“右有左无”“同主键但字段值不同”三类差异拆开识别,全程走索引连接,避免中间结果集膨胀。关键不在语法多炫,而在 NULL 处理和谓词写法。
- 连接字段(如
id)必须有索引,否则FULL JOIN退化为笛卡尔积 - 所有参与比对的字段必须套
NVL(col, '##NULL##')或COALESCE(col, -999),否则a.name != b.name遇到任一端为NULL永远返回FALSE -
WHERE条件里禁止写a.col1 != b.col1 OR a.col2 != b.col2,要拆成独立判断:NVL(a.col1, 'X') != NVL(b.col1, 'X') OR NVL(a.col2, 'X') != NVL(b.col2, 'X'),防止短路优化跳过后续字段 - 企业版可加
/*+ parallel(4) */提示,对分区表效果显著;但需确认目标表未被其他长事务锁住,否则并行反而加剧阻塞
MySQL 存储过程中无法用 FULL OUTER JOIN,得手写 LEFT/RIGHT JOIN + UNION ALL
MySQL 直接解析 FULL OUTER JOIN 会报 ERROR 1054 或语法错误。必须用 LEFT JOIN 找出左表独有行,RIGHT JOIN 找出右表独有行,再用 UNION ALL 合并——UNION 会去重,而重复本身就是差异线索。
- 左右两边
SELECT的列数、顺序、类型必须严格一致,缺一列就得补NULL AS col_name - 必须显式加
source字段,例如'left_only' AS source和'right_only' AS source,否则合并后无法区分来源 - 不能依赖
IS NULL判断缺失,要写成WHERE b.id IS NULL(左表独有)和WHERE a.id IS NULL(右表独有),避免因ON条件里用了函数导致索引失效 - 如果表有自增主键,注意
INSERT ... SELECT可能触发auto_increment值偏移,比对前先SELECT MAX(id)留底
所有数据库都绕不开的底层陷阱:NULL、索引、字段类型
无论用什么语法,只要没处理这三点,结果就不可信。不是“差不多”,是根本没法用。
-
NULL不是值,是“未知”,所以任何与NULL的等值/不等值比较都返回UNKNOWN,被WHERE当作FALSE过滤掉——这是最常被忽略的静默错误源 - 没有主键或唯一索引的表,
FULL JOIN或NOT EXISTS都会触发全表扫描,性能随数据量平方级恶化 - 字段类型隐式转换(比如
VARCHAR2和CHAR、NUMBER和INTEGER)会导致索引失效,且某些数据库(如 SQL Server)在EXCEPT中会因类型不精确报错
真正卡住人的从来不是语法不会写,而是看到 NULL 就习惯性跳过,或者以为建了索引就万事大吉——但索引字段参与 NVL 或函数运算后,就只是个摆设。

















