OracleSQL优化案例-3

复制代码
WITH T1 AS (
SELECT a.SECU_ID || cast(TO_CHAR(a.TRD_DT,'YYYY-MM-DD')  AS varchar2(200)) AS ONE_ID
FROM STK_HK_EXCH_QUOT a JOIN BASE_SECURITY_INFO b ON a.SECU_ID = b.SECURITY_ID  AND a.ROR_FCTR IS NOT null
where a.IS_DELETE = 0 AND b.IS_DELETE = 0 
AND a.TRD_DT >= #{curt_running_dt} 
AND a.TRD_DT <= #{next_running_dt}
),
T2 AS (
SELECT 
			ID,SECU_ID,TRD_DT,TRD_STS,CPRC,DAY_CHG,DAY_CHG_RAT,TUROV_RATE,DAY_AMP,FMKT_VAL,TMKT_VAL,PE,PE_DYM,PETTM,PB,PB_NEW,PC,PC_DYM,PCTTM,PS,PS_DYM,PSTTM,TRD_AMT_RW,
            TRD_VOL_RW,HPRC_RW,LPRC_RW,CHG_RAT_RW,TUROV_RATE_RW,AMP_RAT_RW,TRD_AMT_AVG_RW,TUROV_RATE_AVG_RW,CHG_RW,TRD_AMT_TW,TRD_VOL_TW,HPRC_TW,LPRC_TW,CHG_RAT_TW,TUROV_RATE_TW,
            AMP_RAT_TW,TRD_AMT_AVG_TW,TUROV_RATE_AVG_TW,CHG_TW,TRD_AMT_RM,TRD_VOL_RM,HPRC_RM,LPRC_RM,CHG_RAT_RM,TUROV_RATE_RM,AMP_RAT_RM,TRD_AMT_AVG_RM,TUROV_RATE_AVG_RM,CHG_RM,
            TRD_AMT_TM,TRD_VOL_TM,HPRC_TM,LPRC_TM,CHG_RAT_TM,TUROV_RATE_TM,AMP_RAT_TM,TRD_AMT_AVG_TM,TUROV_RATE_AVG_TM,CHG_TM,TRD_AMT_R3M,TRD_VOL_R3M,HPRC_R3M,LPRC_R3M,
            CHG_RAT_R3M,TUROV_RATE_R3M,AMP_RAT_R3M,TRD_AMT_AVG_R3M,TUROV_RATE_AVG_R3M,CHG_R3M,TRD_AMT_R6M,TRD_VOL_R6M,HPRC_R6M,LPRC_R6M,CHG_RAT_R6M,TUROV_RATE_R6M,
            AMP_RAT_R6M,TRD_AMT_AVG_R6M,TUROV_RATE_AVG_R6M,CHG_R6M,TRD_AMT_RY,TRD_VOL_RY,HPRC_RY,LPRC_RY,CHG_RAT_RY,TUROV_RATE_RY,AMP_RAT_RY,TRD_AMT_AVG_RY,
            TUROV_RATE_AVG_RY,CHG_RY,TRD_AMT_TY,TRD_VOL_TY,HPRC_TY,LPRC_TY,CHG_RAT_TY,TUROV_RATE_TY,AMP_RAT_TY,TRD_AMT_AVG_TY,TUROV_RATE_AVG_TY,CHG_TY,
			a.SECU_ID || cast(TO_CHAR(a.TRD_DT,'YYYY-MM-DD')  AS varchar2(200)) AS ONE_ID
FROM STK_HK_MKT_QUOT_INDX a where a.TRD_DT >= #{curt_running_dt} 
AND a.TRD_DT <= #{next_running_dt}
)
SELECT 
            ID,SECU_ID,TRD_DT,TRD_STS,CPRC,DAY_CHG,DAY_CHG_RAT,TUROV_RATE,DAY_AMP,FMKT_VAL,TMKT_VAL,PE,PE_DYM,PETTM,PB,PB_NEW,PC,PC_DYM,PCTTM,PS,PS_DYM,PSTTM,TRD_AMT_RW,
            TRD_VOL_RW,HPRC_RW,LPRC_RW,CHG_RAT_RW,TUROV_RATE_RW,AMP_RAT_RW,TRD_AMT_AVG_RW,TUROV_RATE_AVG_RW,CHG_RW,TRD_AMT_TW,TRD_VOL_TW,HPRC_TW,LPRC_TW,CHG_RAT_TW,TUROV_RATE_TW,
            AMP_RAT_TW,TRD_AMT_AVG_TW,TUROV_RATE_AVG_TW,CHG_TW,TRD_AMT_RM,TRD_VOL_RM,HPRC_RM,LPRC_RM,CHG_RAT_RM,TUROV_RATE_RM,AMP_RAT_RM,TRD_AMT_AVG_RM,TUROV_RATE_AVG_RM,CHG_RM,
            TRD_AMT_TM,TRD_VOL_TM,HPRC_TM,LPRC_TM,CHG_RAT_TM,TUROV_RATE_TM,AMP_RAT_TM,TRD_AMT_AVG_TM,TUROV_RATE_AVG_TM,CHG_TM,TRD_AMT_R3M,TRD_VOL_R3M,HPRC_R3M,LPRC_R3M,
            CHG_RAT_R3M,TUROV_RATE_R3M,AMP_RAT_R3M,TRD_AMT_AVG_R3M,TUROV_RATE_AVG_R3M,CHG_R3M,TRD_AMT_R6M,TRD_VOL_R6M,HPRC_R6M,LPRC_R6M,CHG_RAT_R6M,TUROV_RATE_R6M,
            AMP_RAT_R6M,TRD_AMT_AVG_R6M,TUROV_RATE_AVG_R6M,CHG_R6M,TRD_AMT_RY,TRD_VOL_RY,HPRC_RY,LPRC_RY,CHG_RAT_RY,TUROV_RATE_RY,AMP_RAT_RY,TRD_AMT_AVG_RY,
            TUROV_RATE_AVG_RY,CHG_RY,TRD_AMT_TY,TRD_VOL_TY,HPRC_TY,LPRC_TY,CHG_RAT_TY,TUROV_RATE_TY,AMP_RAT_TY,TRD_AMT_AVG_TY,TUROV_RATE_AVG_TY,CHG_TY,
           CASE WHEN ONE_ID IN (SELECT ONE_ID FROM T1 GROUP BY ONE_ID) THEN 0 ELSE 1 END AS IS_DELETE_NEW
FROM T2;帮忙优化下这段OracleSQL代码,其中 T1 和 T2 的数据量相当且都是百万级

下面从可读性、性能、可维护性 三个角度给你一个针对百万级数据量的 Oracle SQL 优化版本,并解释每一步为什么这么改。


一、当前 SQL 存在的主要性能瓶颈

结合你给出的 SQL,主要问题有:

  1. T1 / T2 都做全表扫描 TRD_DT范围过滤,但如果没有合适索引,会扫百万行
  2. 字符串拼接 || TO_CHAR(...)作为 JOIN/IN 条件 无法使用普通 BTree 索引 会导致大量 CPU + 内存消耗
  3. CASE WHEN ONE_ID IN (SELECT ...) IN (子查询)在百万级数据下非常容易变成 FILTER 操作
  4. 字段过多,SELECT * 风格 即使只需要少量字段,也会读整行数据
  5. 重复计算 ONE_ID在两个 CTE 中都算一次

二、优化总体思路(强烈推荐)

✅ 核心优化点:

  • 用 EXISTS 替代 IN
  • 去掉 ONE_ID的字符串拼接
  • 让 T1 / T2 直接通过 (SECU_ID, TRD_DT)关联
  • 给大表建立 组合索引
  • 避免 CTE 物化(Oracle 12c+)

三、等价改写后的高性能版本(推荐)

✅ 优化后 SQL(逻辑完全等价)

复制代码
SELECT
       t2.ID,
       t2.SECU_ID,
       t2.TRD_DT,
       t2.TRD_STS,
       t2.CPRC,
       t2.DAY_CHG,
       t2.DAY_CHG_RAT,
       t2.TUROV_RATE,
       t2.DAY_AMP,
       t2.FMKT_VAL,
       t2.TMKT_VAL,
       t2.PE,
       t2.PE_DYM,
       t2.PETTM,
       t2.PB,
       t2.PB_NEW,
       t2.PC,
       t2.PC_DYM,
       t2.PCTTM,
       t2.PS,
       t2.PS_DYM,
       t2.PSTTM,
       -- 后续字段省略,实际保留你需要的
       CASE
           WHEN EXISTS (
               SELECT 1
               FROM STK_HK_EXCH_QUOT a
               JOIN BASE_SECURITY_INFO b
                 ON a.SECU_ID = b.SECURITY_ID
              WHERE a.SECU_ID = t2.SECU_ID
                AND a.TRD_DT  = t2.TRD_DT
                AND a.ROR_FCTR IS NOT NULL
                AND a.IS_DELETE = 0
                AND b.IS_DELETE = 0
           )
           THEN 0
           ELSE 1
       END AS IS_DELETE_NEW
FROM STK_HK_MKT_QUOT_INDX t2
WHERE t2.TRD_DT BETWEEN #{curt_running_dt}
                     AND #{next_running_dt};

✅ 优点

  • 不再需要 T1
  • 不再拼接字符串
  • EXISTS在百万级数据下性能远优于 IN
  • Oracle 可以直接用 (SECU_ID, TRD_DT)索引

四、必须建立的索引(关键)

✅ 1️⃣ STK_HK_MKT_QUOT_INDX(主表)

复制代码
CREATE INDEX IDX_MKT_QUOT_DT_SEC
ON STK_HK_MKT_QUOT_INDX (TRD_DT, SECU_ID);

✅ 2️⃣ STK_HK_EXCH_QUOT(被 EXISTS 引用的表)

复制代码
CREATE INDEX IDX_EXCH_QUOT_DT_SEC
ON STK_HK_EXCH_QUOT (TRD_DT, SECU_ID, ROR_FCTR)
WHERE IS_DELETE = 0;

⚠️ 如果 Oracle 12c+,建议用 Partial Index(上面已体现)

✅ 3️⃣ BASE_SECURITY_INFO

复制代码
CREATE INDEX IDX_BASE_SEC_ID
ON BASE_SECURITY_INFO (SECURITY_ID)
WHERE IS_DELETE = 0;

五、如果你一定要保留 CTE(不推荐)

若业务必须使用 T1 / T2,请至少这样改写:

复制代码
WITH T1 AS (
    SELECT DISTINCT SECU_ID, TRD_DT
    FROM STK_HK_EXCH_QUOT a
    JOIN BASE_SECURITY_INFO b
      ON a.SECU_ID = b.SECURITY_ID
     AND a.ROR_FCTR IS NOT NULL
     AND a.IS_DELETE = 0
     AND b.IS_DELETE = 0
    WHERE a.TRD_DT BETWEEN #{curt_running_dt}
                       AND #{next_running_dt}
),
T2 AS (
    SELECT *
    FROM STK_HK_MKT_QUOT_INDX
    WHERE TRD_DT BETWEEN #{curt_running_dt}
                     AND #{next_running_dt}
)
SELECT t2.*,
       CASE WHEN (t2.SECU_ID, t2.TRD_DT) IN (SELECT SECU_ID, TRD_DT FROM T1)
            THEN 0 ELSE 1 END AS IS_DELETE_NEW
FROM T2 t2;

✅ 但仍不如 EXISTS 方案


六、性能对比预期(经验值)

写法 执行时间(百万级)
原始 SQL 30s ~ 数分钟
EXISTS 改写 2 ~ 5s
加索引 + EXISTS < 1s

七、总结一句话

不要再用字符串拼接做主键判断,也不要用 IN (子查询),百万级数据下一定用 EXISTS + 组合索引。

相关推荐
涛思数据(TDengine)4 分钟前
TDengine TSDB 3.4.3.3 上线:流计算分层闭环、虚拟表跨层引用,查询关联一次写完
数据库·人工智能·数据分析·时序数据库·tdengine·工业
远方16096 分钟前
118-五大进程LGWR DBWn CKPT SMON PMON 与存储故障-文件系统对照
大数据·数据库·sql·oracle·database
ss2738 分钟前
AI时代首先数据库-PostgreSQL实战笔记 | 17 PostgreSQL SQL高级查询:窗口函数、CASE、COALESCE
数据库·笔记·postgresql
远方160914 分钟前
119-Oracle CDB 下修改和重置 SYS SYSTEM密码记录
大数据·数据库·sql·oracle·database
橙光成长18 分钟前
Context Mode 深度解析:用沙盒、SQLite 与钩子路由,把 AI 编码 Agent 的上下文消耗削减 98%
数据库·人工智能·sqlite
数据库小学妹34 分钟前
MySQL时区问题排查:TIMESTAMP和DATETIME的区别与跨时区对账踩坑
数据库·mysql·datetime·时区·timestamp·跨时区对账
创世虚拟世界1 小时前
把 3D 虚拟世界拉到本地:vibe coding 的手感,是从看见自己那一行改动开始的
javascript·数据库·人工智能·3d·gitee·虚拟现实
俊昭喜喜里1 小时前
Springboot的Mybatis中的条件构造器
数据库·mybatis
倔强的石头_1 小时前
列存储到底快在哪 在金仓KES上把原理和实操跑了一遍
数据库