OceanBase的SQL 优化实践: NOT IN 子查询

作者:胡呈清,爱可生 DBA 团队成员,擅于故障分析、性能优化。

数据库版本:OceanBase V3.2.3

本文借助一个案例,来了解 not in 对 NULL 值敏感的处理逻辑,并探讨相应的优化方法。

问题概要

前不久,我遇到了一个慢SQL的case,在这段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 效率低有两个原因:

  1. 关联字段 AGENT_ID 有索引,但对被驱动表做查询时却使用全表扫描,效率必定低。为什么不走索引?
  2. 既然被驱动表不走索引,基于代价的比较,优化器为什么没有选择更高效的 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,最符合开发规范:

  1. 给 AGENT_ID 字段加上 NOT NULL 约束,这样优化器就可以使用 HASH ANTI JOIN 了;
  2. 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|
==========================================================================
  1. 给父查询、子查询都加上 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 秒快,从执行逻辑来看:

  1. 这里是非相关子查询,每次重复执行的结果都是一样的,所以执行一次后保存在参数集合中(init_plan_idxs_([1]) 表示子查询只需要执行一次)。
  2. 从参数中拿到右边非相关子查询的结果,下推 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/575667.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

保姆级银河麒麟V10高级服务器离线安装mysql5.7数据库

离线在银河麒麟高级操作系统v10安装mysql5.7 下载mysql5.7 MySQL :: Download MySQL Community Server (Archived Versions) 2、把下载好的包上传到服务器 3、解压 [root1-0001 ~]# cd /data/mysql[root1-0001 mysql]# tar -zxvf mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz…

Beego框架学习:深入指南

文章目录 Beego框架学习&#xff1a;深入指南安装与设置创建控制器自定义路由使用中间件使用模板引擎使用ORM Beego框架学习&#xff1a;深入指南 Beego是一个快速开发Go语言应用的开源框架&#xff0c;它基于MVC模式设计&#xff0c;提供了一系列的工具和库&#xff0c;使得开…

C++ 之 string类的模拟实现

这学习我有三不学 昨天不学&#xff0c;因为昨天是个过去 明天不学&#xff0c;因为明天还是个未知数 今天不学&#xff0c;因为我们要活在当下&#xff0c;我就是玩嘿嘿~ –❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀-正文开始-❀–❀–…

Docker基础学习(3.Docker架构)

⭐ 作者简介&#xff1a;码上言 ⭐ 代表教程&#xff1a;Spring Boot vue-element 开发个人博客项目实战教程 ⭐专栏内容&#xff1a;个人博客系统 ⭐我的文档网站&#xff1a;http://xyhwh-nav.cn/ ⭐微信公众号&#xff1a;码上言 文章目录 Docker基本概念1、镜像&…

JavaWeb-自学JSP组件报告

自学JSP组件报告 一、组件资源及作用 1. commons-fileupload-1.2.2.jar 组件作用&#xff1a;用于处理HTTP文件上传请求&#xff0c;提供了文件上传的解析和存储功能。它允许开发者在Web应用中轻松实现文件上传功能。 2. commons-io-2.4.jar 组件作用&#xff1a;提供了一…

springboot+vue新疆肉牛智慧牧场养殖系统

系统涉及的对象是奶牛。 系统使用员工有管理员和普通员工。 管理员有修改的权限&#xff0c;普通员工没有。 系统需要包含奶牛的编号&#xff0c;种类&#xff0c;体重&#xff0c;健康情况、生长情况、牛奶产量&#xff0c;以及上次更新数据时间等信息&#xff0c;管理员可以对…

Perfect Clear WorkBench 智能修图黑科技,你尽管拍剩下的交给我(v4.6.0.2653)

01 Perfect Clear Perfect Clear WorkBench是EyeQlmaging推出的先进图片处理工具&#xff0c;旨在自动优化和简化图像校正。它通过智能技术提高图片的清晰度、颜色保真度&#xff0c;并增强视觉效果&#xff0c;确保高品质输出。 它的核心优势是利用高级算法和AI技术&#xff…

第59篇:创建Nios II工程之控制LED<一>

Q&#xff1a;还记得第1篇吗&#xff1f;设计简单的逻辑电路&#xff0c;控制DE2-115开发板上LED的亮与熄灭&#xff0c;一行Verilog HDL的assign赋值语句即可实现。本期开始创建Nios II工程&#xff0c;用C语言代码控制DE2-115开发板上的LED实现流水灯效果。 A&#xff1a;在…

win下安装desktop及使用desktop安装k8s

1、下载desktop安装包 Docker Desktop: The #1 Containerization Tool for Developers | Docker 2、点击exe文件进行安装 3、安装完需要在启用或关闭windows功能中勾选如下三个选项 4、在desktop中配置Docker Engine { "registry-mirrors": [ "https:/…

Linux创建YUM仓库

在rhel-8.5中的/mnt/目录下是有AppStream和BaseOS这两个软件包的&#xff0c;里面有可安装的一些软件。 /mnt/BaseOS/Packages/ 普通安装 1.使用rpm命令安装&#xff08;rpm -i 程序名称&#xff09; 查看&#xff0c;已经有了这个程序&#xff08;rpm -qa | grep 程序名&…

Footprint Analytics 与 GalaChain 达成战略合作

​ Footprint Analytics 宣布与 GalaChain 达成战略合作。GalaChain 是 Gala 旗下的 Layer 1 区块链。此次合作标志着双方在游戏&#xff08;包括 Gala Games) 、娱乐和金融等多个行业的区块链生态系统革新方面迈出了重要的一步。 GalaChain 致力于满足企业级项目的广泛需求&…

【电路笔记】-Colpitts振荡器

Colpitts振荡器 文章目录 Colpitts振荡器1、概述2、基本Colpitts 振荡器电路3、示例14、使用运算放大器的Colpitts振荡器5、总结Colpitts 振荡器设计使用两个中心抽头电容器与并联电感器串联,形成产生正弦振荡的谐振储能电路。 1、概述 在许多方面,Colpitts 振荡器与我们在上…

GO语言写Prometheus自定义node-exporter的Docker容器测试

1. 安装docker-compose 执行以下命令&#xff0c;安装docker-compose到CentOS7.9环境中&#xff1a; # 下载二进制文件 sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.7/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/d…

不懂就问!现货黄金和实物黄金如何选择?

近期金价大涨&#xff0c;很多投资者就将资金从股票等其他投资品种抽调出来&#xff0c;而投入到黄金市场中。然而&#xff0c;整个黄金投资市场中拥有这么多不同的黄金投资品种&#xff0c;像现货黄金和实物黄金&#xff0c;投资者根本不知道该选哪种&#xff0c;下面我们就来…

[数据结构]——排序——插入排序

目录 ​编辑 1 .插入排序 1.基本思想&#xff1a; 2.直接插入排序&#xff1a; ​编辑 1.代码实现 2.直接插入排序的特性总结&#xff1a; 3.希尔排序( 缩小增量排序 ) 1.预排序 2.预排序代码 3.希尔排序代码 4.希尔排序的特性总结&#xff1a; 1 .插入排序 1.基本思…

2023年全国消费金融财务数据挖掘-投资回报率最高的竟是!

作者Toby&#xff0c;来源公众号Python风控模型&#xff0c;2023年全国消费金融财务统计 大家好&#xff0c;Toby老师汇总了2023年全国消费金融财务数据。这份数据可以用来分析各个消费金融公司在2023年的财务表现&#xff0c;包括资产状况、营业收入、净利润以及投资回报率等…

鸿蒙APP开发页面组件之间的属性关系

我们将对于多页面以及更多有趣的功能展开叙述&#xff0c;这次我们对于 HarmonyOS 的很多有趣常用组件并引出一些其他概念以及解决方案、页面跳转传值、生命周期、启动模式&#xff08;UiAbility&#xff09;&#xff0c;样式的书写、状态管理以及动画等方面进行探讨 页面之间…

【自动化测试】使用MeterSphere进行接口测试

一、接口介绍二、接口测试的过程三、接口自动化测试执行自动化流程 四、接口之间的协议HTTP协议 五、 接口测试用例设计接口文档 六、使用MeterSphere创建接口测试创建接口定义设计接口测试用例 一、接口介绍 自动化测试按对象分为&#xff1a;单元测试、接口测试、UI测试等。…

一次违法网站的渗透经历

0x01 前言 在一次攻防演练中&#xff0c;我发现了一个有趣的渗透路径。在信息收集阶段&#xff0c;我注意到目标网站和用户资产网站共享相同的IP网段。这意味着它们可能在同一台服务器上托管&#xff0c;或者至少由同一家互联网服务提供商管理。这种情况为我们的渗透测试提供了…

路由重分布的概念与配置

路由重分布的概念 l 路由重分布是指连接不同路由域&#xff08;自治系统&#xff09;的边界路由器&#xff0c;它在路由协议之间交换和通告路由信息 从一种协议&#xff08;含静态/直连路由&#xff09;到另一种协议 同一种协议的多个实例 路由重分布的背景 网络出口位置…