OceanBase 中一个关于 NOT IN 子查询的 SQL 优化案例
通过一个案例了解 not in 对 NULL 值敏感的处理逻辑和优化方法。
作者:胡呈清,爱可生 DBA 团队成员,擅长故障分析、性能优化,个人博客:[简书 | 轻松的鱼],欢迎讨论。
爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
本文约 3300 字,预计阅读需要 11 分钟。
数据库版本:OceanBase3.2.3.3
问题描述
前段时间碰到一个慢 SQL,NOT IN 子查询被优化器改写成了 NESTED-LOOP ANTI JOIN,但是被驱动表全表扫描无法使用索引,执行耗时 16 秒。SQL 如下:
SELECT AGENT_ID, MAX(REL_AGENT_ID) FROM T_LDIM_AGENT_UPREL WHERE AGENT_ID NOT IN (select AGENT_ID from T_LDIM_AGENT_UPREL where valid_flg = '1') group by AGENT_ID;
简略执行计划如下:
============================================================================================== |ID|OPERATOR |NAME |EST. ROWS|COST | ---------------------------------------------------------------------------------------------- |0 |MERGE GROUP BY | |146 |62970523| |1 | NESTED-LOOP ANTI JOIN| |149 |62970511| |2 | TABLE SCAN |T_LDIM_AGENT_UPREL(I_LDIM_AGENT_UPREL_AGENT_ID)|27760 |10738 | |3 | MATERIAL | |13880 |11313 | |4 | SUBPLAN SCAN |VIEW1 |13880 |11115 | |5 | TABLE SCAN |T_LDIM_AGENT_UPREL |13880 |10906 | ==============================================================================================
问题分析
1. 分析表结构、数据量
表结构如下,关联字段 AGENT_ID
是有索引的:
CREATE TABLE "T_LDIM_AGENT_UPREL" ( "REL_AGENT_ID" NUMBER(22) CONSTRAINT "T_LDIM_AGENT_UPREL_OBNOTNULL_1679987669730612" NOT NULL ENABLE, "AGENT_ID" NUMBER(22), "EMPLOYEE_ID" NUMBER(22), "EMP_PARTY_FULLNAME" VARCHAR2(60), "GRP_ID" NUMBER(22), "GRP_PARTY_FULLNAME" VARCHAR2(255), "CS_ID" NUMBER(22), "CS_ORGAN_NAME" VARCHAR2(255), "CRT_DTTM" DATE, "LASTUPT_DTTM" DATE, "VALID_FLG" VARCHAR2(1), "VALID_DTTM" DATE, "INVALID_DTTM" DATE, CONSTRAINT "PK_T_LDIM_AGENT_UPREL" PRIMARY KEY ("REL_AGENT_ID") ); CREATE INDEX "IDX_T_LDIM_AGENT_UPREL_CT" on "T_LDIM_AGENT_UPREL" ("CRT_DTTM") GLOBAL ; CREATE INDEX "IDX_T_LDIM_AGENT_UPREL_LT" on "T_LDIM_AGENT_UPREL" ("LASTUPT_DTTM") GLOBAL ; CREATE INDEX "I_LDIM_AGENT_UPREL_AGENT_ID" on "T_LDIM_AGENT_UPREL" ("AGENT_ID") GLOBAL ;
数据量:T_LDIM_AGENT_UPREL
表一共 2.7 万行,子查询结果 3900 行。
2. 判断直接原因
从执行计划、表结构和数据量来看,这个 SQL 效率低有两个原因:
- 关联字段
AGENT_ID
有索引,但对被驱动表做查询时却使用全表扫描,效率必定低。为什么不走索引? - 既然被驱动表不走索引,基于代价的比较,优化器为什么没有选择更高效的 HASH ANTI JOIN?
问题得一个一个看,先分析第二个问题。
3. 使用 HINT 干预 JOIN 算法
使用如下 HINT 都不生效(并且尝试了 Outline Data 中的写法):
/*+ use_hash(A B)*/ /*+ USE_HASH(@"SEL$1" ("VIEW1"@"SEL$1" )) */ /*+ NO_USE_NL_AGGREGATION */
执行计划显示 Used Hint 部分都为空,说明 HINT 无法生效,原因未知:
Used Hint: ------------------------------------- /*+ */ Outline Data: ------------------------------------- /*+ BEGIN_OUTLINE_DATA NO_USE_HASH_AGGREGATION(@"SEL$1") LEADING(@"SEL$1" ("REPORT.A"@"SEL$1" "VIEW1"@"SEL$1" )) USE_NL(@"SEL$1" ("VIEW1"@"SEL$1" )) PQ_DISTRIBUTE(@"SEL$1" ("VIEW1"@"SEL$1" ) LOCAL LOCAL) USE_NL_MATERIALIZATION(@"SEL$1" ("VIEW1"@"SEL$1" )) INDEX(@"SEL$1" "REPORT.A"@"SEL$1" "I_LDIM_AGENT_UPREL_AGENT_ID") FULL(@"SEL$2" "REPORT.B"@"SEL$2") END_OUTLINE_DATA */
4. 对比 Oracle 执行计划
Tips:当 OB 上看到的执行计划不符合预期,但又找不到原因时,可以对比 Oracle 的执行计划。
Oracle 上执行计划如下(这里得用 set autotrace on
的方式查看真实执行计划):
- 可以使用 HASH ANTI JOIN,并且有个重要信息 HASH JOIN RIGHT ANTI NA (EXPLAIN 是看不到 NA 的),
直接搜索就可以得到大概的解释 NA 即 Null-Aware Anti Join,这种反连接能够处理 NULL 值。啥意思?下面展开讲讲。
SQL> set autotrace on SQL> SELECT AGENT_ID, MAX(REL_AGENT_ID) FROM T_LDIM_AGENT_UPREL WHERE AGENT_ID NOT IN (select AGENT_ID from T_LDIM_AGENT_UPREL where valid_flg = '1') group by AGENT_ID; 2 3 4 5 6 no rows selected Execution Plan ---------------------------------------------------------- Plan hash value: 1033962367 ----------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 9 | 171 | 276 (2)| 00:00:04 | | 1 | HASH GROUP BY | | 9 | 171 | 276 (2)| 00:00:04 | |* 2 | HASH JOIN RIGHT ANTI NA| | 9672 | 179K| 275 (2)| 00:00:04 | |* 3 | TABLE ACCESS FULL | T_LDIM_AGENT_UPREL | 3886 | 31088 | 137 (1)| 00:00:02 | | 4 | TABLE ACCESS FULL | T_LDIM_AGENT_UPREL | 28098 | 301K| 137 (1)| 00:00:02 | ----------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("AGENT_ID"="AGENT_ID") 3 - filter("VALID_FLG"='1')
5. NULL 值与 NOT IN
为了更好的说明 NULL 值对 NOT IN 的影响,下面举个简单的例子:
create table t1(a number,b varchar2(50),c varchar2(50) not null); insert into t1 values(1,'aaa','aaa'),(2,'bbb','bbb'),(3,'ccc','ccc'),(4,NULL,'ddd'); commit;
只要 NOT IN 后面的子查询或者常量集合一旦有 NULL 值出现,则整个 SQL 的执行结果就会为 NULL:
obclient [TESTUSER]> select * from t1 where b not in('aaa',NULL); Empty set (0.004 sec) obclient [TESTUSER]> select tt.b from t1 tt where tt.a=4; +------+ | B | +------+ | NULL | +------+ 1 row in set (0.007 sec) obclient [TESTUSER]> select t.* from t1 t where b not in(select tt.b from t1 tt where tt.a=4); Empty set (0.005 sec)
NOT EXISTS 对 NULL 值不敏感,这意味着 NULL 值对 NOT EXISTS 的执行结果不会有什么影响:
obclient [TESTUSER]> select t.* from t1 t where not EXISTS (select tt.b from t1 tt where t.b=tt.b and tt.a=4); +------+------+-----+ | A | B | C | +------+------+-----+ | 1 | aaa | aaa | | 2 | bbb | bbb | | 3 | ccc | ccc | | 4 | NULL | ddd | +------+------+-----+ 4 rows in set (0.005 sec)
IN 对 NULL 值也不敏感:
obclient [TESTUSER]> select * from t1 where b in('aaa',NULL); +------+------+-----+ | A | B | C | +------+------+-----+ | 1 | aaa | aaa | +------+------+-----+ 1 row in set (0.004 sec) obclient [TESTUSER]> select t.* from t1 t where b in(select tt.b from t1 tt where tt.a<5); +------+------+-----+ | A | B | C | +------+------+-----+ | 1 | aaa | aaa | | 2 | bbb | bbb | | 3 | ccc | ccc | +------+------+-----+ 3 rows in set (0.002 sec)
结合 Null-Aware Anti Join,我们可以得到如下结论:
NOT IN 和 <>ALL 对 NULL 值敏感,这意味着 NOT IN 后面的子查询或者常量集合一旦有 NULL 值出现,则整个 SQL 的执行结果就会为 NULL。
所以一旦相关的连接列上出现了 NULL 值(实际只会判断字段是否有 NOT NULL 约束),此时 Oracle 如果还按照通常的 ANTI JOIN 的处理逻辑来处理(实际和 INNER JOIN 的处理逻辑一致,差别在于只取不满足关联条件的结果,而 INNER JOIN 对 NULL 值是不敏感的),得到的结果就不对了。
为了解决 NOT IN 和 <>ALL 对 NULL 值敏感的问题,Oracle 推出了改良的 ANTI JOIN(11g 新增了参数 _OPTIMIZER_NULL_AWARE_ANTIJOIN
,默认为 true),这种反连接能够处理 NULL 值,Oracle 称其为 Null-Aware Anti Join(在真实的执行计划中显示为 XX ANTI NA)。
6. 小结
到这里我们能解释一个问题:为什么 OB 不能使用 HASH ANTI JOIN ?
原因是关联字段 AGENT_ID
没有 NOT NULL 约束,由于 NOT IN 对 NULL 敏感,不能使用普通的 ANTI JOIN,否则遇到 NULL 结果将不正确。Oracle 11g 推出的 Null-Aware ANTI JOIN 可以处理 NULL 敏感的场景,但是 OB 3.x 还没有这个功能,因此不能使用 HASH ANTI JOIN ,4.x 版本将推出 _OPTIMIZER_NULL_AWARE_ANTIJOIN
参数,和 Oracle 保持一致。
优化建议
既然 NOT IN 对 NULL 敏感,有两个优化方向,先和业务确认 NOT IN 子查询结果集有没有可能出现 NULL,如果不会进一步确认关联字段 AGENT_ID
是否会有 NULL 值,如果不会则下面三种方式任选其一,最佳选择是方法 1,最符合开发规范:
- 给
AGENT_ID
字段加上 NOT NULL 约束,这样优化器就可以使用 HASH ANTI JOIN 了; - NOT EXISTS 对 NULL 值不敏感,因此可以将 NOT IN 改写(或者也可以改写成 LEFT JOIN WHERE xx IS NULL 这种 ANTI JOIN 语法):
SELECT AGENT_ID, MAX(REL_AGENT_ID) FROM T_LDIM_AGENT_UPREL t1 WHERE NOT EXISTS (select AGENT_ID from T_LDIM_AGENT_UPREL t2 where t1.agent_id=t2.agent_id and valid_flg = '1') group by AGENT_ID;
改写后的执行计划走了 HASH RIGHT ANTI JOIN,执行耗时只要 50ms:
========================================================================== |ID|OPERATOR |NAME |EST. ROWS|COST | -------------------------------------------------------------------------- |0 |HASH GROUP BY | |146 |46828| |1 | HASH RIGHT ANTI JOIN| |149 |46697| |2 | SUBPLAN SCAN |VIEW1 |13880 |11115| |3 | TABLE SCAN |T2 |13880 |10906| |4 | TABLE SCAN |T1(I_LDIM_AGENT_UPREL_AGENT_ID)|27760 |10738| ==========================================================================
- 给父查询、子查询都加上 AND AGENT_ID is NOT NULL 条件,也可以让优化器走 HASH ANTI JOIN:
SELECT AGENT_ID, MAX(REL_AGENT_ID) FROM T_LDIM_AGENT_UPREL WHERE AGENT_ID NOT IN (select AGENT_ID from T_LDIM_AGENT_UPREL where valid_flg = '1' and AGENT_ID is not null ) and AGENT_ID is not null group by AGENT_ID;
执行计划:
========================================================================================== |ID|OPERATOR |NAME |EST. ROWS|COST | ------------------------------------------------------------------------------------------ |0 |HASH GROUP BY | |146 |47472| |1 | HASH RIGHT ANTI JOIN| |149 |47341| |2 | SUBPLAN SCAN |VIEW1 |13880 |11173| |3 | TABLE SCAN |T_LDIM_AGENT_UPREL |13880 |10965| |4 | TABLE SCAN |T_LDIM_AGENT_UPREL(I_LDIM_AGENT_UPREL_AGENT_ID)|27760 |11324| ==========================================================================================
答疑
问题 1. HASH JOIN 只能用于关联条件的等值查询,不支持连接条件是大于、小于、不等于和 LIKE 的场景。为什么 NOT IN、NOT EXISTS 可以使用 HASH ANTI JOIN?
NOT IN、NOT EXISTS 子查询和 WHERE t1.a!=t2.a
看起来相似,但其实语义是不一样的,下面例子可以说明。NOT IN 的语义其实是说如果有相等的值,则外表结果丢弃,因此本质上 NOT IN 的实现方式还是做等值查找,所以 HASH ANTI JOIN 的实现本质和 HASH JOIN 一样,只是在返回结果时做了相反的判断。
obclient [TESTUSER]> select * from t1 t join t1 tt on t.a!=tt.a; +------+------+-----+------+------+-----+ | A | B | C | A | B | C | +------+------+-----+------+------+-----+ | 1 | aaa | aaa | 2 | bbb | bbb | | 1 | aaa | aaa | 3 | ccc | ccc | | 1 | aaa | aaa | 4 | NULL | ddd | | 2 | bbb | bbb | 1 | aaa | aaa | | 2 | bbb | bbb | 3 | ccc | ccc | | 2 | bbb | bbb | 4 | NULL | ddd | | 3 | ccc | ccc | 1 | aaa | aaa | | 3 | ccc | ccc | 2 | bbb | bbb | | 3 | ccc | ccc | 4 | NULL | ddd | | 4 | NULL | ddd | 1 | aaa | aaa | | 4 | NULL | ddd | 2 | bbb | bbb | | 4 | NULL | ddd | 3 | ccc | ccc | +------+------+-----+------+------+-----+ 12 rows in set (0.005 sec) obclient [TESTUSER]> select t.* from t1 t where a not in(select tt.a from t1 tt); Empty set (0.005 sec)
这个还可以用 Oracle 的执行计划和优化报告来验证:
##执行计划的2号算子 HASH JOIN RIGHT ANTI NA 有如下条件,这里能说明是做的等值查找 2 - access("AGENT_ID"="AGENT_ID") ##另外可以通过下面方法查看优化器改写后的SQL: alter session set tracefile_identifier='10053c'; alter session set events '10053 trace name context forever,level 1'; 执行 SQL; alter session set events '10053 trace name context off'; cd /u01/oracle/diag/rdbms/repo/repo/trace cat repo_ora_6702_10053c.trc 在 "Final query after transformations" 部分即为优化器改写后的SQL,关联条件也是等值查询: Final query after transformations:******* UNPARSED QUERY IS ******* SELECT "T_LDIM_AGENT_UPREL"."AGENT_ID" "AGENT_ID",MAX("T_LDIM_AGENT_UPREL"."REL_AGENT_ID") "MAX(REL_AGENT_ID)" FROM "REPORT"."T_LDIM_AGENT_UPREL" "T_LDIM_AGENT_UPREL","REPORT"."T_LDIM_AGENT_UPREL" "T_LDIM_AGENT_UPREL" WHERE "T_LDIM_AGENT_UPREL"."AGENT_ID"="T_LDIM_AGENT_UPREL"."AGENT_ID" AND "T_LDIM_AGENT_UPREL"."VALID_FLG"='1' GROUP BY "T_LDIM_AGENT_UPREL"."AGENT_ID" kkoqbc: optimizing query block SEL$5DA710D3 (#1)
问题 2. 为什么 OB 可以使用 NESTED-LOOP ANTI JOIN?它能处理 NULL 敏感?怎么实现的?因为它的实现方式导致了对被驱动表只能全表扫描不能走索引?
从结果来看,OB 的 NESTED-LOOP ANTI JOIN 查询结果正确,能处理 NULL 敏感。
实现方式可以从执行计划看出一些端倪:
============================================================================================== |ID|OPERATOR |NAME |EST. ROWS|COST | ---------------------------------------------------------------------------------------------- |0 |MERGE GROUP BY | |146 |62970523| |1 | NESTED-LOOP ANTI JOIN| |149 |62970511| |2 | TABLE SCAN |T_LDIM_AGENT_UPREL(I_LDIM_AGENT_UPREL_AGENT_ID)|27760 |10738 | |3 | MATERIAL | |13880 |11313 | |4 | SUBPLAN SCAN |VIEW1 |13880 |11115 | |5 | TABLE SCAN |T_LDIM_AGENT_UPREL |13880 |10906 | ============================================================================================== Outputs & filters: ------------------------------------- 0 - output([T_LDIM_AGENT_UPREL.AGENT_ID(0x7eeef19c3fe0)], [T_FUN_MAX(T_LDIM_AGENT_UPREL.REL_AGENT_ID(0x7eeef19c50f0))(0x7eeef19c49e0)]), filter(nil), group([T_LDIM_AGENT_UPREL.AGENT_ID(0x7eeef19c3fe0)]), agg_func([T_FUN_MAX(T_LDIM_AGENT_UPREL.REL_AGENT_ID(0x7eeef19c50f0))(0x7eeef19c49e0)]) 1 - output([T_LDIM_AGENT_UPREL.AGENT_ID(0x7eeef19c3fe0)], [T_LDIM_AGENT_UPREL.REL_AGENT_ID(0x7eeef19c50f0)]), filter(nil), conds([(T_OP_OR, T_LDIM_AGENT_UPREL.AGENT_ID(0x7eeef19c3fe0) = VIEW1.AGENT_ID(0x7eeef19ce070)(0x7eeef19ce360), (T_OP_IS, T_LDIM_AGENT_UPREL.AGENT_ID(0x7eeef19c3fe0), NULL, 0)(0x7eeef19cf2e0), (T_OP_IS, VIEW1.AGENT_ID(0x7eeef19ce070), NULL, 0)(0x7eeef19cfee0))(0x7eeef19cec00)]), nl_params_(nil), batch_join=false 2 - output([T_LDIM_AGENT_UPREL.AGENT_ID(0x7eeef19c3fe0)], [T_LDIM_AGENT_UPREL.REL_AGENT_ID(0x7eeef19c50f0)]), filter(nil), access([T_LDIM_AGENT_UPREL.AGENT_ID(0x7eeef19c3fe0)], [T_LDIM_AGENT_UPREL.REL_AGENT_ID(0x7eeef19c50f0)]), partitions(p0), is_index_back=false, range_key([T_LDIM_AGENT_UPREL.AGENT_ID(0x7eeef19c3fe0)], [T_LDIM_AGENT_UPREL.REL_AGENT_ID(0x7eeef19c50f0)]), range(MIN,MIN ; MAX,MAX)always true 3 - output([VIEW1.AGENT_ID(0x7eeef19ce070)]), filter(nil) 4 - output([VIEW1.AGENT_ID(0x7eeef19ce070)]), filter(nil), access([VIEW1.AGENT_ID(0x7eeef19ce070)]) 5 - output([T_LDIM_AGENT_UPREL.AGENT_ID(0x7eeef1a609a0)]), filter([T_LDIM_AGENT_UPREL.VALID_FLG(0x7eeef1a606b0) = ?(0x7eeef1a60c90)]), access([T_LDIM_AGENT_UPREL.VALID_FLG(0x7eeef1a606b0)], [T_LDIM_AGENT_UPREL.AGENT_ID(0x7eeef1a609a0)]), partitions(p0), is_index_back=false, filter_before_indexback[false], range_key([T_LDIM_AGENT_UPREL.REL_AGENT_ID(0x7eeef1a821a0)]), range(MIN ; MAX)always true
把 1 号 NESTED-LOOP ANTI JOIN 算子的 Outputs & filters 单独拿出来看:
1 - output([T_LDIM_AGENT_UPREL.AGENT_ID(0x7eeef19c3fe0)], [T_LDIM_AGENT_UPREL.REL_AGENT_ID(0x7eeef19c50f0)]), filter(nil), conds([(T_OP_OR, T_LDIM_AGENT_UPREL.AGENT_ID(0x7eeef19c3fe0) = VIEW1.AGENT_ID(0x7eeef19ce070)(0x7eeef19ce360), (T_OP_IS, T_LDIM_AGENT_UPREL.AGENT_ID(0x7eeef19c3fe0), NULL, 0)(0x7eeef19cf2e0), (T_OP_IS, VIEW1.AGENT_ID(0x7eeef19ce070), NULL, 0)(0x7eeef19cfee0))(0x7eeef19cec00)]), nl_params_(nil), batch_join=false
匹配条件是:
where T_LDIM_AGENT_UPREL.AGENT_ID=VIEW1.AGENT_ID Or T_LDIM_AGENT_UPREL.AGENT_ID is NULL -- 判断父查询AGENT_ID是否为空,如果遇到 NULL值,则剔除这行结果 Or VIEW1.AGENT_ID is NULL -- 判断子查询结果集 AGENT_ID是否为 NULL,如果遇到NULL值,直接进入JOIN_END阶段,不返回任何数据
以上逻辑是可以实现 NULL 值敏感的。
按照这个逻辑,即使加上 Or VIEW1.AGENT_ID IS NULL 条件,被驱动表依然是可以使用索引的,只有 IS NOT NULL 无法使用索引:
##SQL select AGENT_ID from T_LDIM_AGENT_UPREL where AGENT_ID='124253' or AGENT_ID is null; ##执行计划 ============================================================================== |ID|OPERATOR |NAME |EST. ROWS|COST| ------------------------------------------------------------------------------ |0 |TABLE SCAN|T_LDIM_AGENT_UPREL(I_LDIM_AGENT_UPREL_AGENT_ID)|1 |46 | ============================================================================== Outputs & filters: ------------------------------------- 0 - output([T_LDIM_AGENT_UPREL.AGENT_ID(0x7eef739f9120)]), filter(nil), access([T_LDIM_AGENT_UPREL.AGENT_ID(0x7eef739f9120)]), partitions(p0), is_index_back=false, range_key([T_LDIM_AGENT_UPREL.AGENT_ID(0x7eef739f9120)], [T_LDIM_AGENT_UPREL.REL_AGENT_ID(0x7eef73a40830)]), range(124253,MIN ; 124253,MAX), (NULL,MIN ; NULL,MAX), range_cond([T_LDIM_AGENT_UPREL.AGENT_ID(0x7eef739f9120) = ?(0x7eef739f7a50) OR (T_OP_IS, T_LDIM_AGENT_UPREL.AGENT_ID(0x7eef739f9120), NULL, 0)(0x7eef739f86d0)(0x7eef739f6dd0)])
按照经验,此时我们应该到 Oracle 上看看 NESTED-LOOP ANTI JOIN NA 的处理逻辑,不过在 Oracle 上调不出这个执行计划,因此线索中断。
推断:
目前 3.x 版本没有实现真正意义上的 NESTED-LOOP ANTI JOIN NA,但是 NESTED-LOOP ANTI JOIN 可以正确处理 NULL 敏感。4.x 会实现 NESTED-LOOP ANTI JOIN NA,实现方式就是我们前面推理出的逻辑,也就是说 3.x 用的不是这一套逻辑,执行计划虽然这么显示,但实际不一样,对被驱动表匹配查询时就是要遍历全表,不能直接走索引匹配。
问题 3. 加 /+ no_rewrite / 后,走 SUBPLAN FILTER 算子,父查询显示可以走索引,为什么执行效率还是慢?
加 /*+ no_rewrite */
的执行计划,执行耗时 7 秒,比原始 SQL 耗时 16 秒快,从执行逻辑来看:
-
这里是非相关子查询,每次重复执行的结果都是一样的,所以执行一次后保存在参数集合中(init_plan_idxs_([1]) 表示子查询只需要执行一次)。
-
从参数中拿到右边非相关子查询的结果,下推 FILTER 到左边计划,执行父查询,注意看条件是 A.AGENT_ID!= ALL(subquery(1)),这里是 !=,因此无法使用索引快速过滤数据,需要扫描整个索引,所以执行效率并不高。如果这里不是 NOT IN 而是 IN,则可以走索引快速查找。
====================================================================== |ID|OPERATOR |NAME |EST. ROWS|COST | ---------------------------------------------------------------------- |0 |MERGE GROUP BY | |3659 |58062035| |1 | SUBPLAN FILTER| |13880 |58061224| |2 | TABLE SCAN |A(I_LDIM_AGENT_UPREL_AGENT_ID)|27760 |10738 | |3 | TABLE SCAN |B |13880 |10906 | ====================================================================== Outputs & filters: ------------------------------------- 0 - output([A.AGENT_ID(0x7ee843c44330)], [T_FUN_MAX(A.REL_AGENT_ID(0x7ee843c45440))(0x7ee843c44d30)]), filter(nil), group([A.AGENT_ID(0x7ee843c44330)]), agg_func([T_FUN_MAX(A.REL_AGENT_ID(0x7ee843c45440))(0x7ee843c44d30)]) 1 - output([A.AGENT_ID(0x7ee843c44330)], [A.REL_AGENT_ID(0x7ee843c45440)]), filter([A.AGENT_ID(0x7ee843c44330) != ALL(subquery(1)(0x7ee843bf8e60))(0x7ee843bf8470)]), exec_params_(nil), onetime_exprs_(nil), init_plan_idxs_([1]) 2 - output([A.AGENT_ID(0x7ee843c44330)], [A.REL_AGENT_ID(0x7ee843c45440)]), filter(nil), access([A.AGENT_ID(0x7ee843c44330)], [A.REL_AGENT_ID(0x7ee843c45440)]), partitions(p0), is_index_back=false, range_key([A.AGENT_ID(0x7ee843c44330)], [A.REL_AGENT_ID(0x7ee843c45440)]), range(MIN,MIN ; MAX,MAX)always true 3 - output([B.AGENT_ID(0x7ee843c41350)]), filter([B.VALID_FLG(0x7ee843c40c40) = ?(0x7ee843c40520)]), access([B.VALID_FLG(0x7ee843c40c40)], [B.AGENT_ID(0x7ee843c41350)]), partitions(p0), is_index_back=false, filter_before_indexback[false], range_key([B.REL_AGENT_ID(0x7ee843cb5bb0)]), range(MIN ; MAX)always true Used Hint: ------------------------------------- /*+ NO_REWRITE(@"SEL$1") */
更多技术文章,请访问:https://opensource.actionsky.com/
关于 SQLE
SQLE 是一款全方位的 SQL 质量管理平台,覆盖开发至生产环境的 SQL 审核和管理。支持主流的开源、商业、国产数据库,为开发和运维提供流程自动化能力,提升上线效率,提高数据质量。
SQLE 获取

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
物联网中的预测分析:当IoTDA遇上ModelArts
本文分享自华为云社区《最佳实践:华为云IoTDA结合ModelArts实现预测分析》,作者:华为IoT云服务。 场景说明 在物联网解决方案中,针对庞大的数据进行自动学习时,需要对海量数据进行标注、训练,按照传统的方式进行标注、训练不仅耗时耗力,而且对资源消耗也是非常庞大的。华为云物联网平台可以通过规则引擎,将数据转发到华为云其他云服务,可实现将海量数据通过函数工作流(FunctionGraph)进行处理,再将数据流入AI开发平台(ModelArts)进行AI分析,并将分析结果统一转发至HTTP服务器中。 图1场景说明 在本示例中,我们实现以下场景: 设备上报银行客户特征信息,物联网平台将数据转发至FunctionGraph,由FunctionGraph转发至ModelArts进行AI分析,最终将分析的结果转发至HTTP服务器中。 整体流程 创建并发布ModelArts模型。 创建FunctionGraph函数。 构建一个HTTP服务器。 创建MQTT协议产品,并创建设备。 创建流转规则,将数据流转至FunctionGraph。 查看HTTP服务器是否收到AI分析后的消息。 前提条件 已...
- 下一篇
计算机网络协议介绍
一、从一个请求来看网络分层原理 1.1 复杂的网络 以下为一次请求过程中可能遇到的问题,预示着网络的复杂性。 1.2 如何简化复杂度 为了简化网络的复杂度,网络通信的不同方面被分解为多层次结构,每一层只与紧挨着的上层或者下层进行交互,将网络分层,这样就可以修改,甚至替换某一层的软件,只要层与层之间的接口保持不变,就不会影响到其他层。 1.2.1 OSI( Open System Interconnection Reference Model): 开放系统互联参考模型 1.2.2 TCP/IP 协议族 1.2.3 两种协议的对应关系 应用层:应用程序负责的部分 传输层:TCP、UDP、SCTP 等 网络层:IPv4、IPv6等 数据链路层:以太网、无限LAN(WIFI) 物理层:光纤、双绞线电缆、无线设备 1.3 一个请求的分层解析流程 请求各层之间都是调用对应层的接口(这个接口可以类比java中的接口,它可以有各种实现方式)。 1. 在请求过程中域名是无法直接被计算机识别的,必须先转换成ip,此时先检测本地是否配置了h...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS关闭SELinux安全模块
- CentOS8安装Docker,最新的服务器搭配容器使用
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Windows10,CentOS7,CentOS8安装Nodejs环境
- 设置Eclipse缩进为4个空格,增强代码规范