我们的文章会在微信公众号 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 再过滤 | 否 | 近全表 | 未下推 |
验收门禁(生产也适用):
- 调用前后在 Oracle 抓
V$SQL(可先FLUSH SHARED_POOL或按sql_id差分); - 远程文本须出现
publish_time >= TO_DATE('实际交易日...'); ROWS_PROCESSED应接近命中量级,而不是远端大表全量。
5 建议及总结
- Yashan → Oracle DBLink 场景下,不要指望 PL/SQL 变量日期条件自动下推;以 Oracle 端 SQL 为准。
- 单日跑批保持原业务语义即可:算日 → 校验
^[0-9]{8}$→ 动态 SQL 把上下界都写成字面量(勿在串里留SYSDATE)。 - 「一次 JOIN 全部交易日」解决不了下推;多日需求应外层按日循环,每日仍用字面量。
- 墙钟快慢另做同口径压测(连续执行 3 次取第 3 次);本文证据链停在「远端扫描行数 / 远程 SQL 形态」。
作者:黄廷忠,现就职深圳计算科学研究院,曾任职 Oracle、OceanBase、云和恩墨、东方龙马。