我正在使用 oracle 11g 并尝试优化查询。
查询的基本结构是:
SELECT val1, val2, val3,
FROM
table_name
WHERE
val1 in (subselect statement is here, it selects a list of possible values for
val1 from another table)
and val5>=X and val5<=Y
group by val1
order by val2 desc;
Run Code Online (Sandbox Code Playgroud)
我的问题是,当我使用子选择时,成本是 3130。如果我手动填写子选择的结果 - 例如
field1 in (1, 2, 3, 4, 5, 6)
Run Code Online (Sandbox Code Playgroud)
其中 (1, 2, 3, 4, 5, 6) 是子查询的结果,本例中是字段 1 的所有可能值,查询的成本为 14,oracle 使用“inlist 迭代器”进行按查询的一部分进行分组。两个查询的结果是相同的。
我的问题是如何模仿使用 subselect 语句手动列出 field1 的可能值的行为。我没有在查询中列出这些值的原因是,可能的值会根据其他字段之一而变化,因此子选择会根据字段 2 从第二个表中提取字段 1 的可能值。
我有一个索引 val1,val5,所以它不执行任何全表扫描 - 它在两种情况下都会执行范围扫描,但在子选择情况下范围扫描要昂贵得多。然而,它并不是子选择查询中最昂贵的部分。最昂贵的部分是group by,它是一个HASH。
编辑 - 是的,查询在语法上不正确 - 我不想提出任何太具体的内容。实际的查询很好 - 选择使用有效的分组依据函数。
子选择返回 6 个值,但它可以是 1-50 左右的任意值,具体取决于其他值。
Edit2 - 我最终做的是两个单独的查询,这样我就可以生成子选择中使用的列表。我实际上在 sqlite 中尝试了类似的测试,它做了同样的事情,所以这不仅仅是 Oracle。
您所看到的是 IN () bieng 受绑定变量窥视的结果。当你有直方图时,你编写一个像“where a = 'a'”这样的查询,oracle将使用直方图来猜测将返回多少行(与inlist运算符相同的想法,它迭代每个项目并聚合行)。如果没有直方图,它将以行/不同值的形式进行猜测。在子查询中,oracle 不会这样做(在大多数情况下......有一个独特的情况会这样做)。
例如:
SQL> create table test
2 (val1 number, val2 varchar2(20), val3 number);
Table created.
Elapsed: 00:00:00.02
SQL>
SQL> insert into test select 1, 'aaaaaaaaaa', mod(rownum, 5) from dual connect by level <= 100;
100 rows created.
Elapsed: 00:00:00.01
SQL> insert into test select 2, 'aaaaaaaaaa', mod(rownum, 5) from dual connect by level <= 1000;
1000 rows created.
Elapsed: 00:00:00.02
SQL> insert into test select 3, 'aaaaaaaaaa', mod(rownum, 5) from dual connect by level <= 100;
100 rows created.
Elapsed: 00:00:00.00
SQL> insert into test select 4, 'aaaaaaaaaa', mod(rownum, 5) from dual connect by level <= 100000;
100000 rows created.
Run Code Online (Sandbox Code Playgroud)
所以我有一个包含 101200 行的表。对于 VAL1 ,100 是“1”,1000 是“2”,100 是“3”,100k 是“4”。
现在如果收集直方图(在这种情况下我们确实需要它们)
SQL> exec dbms_stats.gather_table_stats(user , 'test', degree=>4, method_opt=>'for all indexed columns size 4', estimate_percent=>100);
SQL> exec dbms_stats.gather_table_stats(user , 'lookup', degree=>4, method_opt =>'for all indexed columns size 3', estimate_percent=>100);
Run Code Online (Sandbox Code Playgroud)
我们看到以下内容:
SQL> explain plan for select * from test where val1 in (1, 2, 3) ;
Explained.
SQL> @explain ""
Plan hash value: 3165434153
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1200 | 19200 | 23 (0)| 00:00:01 |
| 1 | INLIST ITERATOR | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| TEST | 1200 | 19200 | 23 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TEST1 | 1200 | | 4 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
与
SQL> explain plan for select * from test where val1 in (select id from lookup where str = 'A') ;
Explained.
SQL> @explain ""
Plan hash value: 441162525
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 25300 | 518K| 106 (3)| 00:00:02 |
| 1 | NESTED LOOPS | | 25300 | 518K| 106 (3)| 00:00:02 |
| 2 | TABLE ACCESS BY INDEX ROWID| LOOKUP | 1 | 5 | 1 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN | LOOKUP1 | 1 | | 0 (0)| 00:00:01 |
|* 4 | TABLE ACCESS FULL | TEST | 25300 | 395K| 105 (3)| 00:00:02 |
----------------------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
查找表在哪里
SQL> select * From lookup;
ID STR
---------- ----------
1 A
2 B
3 C
4 D
Run Code Online (Sandbox Code Playgroud)
(str 是唯一索引的并且有直方图)。
注意到 inlist 的基数为 1200,这是一个很好的计划,但子查询的基数却非常不准确?Oracle 没有计算连接条件的直方图,而是说“看,我不知道 id 是什么,所以我猜测总行数(100k+1000+100+100)/不同值(4)= 25300 并使用因此,它选择了全表扫描。
这一切都很好,但是如何解决呢?如果您知道这个子查询将匹配少量行(我们知道)。那么你必须提示外部查询尝试让它使用索引。喜欢:
SQL> explain plan for select /*+ index(t) */ * from test t where val1 in (select id from lookup where str = 'A') ;
Explained.
SQL> @explain
Plan hash value: 702117913
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 25300 | 518K| 456 (1)| 00:00:06 |
| 1 | NESTED LOOPS | | 25300 | 518K| 456 (1)| 00:00:06 |
| 2 | TABLE ACCESS BY INDEX ROWID| LOOKUP | 1 | 5 | 1 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN | LOOKUP1 | 1 | | 0 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| TEST | 25300 | 395K| 455 (1)| 00:00:06 |
|* 5 | INDEX RANGE SCAN | TEST1 | 25300 | | 61 (2)| 00:00:01 |
----------------------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
另一件事是我的具体情况。由于 val1=4 是表的大部分,可以说我有我的标准查询:
select * from test t where val1 in (select id from lookup where str = :B1);
为可能的:B1输入。如果我知道传入的有效值是 A、B 和 C(即不是映射到 id=4 的 D)。我可以添加这个技巧:
SQL> explain plan for select * from test t where val1 in (select id from lookup where str = :b1 and id in (1, 2, 3)) ;
Explained.
SQL> @explain ""
Plan hash value: 771376936
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 250 | 5250 | 24 (5)| 00:00:01 |
|* 1 | HASH JOIN | | 250 | 5250 | 24 (5)| 00:00:01 |
|* 2 | VIEW | index$_join$_002 | 1 | 5 | 1 (100)| 00:00:01 |
|* 3 | HASH JOIN | | | | | |
|* 4 | INDEX RANGE SCAN | LOOKUP1 | 1 | 5 | 0 (0)| 00:00:01 |
| 5 | INLIST ITERATOR | | | | | |
|* 6 | INDEX UNIQUE SCAN | SYS_C002917051 | 1 | 5 | 0 (0)| 00:00:01 |
| 7 | INLIST ITERATOR | | | | | |
| 8 | TABLE ACCESS BY INDEX ROWID| TEST | 1200 | 19200 | 23 (0)| 00:00:01 |
|* 9 | INDEX RANGE SCAN | TEST1 | 1200 | | 4 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
现在请注意,oracle 已经得到了一张合理的卡(它将 1,2,3 推到了 TEST 表上,并得到了 1200.. 不是 100% 准确,因为我只过滤了其中的任何一个,但我告诉oralce 当然不是 4!