YashanDB → Oracle DBLink:PL/SQL 变量日期条件未下推

0 阅读6分钟

我们的文章会在微信公众号 IT民工的龙马人生 和博客网站 ( www.htz.pw )同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢!

由于博客中有大量代码,通过页面浏览效果更佳。

案例摘要

  • 现象:YashanDB 通过异构 DBLink 访问 Oracle 大表时,过程里用 TO_DATE(v_trade_date,'yyyymmdd') 过滤远端日期列,业务结果正确,但 Oracle 端近乎全表拉回。
  • 根因:该日期谓词留在 Yashan 侧过滤,没有进入发往 Oracle 的远程 SQL。
  • 改法:先算出 v_trade_date,上界用 TO_CHAR(SYSDATE,...) 取当前时刻;动态 SQL 把上下界都拼成字面量后 EXECUTE IMMEDIATE(字符串里不要留 SYSDATE 关键字)。
  • 效果(实验室,Oracle 对远端新闻体表):ROWS_PROCESSED 约 497500 → 1990;远程 SQL 出现日期下界。
  • 版本:YashanDB 23.5.6.100 → Oracle 19c

本文只关心「谓词有没有下推」,不做耗时倍数结论(实验室数据量有限,COUNT/INSERT 口径也不宜直接比墙钟)。文中主机、服务名、对象名均为实验室脱敏示例。


1 问题引入

迁移/联调一类「按交易日刷舆情」的过程时,核心过滤长这样(业务过程已大幅裁剪,只留能复现问题的骨架):

DECLARE
  v_trade_date CHAR(8) := '20261101';  -- 上一交易日
  v_cnt INT;
BEGIN
  SELECT COUNT(*)
  INTO v_cnt
  FROM remote_news_hdr@ora_dblink t1
  JOIN remote_news_body@ora_dblink t2 ON t2.newscode = t1.newscode
  WHERE t1.flag <> 1
    AND t2.flag <> 1
    AND t2.publish_time >= TO_DATE(v_trade_date, 'yyyymmdd')
    AND t2.publish_time < SYSDATE;
END;
/

结果集往往只有几千行,但跑起来仍慢。怀疑点很自然:日期条件有没有推到 Oracle?


2 怎么确认:看 Oracle 端真实 SQL

Yashan 侧 EXPLAIN 只能辅助;是否下推要以 Oracle V$SQL 为准。

实验室造数:Oracle 侧新闻表约 50 万行,其中约 2000 行落在 publish_time >= 2026-11-01。Yashan 建 DBLink:

CREATE DATABASE LINK ora_dblink
CONNECT TO lab_user IDENTIFIED BY "******"
USING 'oracle:192.168.10.20:1521/orcl';

2.1 变量写法(改写前)

与现场过程一致的形态是:下界用变量,上界用 SYSDATE:

AND t2.publish_time >= TO_DATE(v_trade_date, 'yyyymmdd')
AND t2.publish_time < SYSDATE

Yashan 执行后(实验室实测:COUNT=1990),Oracle V$SQL 里典型只有常量侧条件,看不到由 v_trade_date 展开的日期下界,也看不到 SYSDATE 上界。完整远程 SQL(对象/列名已脱敏)如下:

ROWS_PROCESSED = 497500

SELECT "NEWSCODE","PUBLISH_TIME","FLAG"
  FROM "REMOTE_NEWS_BODY" "T2"
 WHERE "T2"."FLAG" <> 1

要点:

  • 投影里虽有 PUBLISH_TIME,但 WHERE 中没有任何日期谓词;
  • 没有 PUBLISH_TIME >= TO_DATE('2026-11-01 00:00:00',...)——这才是 v_trade_date 该带来、却未下推的谓词;
  • 没有 PUBLISH_TIME < SYSDATE(或等价上界)——上界同样留在 Yashan 侧过滤;
  • ROWS_PROCESSED = 497500,说明大结果在 Yashan 侧再按变量日期 / SYSDATE 过滤;
  • 业务 COUNT 仍是 1990——结果行少 ≠ 远端扫得少。

容易误判的一点:若实验里把上界也写成字面量(如 DATE '2026-11-11'),远程 SQL 里会出现 PUBLISH_TIME < TO_DATE('2026-11-11...')。那是上界字面量自己下推了,并不代表 v_trade_date 下界已下推;只要缺少 >= TO_DATE('交易日...'),下推仍算失败。刚才误判正来自这类混用:字面量上界进了 V$SQL,看起来像「有日期谓词」,其实变量下界始终没下推。

2.2 字面量写法(对照)

把条件改成常量日期:

AND t2.publish_time >= TO_DATE('20261101', 'yyyymmdd')
AND t2.publish_time < TO_DATE('2026-11-11 00:00:00', 'yyyy-mm-dd hh24:mi:ss')

Oracle V$SQL 完整远程 SQL(脱敏)如下:

ROWS_PROCESSED = 1990

SELECT "NEWSCODE","PUBLISH_TIME","FLAG"
  FROM "REMOTE_NEWS_BODY" "T2"
 WHERE "T2"."PUBLISH_TIME" >= TO_DATE('2026-11-01 00:00:00','yyyy-mm-dd hh24:mi:ss')
   AND "T2"."PUBLISH_TIME" < TO_DATE('2026-11-11 00:00:00','yyyy-mm-dd hh24:mi:ss')
   AND "T2"."FLAG" <> 1

日期上下界都进了远程 SQL,ROWS_PROCESSED 掉到命中量级。说明引擎能下推常量日期,推不动 PL/SQL 变量 / SYSDATE。

补充:把日期写成「本地表/CTE 的 d.trade_date 再 JOIN」同样不会下推,和变量是一类问题——边界来自本地运行时值。


3 解决方案:动态 SQL 拼字面量

业务逻辑可以不变(仍先算上一交易日,再删/插该日),只改「远端日期怎么写进 SQL 文本」。

注意:动态 SQL 字符串里若仍写关键字 SYSDATE,与静态过程里的 SYSDATE 一样,通常也不会下推(§2.1 已证实)。要与成功取证一致,上下界都应在拼串时变成字面量——下界用 v_trade_date,上界用拼串瞬间的 TO_CHAR(SYSDATE, ...):

DECLARE
  v_trade_date CHAR(8);
  v_end_ts     VARCHAR(19);  -- yyyy-mm-dd hh24:mi:ss
  v_sql        CLOB;
BEGIN
  -- 1) 照旧算出上一交易日
  v_trade_date := '20261101';
  -- 上界:在 PL/SQL 里先取当前时间,再拼进 SQL 文本(变成常量)
  v_end_ts := TO_CHAR(SYSDATE, 'yyyy-mm-dd hh24:mi:ss');

  -- 2) 动态 SQL 注入防护:只允许 8 位数字
  IF v_trade_date IS NULL OR NOT REGEXP_LIKE(v_trade_date, '^[0-9]{8}$') THEN
    RAISE_APPLICATION_ERROR(-20001, 'invalid trade_date');
  END IF;

  -- 3) INSERT/SELECT 用 EXECUTE IMMEDIATE;日期上下界都写成字面量
  v_sql :=
    'INSERT INTO tb_neg_news (...) '
    || 'SELECT ... '
    || 'FROM local_org lo '
    || 'LEFT JOIN ( '
    || '  SELECT ... FROM remote_news_hdr@ora_dblink t1 '
    || '  JOIN remote_news_body@ora_dblink t2 ON ... '
    || '  WHERE t2.publish_time >= TO_DATE(''' || v_trade_date || ''', ''yyyymmdd'') '
    || '    AND t2.publish_time < TO_DATE(''' || v_end_ts || ''', ''yyyy-mm-dd hh24:mi:ss'') '
    || ') t2 ON t2.org_code = lo.org_code '
    || 'WHERE t2.org_code IS NOT NULL';

  EXECUTE IMMEDIATE v_sql;
  COMMIT;
END;
/

拼串后,远端过滤对应的 Oracle V$SQL 完整语句与 §2.2 一致(ROWS_PROCESSED = 1990):

SELECT "NEWSCODE","PUBLISH_TIME","FLAG"
  FROM "REMOTE_NEWS_BODY" "T2"
 WHERE "T2"."PUBLISH_TIME" >= TO_DATE('2026-11-01 00:00:00','yyyy-mm-dd hh24:mi:ss')
   AND "T2"."PUBLISH_TIME" < TO_DATE('2026-11-11 00:00:00','yyyy-mm-dd hh24:mi:ss')
   AND "T2"."FLAG" <> 1

实验室同构 INSERT 过程执行成功,目标表按本地机构关联写入 190 行(与「远端命中 1990、再按本地 org 过滤」一致,不是下推失败)。Yashan 侧拼出的完整运行态 SQL 示例(脱敏):

INSERT INTO tb_neg_news
  (symbol, issuername, newstitle, publishtime, newssource, news_url, trade_date)
SELECT lo.s_info_windcode, lo.issuername, t2.newstitle,
       to_char(t2.publish_time, 'yyyymmdd'), t2.newssource, t2.news_url, '20261101'
  FROM local_org lo
  LEFT JOIN (
    SELECT t2.title AS newstitle,
           t2.publish_time,
           t2.newssource,
           NVL(t2.url1, t2.url2) AS news_url,
           t3.org_code
      FROM remote_news_hdr@ora_dblink t1
      LEFT JOIN remote_news_body@ora_dblink t2 ON t2.newscode = t1.newscode
      LEFT JOIN remote_news_org@ora_dblink t3 ON t3.itcode2 = t1.itcode2
     WHERE t1.sentiment = 1
       AND t1.flag <> 1
       AND t2.flag <> 1
       AND t3.flag <> 1
       AND t2.publish_time >= TO_DATE('20261101', 'yyyymmdd')
       AND t2.publish_time < TO_DATE('2026-11-11 00:00:00', 'yyyy-mm-dd hh24:mi:ss')
       AND t3.org_code IS NOT NULL
  ) t2 ON t2.org_code = lo.org_code
 WHERE t2.org_code IS NOT NULL

若只把下界改成字面量、上界仍写 SYSDATE,下界通常已能大幅裁剪远端行数;但远程 SQL 仍可能缺上界,与上面这份成功 V$SQL 不完全一致。推荐上下界一并字面量化。


4 对照小结

写法远程 SQL 是否含日期下界Oracle ROWS_PROCESSED(示意)判定
TO_DATE(v_trade_date,...)否~497500未下推
DATE '...' / 动态拼 TO_DATE('yyyymmdd',...)是~1990已下推
JOIN 本地 d.trade_date 再过滤否近全表未下推

验收门禁(生产也适用):

  1. 调用前后在 Oracle 抓 V$SQL(可先 FLUSH SHARED_POOL 或按 sql_id 差分);
  2. 远程文本须出现 publish_time >= TO_DATE('实际交易日...');
  3. ROWS_PROCESSED 应接近命中量级,而不是远端大表全量。

5 建议及总结

  1. Yashan → Oracle DBLink 场景下,不要指望 PL/SQL 变量日期条件自动下推;以 Oracle 端 SQL 为准。
  2. 单日跑批保持原业务语义即可:算日 → 校验 ^[0-9]{8}$ → 动态 SQL 把上下界都写成字面量(勿在串里留 SYSDATE)。
  3. 「一次 JOIN 全部交易日」解决不了下推;多日需求应外层按日循环,每日仍用字面量。
  4. 墙钟快慢另做同口径压测(连续执行 3 次取第 3 次);本文证据链停在「远端扫描行数 / 远程 SQL 形态」。

作者:黄廷忠,现就职深圳计算科学研究院,曾任职 Oracle、OceanBase、云和恩墨、东方龙马。