ITPub博客

首页 > 数据库 > Oracle > 同样的 SQL,Oracle 跑得飞快,达梦却慢成狗?

同样的 SQL,Oracle 跑得飞快,达梦却慢成狗?

原创 Oracle 作者:pingdanorcale 时间:2026-06-23 21:15:25 0 删除 编辑

 

在国产化替代的浪潮下,越来越多的团队把 Oracle 迁移到达梦(DM)数据库。大多数语法都能平滑迁移,但总有一些"隐藏关卡"等着你。

下面这条 SQL,功能是把人员表中逗号分隔的 txx1 字段拆分成多行:


SELECT jxx1, rxx1,

       REGEXP_SUBSTR(txx1,'[^,]+',1,LEVEL) AS txx1
FROM  t_tb1
WHERE dxx1='123'
  AND  RYZT='1'
  AND  jxx1    IS NOT NULL
  AND  txx1   IS NOT NULL
  AND  zxx1 IS NULL
CONNECT BY
  LEVEL <= LENGTH(txx1)-LENGTH(REPLACE(txx1,',',''))+1
  AND PRIOR rxx1 = rxx1
  AND PRIOR DBMS_RANDOM.VALUE IS NOT NULL;

在 Oracle 上跑得好好的,迁到达梦之后执行时间飙升,甚至出现超时。问题出在哪里?


问题根源分析

⚠ 痛点一:过滤时机太晚

WHERE 条件本应先于 CONNECT BY 执行,但部分数据库实现里递归展开发生在过滤之前,导致全表参与递归,开销爆炸。

⚠ 痛点二:每行都要计算长度

LENGTH(txx1)-LENGTH(REPLACE(txx1,',','')) 对每一个递归节点都要算两次字符串长度,达梦对此优化不如 Oracle 激进。

⚠ 痛点三:DBMS_RANDOM 代价大

PRIOR DBMS_RANDOM.VALUE 是经典的"打破 CONNECT BY 循环检测"技巧,但随机数生成在达梦中性能开销更大。

⚠ 痛点四:CONNECT BY 非原生优势

达梦对 CONNECT BY 的内部实现与 Oracle 不同,在字符串拆分这类"宽而浅"的场景下,递归 CTE 往往更稳定高效。


优化方案一:改良版 CONNECT BY

如果团队暂时不想大改写法,可以先做以下三处替换,通常能带来明显提升:

  • • 1用 WITH 子句先过滤数据,让递归只处理目标行集。
  • • 2把 LENGTH 差值计算换成 REGEXP_COUNT,语义更清晰,部分引擎可复用正则结果。
  • • 3把 DBMS_RANDOM.VALUE 换成 SYS_GUID(),随机开销更小。

WITH T AS (

  SELECT JSH, RYBH, TABH
  FROM  T_RYXX_KSSZSRY
  WHERE DWDM='140100111'
    AND  RYZT='10'
    AND  JSH    IS NOT NULL
    AND  TABH   IS NOT NULL
    AND  ZZCZJG IS NULL
)
SELECT JSH, RYBH,
       REGEXP_SUBSTR(TABH,'[^,]+',1,LEVEL) AS TABH
FROM  T
CONNECT BY
  LEVEL <= REGEXP_COUNT(TABH, ',') + 1
  AND PRIOR RYBH = RYBH
  AND PRIOR SYS_GUID() IS NOT NULL;

此方案改动小,兼容性好,适合快速止血。但若数据量大、txx1 字段很长,仍建议升级到方案二。


优化方案二:达梦推荐 — 递归 CTE

这是达梦官方更推荐的字符串拆分姿势。用 WITH RECURSIVE 逐位推进游标,完全避开 CONNECT BY 和正则,性能更稳定。


WITH RECURSIVE T (jxx1, rxx1, txx1, POS, VALUE) AS (

  -- 锚点:取第一段(逗号前)
  SELECT jxx1, rxx1, txx1, 1,
         SUBSTR(txx1, 1, INSTR(txx1||',',',')-1)
  FROM  t_tb1
  WHERE dxx1='123'
    AND  RYZT='1'
    AND  jxx1    IS NOT NULL
    AND  txx1   IS NOT NULL
    AND  zxx1 IS NULL

  UNION ALL

  -- 递归:依次推进到下一个逗号
  SELECT jxx1, rxx1, txx1, POS+1,
         SUBSTR(txx1,
                INSTR(txx1,',',1,POS)+1,
                INSTR(txx1||',',',',1,POS+1)
                  - INSTR(txx1,',',1,POS)-1)
  FROM  T
  WHERE INSTR(txx1,',',1,POS) > 0
)
SELECT jxx1, rxx1, VALUE AS txx1
FROM  T;

核心逻辑是:锚点部分取第一个逗号前的字符串,递归部分每次把游标 POS 加一,用 INSTR 精确定位下一段的起止位置,终止条件是找不到更多逗号为止。


三种写法横向对比

维度 原始写法 方案一 方案二(推荐)
达梦兼容性 一般 较好 最佳
大数据量性能 慢 中等 快
改动幅度 — 小 中等
可读性 中等 较好 清晰(显式游标)
依赖正则 是 是 否(纯字符函数)
适用场景 Oracle 原生 快速止血 生产首选

迁移建议

  • • 1**摸底先行:**梳理所有用到 CONNECT BY + REGEXP_SUBSTR 拆分逗号字段的 SQL,这类写法是重点排查对象。
  • • 2**小改先上:**方案一改动最小,可作为紧急优化先上线,快速解除慢查询告警。
  • • 3**长效方案:**利用版本迭代窗口,逐步将字符串拆分逻辑改写为递归 CTE,才是达梦环境下真正稳定的解法。
  • • 4**业务层优化:**如果 txx1 字段存储的是多值,考虑在应用层拆分后再写入,从根本上消灭数据库端字符串拆分的需求。

国产化迁移不只是"换个驱动",执行引擎的内部实现差异,才是那些"莫名其妙变慢"的真正元凶。遇到性能骤降,先看递归和正则,往往能快速定位。

 


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/10201716/viewspace-3174286/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2009-01-02

  • 博文量
    158
  • 访问量
    221183