2012-11-09 77 views
2

我正在使用oracle 11g并试图优化查询。使用子查询列表优化SQL查询条款

查询的基本结构是:

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)是子选择,在这种情况下是场1的所有可能的值的结果,查询的成本是14,和oracle通过部分查询为该组使用“inlist迭代器”。两个查询的结果是相同的。

我的问题是如何模仿用subselect语句手动列出field1的可能值的行为。我没有在查询中列出这些值的原因是可能的值基于其他字段之一而改变,因此子查询从第二个表中基于field2拉动field1的可能值。

我有一个val1,val5的索引,所以它没有做任何全表扫描 - 它在两种情况下都会执行一次范围扫描,但是在子查询中,范围扫描要贵得多。但是,它不是子查询查询中最昂贵的部分。最昂贵的部分是群组,这是一个HASH。

编辑 - 是的,查询语法不正确 - 我不想提出任何太具体。实际的查询很好 - 选择功能使用有效的组。

该子选择返回6个值,但它可以是基于另一个值的1-50左右的任何值。

Edit2 - 我最终做的是2个独立的查询,所以我可以生成子查询中使用的列表。我其实在sqlite中尝试了一个类似的测试,它也做了同样的事情,所以这不仅仅是Oracle。

+0

你的查询在语法上不正确,你在'select'语句中有val2和val3,但这些不是我的'group by'语句 –

+0

使用INNER JOIN而不是子查询,并比较结果,IT应该表现得更好。 –

+0

subselect返回多少个值?这些值包含的列val1中的数据的百分比多少? – sufleR

回答

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是唯一索引并具有直方图)。

注意到基数为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 | 
-------------------------------------------------------------------------------------------------- 

现在发现甲骨文已经得到了合理的卡(其1,2,3推到测试表和有1200..not 100%准确,因为我是唯一的过滤功能noe,但是我告诉oralce一定不是4!

+0

感谢您的详细回复。我将最终手动生成子查询列表,并将其添加到查询中,因为这种方法非常快。这需要我的主要查询从45秒到<1秒。 – user1813522

1

您可能可以通过在子查询上添加索引来修复语句。但是,您必须发布查询和执行计划才能理解这一点。顺便说一下,子查询本身需要多长时间?

你可以试试下面这两个版本之一:

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; 
+0

子选择的成本是2,它有一个索引。对于第一个示例,成本与使用子查询的成本相同。第二种方法的成本是3128而不是3130. – user1813522

+0

Oracle似乎选择了加入表的错误算法。你可以更新表格统计信息,看看是否有帮助。 –

+0

不幸的是,我不能依靠表格统计信息是否正确。有没有办法暗示甲骨文迫使它做我想要的?我在谷歌周围寻找一个暗示强制oracle使用inlist interator,但我没有看到一个。也许我需要选择一个varray? – user1813522

2

我已经做了一些研究,我认为这里的一切都在这里解释:oracle docs
只需查看“CBO如何评估IN列表迭代器” 并将其与“CBO如何评估IN操作员”进行比较。

“(1,2,3,4,5,6)中的field1”与第一种情况相匹配,但使用子选择的查询由Oracle重写。

因此,每个带有子选择或连接的查询都会与您的成本相似,除非您发现非常棘手的方法将子查询返回为参数。

您可以随时尝试设置更多内存进行排序。