我正在使用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;
我的问题是,当我使用子选择时,成本是3130。 如果我手动填写子选择的结果 - 例如
field1 in (1, 2, 3, 4, 5, 6)
其中(1,2,3,4,5,6)是subselect的结果,在这种情况下是字段1的所有可能值,查询的成本是14,oracle使用“inlist”迭代器“为部分查询的组。两个查询的结果完全相同。
我的问题是如何模仿使用subselect语句手动列出field1的可能值的行为。我没有在查询中列出这些值的原因是可能的值基于其他字段之一而更改,因此subselect正在从第2个表中提取field1的可能值,例如field2。
我有一个val1,val5的索引,因此它没有进行任何全表扫描 - 它在两种情况下都会进行范围扫描,但在子选择情况下,范围扫描要贵得多。但是,它不是subselect查询中最昂贵的部分。最昂贵的部分是group by,这是一个HASH。
编辑 - 是的,查询在语法上不正确 - 我不想提出任何过于具体的内容。实际查询很好 - 选择按功能使用有效组。
subselect返回6个值,但基于另一个值,它可以是1-50左右。
Edit2 - 我最终做的是2个单独的查询,因此我可以生成子选择中使用的列表。我实际上在sqlite中尝试过类似的测试,它也做了同样的事情,所以这不仅仅是Oracle。
答案 0 :(得分:4)
你看到的是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.
所以我有一个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);
我们看到以下内容:
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 |
--------------------------------------------------------------------------------------
VS
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 |
----------------------------------------------------------------------------------------
其中查找表是
SQL> select * From lookup;
ID STR
---------- ----------
1 A
2 B
3 C
4 D
(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 |
----------------------------------------------------------------------------------------
另一件事是在我的特殊情况下。因为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 |
--------------------------------------------------------------------------------------------------
现在注意到oracle已经得到了一张合理的卡片(它将1,2,3推到了TEST表上并获得了1200 ...不是100%准确,因为我只是过滤了他们的noe但是我已经告诉oralce CERTAINLY NOT 4 !
答案 1 :(得分:2)
我做了一些研究,我认为一切都在这里解释:oracle docs 只需看看“CBO如何评估IN-List迭代器” 并将其与“CBO如何评估IN运营商”进行比较。
您在“(1,2,3,4,5,6)中的字段1”的查询是匹配第一种情况,但查询与子选择是由Oracle重写。
因此,除非您发现将子查询返回作为参数的非常棘手的方法,否则使用subselect或join的每个查询都将具有与您相似的成本。
您可以随时尝试设置更多内存以进行排序。
答案 2 :(得分:1)
您可以通过在子选择上添加索引来修复该语句。但是,您必须发布查询和执行计划才能理解。顺便说一句,subselect本身需要多长时间?
您可以尝试以下两个版本之一:
select val1, val2, val3
from table_name join
(select distinct val from (subselect here)) t
on table_name.val1 = t.val
where val5>=X and val5<=Y
group by val1, val2, val3
order by val2 desc;
或:
select val1, val2, val3
from table_name
where val5>=X and val5<=Y and
exists (select 1 from (subselect here) t where t.val = table_name.val1)
group by val1, val2, val3
order by val2 desc;
这些在语义上是等价的,其中一个可能会更好地优化。
可能有效的另一种可能性是在分组之后进行过滤。类似的东西:
select t.*
from (select val1, val2, val3
from table_name
where val5>=X and val5<=Y and
group by val1, val2, val3
) t
where val1 in (subselect here)
order by val2 desc;