我需要优化这个 Oracle SQL 查询:
select /*+ INDEX(ex1 EXCH_RATE_BK_IDX) */
ex1.currency_id as currency_id,
ex1.underly_currency_id as underly_currency_id,
ex1.type_id as type_id,
ex1.third_id as third_id,
ex1.market_third_id as market_third_id,
ex1.exch_d as exch_d,
ex1.daily_dflt_f as daily_dflt_f,
ex1.exch_rate as exch_rate,
ex1.external_seq_no as external_seq_no,
ex1.creation_d as creation_d,
ex1.creation_user_id as creation_user_id,
ex1.last_modif_d as last_modif_d,
ex1.last_user_id as last_user_id
/* security_level_e = 0 */
from exch_rate ex1
inner join ( select max(exch_d) as max_exch_d, type_id, third_id, market_third_id
from exch_rate
where currency_id = '1004'
and underly_currency_id = '2'
and exch_d >= TO_DATE('24-12-2009 00:00:00', 'DD-MM-YYYY HH24:MI:SS') and exch_d <= TO_DATE('14-10-2012 00:00:00', 'DD-MM-YYYY HH24:MI:SS')
group by type_id, third_id, market_third_id
) ex3
on ex1.exch_d = ex3.max_exch_d
and (ex1.type_id = ex3.type_id or (ex1.type_id is NULL and ex3.type_id is NULL))
and (ex1.third_id = ex3.third_id or (ex1.third_id is NULL and ex3.third_id is NULL))
and (ex1.market_third_id = ex3.market_third_id or (ex1.market_third_id is NULL and ex3.market_third_id is NULL))
where (ex1.currency_id = '1004') and ( ex1.underly_currency_id = '2')
and ((ex1.exch_d >= TO_DATE('24-12-2009 00:00:00', 'DD-MM-YYYY HH24:MI:SS') and (ex1.exch_d <= TO_DATE('14-10-2012 00:00:00', 'DD-MM-YYYY HH24:MI:SS'))))
order by exch_d desc nulls last
;
建表脚本和索引如下:
CREATE TABLE "EXCH_RATE"
( "CURRENCY_ID" NUMBER(14,0) NOT NULL ENABLE,
"UNDERLY_CURRENCY_ID" NUMBER(14,0) NOT NULL ENABLE,
"TYPE_ID" NUMBER(14,0),
"THIRD_ID" NUMBER(14,0),
"MARKET_THIRD_ID" NUMBER(14,0),
"EXCH_D" TIMESTAMP (6) NOT NULL ENABLE,
"DAILY_DFLT_F" NUMBER(3,0) DEFAULT 0 NOT NULL ENABLE,
"EXCH_RATE" NUMBER(23,14) NOT NULL ENABLE,
"EXTERNAL_SEQ_NO" NUMBER(20,0),
"CREATION_D" TIMESTAMP (6),
"CREATION_USER_ID" NUMBER(14,0),
"LAST_MODIF_D" TIMESTAMP (6),
"LAST_USER_ID" NUMBER(14,0),
CONSTRAINT "EXCH_RATE_DAILY_DFLT_CHK" CHECK (daily_dflt_f in (0, 1)) ENABLE,
CONSTRAINT "FK_701001" FOREIGN KEY ("CURRENCY_ID")
REFERENCES "D_ORA_22_1_AAAMAINDB"."CURRENCY" ("ID") ON DELETE CASCADE ENABLE,
CONSTRAINT "FK_701002" FOREIGN KEY ("UNDERLY_CURRENCY_ID")
REFERENCES "D_ORA_22_1_AAAMAINDB"."CURRENCY" ("ID") ON DELETE CASCADE ENABLE,
CONSTRAINT "FK_701003" FOREIGN KEY ("TYPE_ID")
REFERENCES "D_ORA_22_1_AAAMAINDB"."TYPE" ("ID") ENABLE,
CONSTRAINT "FK_701004" FOREIGN KEY ("THIRD_ID")
REFERENCES "D_ORA_22_1_AAAMAINDB"."THIRD_PARTY" ("ID") ENABLE,
CONSTRAINT "FK_701005" FOREIGN KEY ("MARKET_THIRD_ID")
REFERENCES "D_ORA_22_1_AAAMAINDB"."THIRD_PARTY" ("ID") ENABLE
)
CREATE UNIQUE INDEX "EXCH_RATE_BK_IDX" ON "EXCH_RATE" ("CURRENCY_ID", "EXCH_D", "UNDERLY_CURRENCY_ID", "TYPE_ID", "THIRD_ID", "MARKET_THIRD_ID")
解释计划:
Explain plan
Plan hash value: 471245728
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 259 | 29 (4)| 00:00:01 |
|* 1 | FILTER | | | | | |
| 2 | SORT GROUP BY | | 1 | 259 | 29 (4)| 00:00:01 |
|* 3 | HASH JOIN | | 1 | 259 | 28 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | EXCH_RATE_BK_IDX | 2291 | 174K| 14 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| EXCH_RATE | 2291 | 404K| 14 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | EXCH_RATE_BK_IDX | 9 | | 14 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("EX1"."EXCH_D"=MAX("EXCH_D"))
3 - access(SYS_OP_MAP_NONNULL("EX1"."TYPE_ID")=SYS_OP_MAP_NONNULL("TYPE_ID") AND
SYS_OP_MAP_NONNULL("EX1"."THIRD_ID")=SYS_OP_MAP_NONNULL("THIRD_ID") AND
SYS_OP_MAP_NONNULL("EX1"."MARKET_THIRD_ID")=SYS_OP_MAP_NONNULL("MARKET_THIRD_ID"))
4 - access("CURRENCY_ID"=1004 AND "EXCH_D">=TIMESTAMP' 2009-12-24 00:00:00' AND
"UNDERLY_CURRENCY_ID"=2 AND "EXCH_D"<=TIMESTAMP' 2012-10-14 00:00:00')
filter("UNDERLY_CURRENCY_ID"=2)
6 - access("EX1"."CURRENCY_ID"=1004 AND "EX1"."EXCH_D">=TIMESTAMP' 2009-12-24 00:00:00' AND
"EX1"."UNDERLY_CURRENCY_ID"=2 AND "EX1"."EXCH_D"<=TIMESTAMP' 2012-10-14 00:00:00')
filter("EX1"."UNDERLY_CURRENCY_ID"=2)
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
在不知道您的数据是什么样子的情况下,我们无法优化您的查询。不过,我可以提出一些观察:
不要在连接子句中使用
OR
,特别是如果它只是为了处理 NULL
相等性。哈希连接与 NVL()
配合使用会更好,将 NULL
视为两边都可以匹配的特定值:and NVL(ex1.type_id,-1) = NVL(ex3.type_id,-1)
等
如果内部查询块
group by
中的聚合(ex3
)确实减少了其行数,我发现通常需要强制Oracle在块内执行该聚合,而不是将其推迟到连接到表中的表之后父块。为此,请使用 NO_MERGE
提示来防止视图合并:
from exch_rate ex1
inner join ( select /*+ NO_MERGE */ max(exch_d) as max_exch_d, type_id, third_id, market_third_id
from exch_rate
where currency_id = '1004'
and underly_currency_id = '2'
and exch_d >= TO_DATE('24-12-2009 00:00:00', 'DD-MM-YYYY HH24:MI:SS') and exch_d <= TO_DATE('14-10-2012 00:00:00', 'DD-MM-YYYY HH24:MI:SS')
group by type_id, third_id, market_third_id
) ex3
您的日期范围相当广泛(2009 年至 2012 年)。如果只有几种货币(1004 种很常见)并且表中的大部分都是过时的历史条目,那么要求如此长的时间段会使索引的使用产生问题。您可能想要删除
INDEX
提示。事实上,乍一看,在不了解您的数据的情况下(因此只是做出一些可能是错误的假设),我可能会暗示全表扫描和一些并行性,因为它看起来更像是一个报告查询将处理很大一部分数据:
select /*+ USE_HASH(ex3 ex1) FULL(ex1) PARALLEL(8) */
ex1.currency_id as currency_id,
ex1.underly_currency_id as underly_currency_id,
ex1.type_id as type_id,
ex1.third_id as third_id,
ex1.market_third_id as market_third_id,
ex1.exch_d as exch_d,
ex1.daily_dflt_f as daily_dflt_f,
ex1.exch_rate as exch_rate,
ex1.external_seq_no as external_seq_no,
ex1.creation_d as creation_d,
ex1.creation_user_id as creation_user_id,
ex1.last_modif_d as last_modif_d,
ex1.last_user_id as last_user_id
from exch_rate ex1
inner join ( select /*+ NO_MERGE FULL(exch_rate) */ max(exch_d) as max_exch_d, type_id, third_id, market_third_id
from exch_rate
where currency_id = '1004'
and underly_currency_id = '2'
and exch_d >= TO_DATE('24-12-2009 00:00:00', 'DD-MM-YYYY HH24:MI:SS') and exch_d <= TO_DATE('14-10-2012 00:00:00', 'DD-MM-YYYY HH24:MI:SS')
group by type_id, third_id, market_third_id
) ex3
on ex1.exch_d = ex3.max_exch_d
and NVL(ex1.type_id,-1) = NVL(ex3.type_id,-1)
and NVL(ex1.third_id,-1) = NVL(ex3.third_id,-1)
and NVL(ex1.market_third_id,-1) = NVL(ex3.market_third_id,-1)
where (ex1.currency_id = '1004') and ( ex1.underly_currency_id = '2')
and ((ex1.exch_d >= TO_DATE('24-12-2009 00:00:00', 'DD-MM-YYYY HH24:MI:SS') and (ex1.exch_d <= TO_DATE('14-10-2012 00:00:00', 'DD-MM-YYYY HH24:MI:SS'))))
order by exch_d desc nulls last
;
如果您的桌子很小并且您想要获得这一亚秒,您可能需要省略
parallel
提示。并行性在开始时会产生一些开销,因此仅将其用于没有并行性至少需要几秒钟的查询。
当然,如果
currency_id = 1004
是稀有,那么索引仍然有价值,不应该使用这些提示。只有了解您的数据才能确定哪个更合适。