今天,某省的同事来告诉我,表重组后,他用于统计的一个sql脚本运行变慢了,之前只需要17、8分钟能出来的结果,现在1小时40分钟左右才能出来结果。
我们一起来看看脚本中的一个sql:
SQL> explain plan for
2 select a.startdate,b.subsid from tab_1 a,tab_2 b where
3 a.servid='025001003681' and a.status!='C' and a.mid=b.mid;
Explained.
Elapsed: 00:00:00.03
SQL> select * from table(dbms_xplan.display)
SQL> /
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 369 | 23985 | 980 | | |
| 1 | NESTED LOOPS | | 369 | 23985 | 980 | | |
| 2 | PARTITION HASH ALL | | | | | 1 | 4 |
|* 3 | TABLE ACCESS BY LOCAL INDEX ROWID| tab_1 | 369 | 14022 | 242 | 1 | 4 |
|* 4 | INDEX RANGE SCAN | IDX_tab_1_SERVID | 492 | | 10 | 1 | 4 |
| 5 | PARTITION HASH ITERATOR | | | | | KEY | KEY |
| 6 | TABLE ACCESS BY LOCAL INDEX ROWID| tab_2 | 1 | 27 | 2 | KEY | KEY |
|* 7 | INDEX UNIQUE SCAN | PK_tab_2_MID | 1 | | 1 | KEY | KEY |
----------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("A"."STATUS"<>'C')
4 - access("A"."SERVID"='025001003681')
7 - access("A"."MID"="B"."MID")
Note: cpu costing is off
22 rows selected.
Elapsed: 00:00:00.56
2 select a.startdate,b.subsid from tab_1 a,tab_2 b where
3 a.servid='025001003681' and a.status!='C' and a.mid=b.mid;
Explained.
Elapsed: 00:00:00.03
SQL> select * from table(dbms_xplan.display)
SQL> /
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 369 | 23985 | 980 | | |
| 1 | NESTED LOOPS | | 369 | 23985 | 980 | | |
| 2 | PARTITION HASH ALL | | | | | 1 | 4 |
|* 3 | TABLE ACCESS BY LOCAL INDEX ROWID| tab_1 | 369 | 14022 | 242 | 1 | 4 |
|* 4 | INDEX RANGE SCAN | IDX_tab_1_SERVID | 492 | | 10 | 1 | 4 |
| 5 | PARTITION HASH ITERATOR | | | | | KEY | KEY |
| 6 | TABLE ACCESS BY LOCAL INDEX ROWID| tab_2 | 1 | 27 | 2 | KEY | KEY |
|* 7 | INDEX UNIQUE SCAN | PK_tab_2_MID | 1 | | 1 | KEY | KEY |
----------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("A"."STATUS"<>'C')
4 - access("A"."SERVID"='025001003681')
7 - access("A"."MID"="B"."MID")
Note: cpu costing is off
22 rows selected.
Elapsed: 00:00:00.56
我们看到这个sql是通过索引后在走nested loops,我们做一个sqltrace来观察一下它的执行时间和consistent gets:
SQL> set timing on
SQL> set autotrace traceonly
SQL> select a.startdate,b.subsid from tab_1 a,tab_2 b where
2 a.servid='025001003681' and a.status!='C' and a.mid=b.mid;
627965 rows selected.
Elapsed: 00:36:13.04
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=980 Card=369 Bytes=2
3985)
1 0 NESTED LOOPS (Cost=980 Card=369 Bytes=23985)
2 1 PARTITION HASH (ALL)
3 2 TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'tab_1'
(Cost=242 Card=369 Bytes=14022)
4 3 INDEX (RANGE SCAN) OF 'IDX_tab_1_SERVID' (NON
-UNIQUE) (Cost=10 Card=492)
5 1 PARTITION HASH (ITERATOR)
6 5 TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'tab_2' (C
ost=2 Card=1 Bytes=27)
7 6 INDEX (UNIQUE SCAN) OF 'PK_tab_2_MID' (UNIQUE)
(Cost=1 Card=1)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
2725638 consistent gets
406711 physical reads
1120 redo size
20890925 bytes sent via SQL*Net to client
461160 bytes received via SQL*Net from client
41866 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
627965 rows processed
SQL>
SQL> set autotrace traceonly
SQL> select a.startdate,b.subsid from tab_1 a,tab_2 b where
2 a.servid='025001003681' and a.status!='C' and a.mid=b.mid;
627965 rows selected.
Elapsed: 00:36:13.04
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=980 Card=369 Bytes=2
3985)
1 0 NESTED LOOPS (Cost=980 Card=369 Bytes=23985)
2 1 PARTITION HASH (ALL)
3 2 TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'tab_1'
(Cost=242 Card=369 Bytes=14022)
4 3 INDEX (RANGE SCAN) OF 'IDX_tab_1_SERVID' (NON
-UNIQUE) (Cost=10 Card=492)
5 1 PARTITION HASH (ITERATOR)
6 5 TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'tab_2' (C
ost=2 Card=1 Bytes=27)
7 6 INDEX (UNIQUE SCAN) OF 'PK_tab_2_MID' (UNIQUE)
(Cost=1 Card=1)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
2725638 consistent gets
406711 physical reads
1120 redo size
20890925 bytes sent via SQL*Net to client
461160 bytes received via SQL*Net from client
41866 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
627965 rows processed
SQL>
我们看到有270多万个consistent gets,并且这个sql也确实要36分钟才能运行出来,那为什么同事说之前10多分钟就能跑出来?我们来试试hash连接:
SQL> select /*+ use_hash(a,b) */a.startdate,b.subsid from tab_1 a,tab_2 b where
a.servid='025001003681' and a.status!='C' and a.mid=b.mid; 2
628514 rows selected.
Elapsed: 00:04:21.90
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=20594 Card=369 Bytes
=23985)
1 0 HASH JOIN (Cost=20594 Card=369 Bytes=23985)
2 1 PARTITION HASH (ALL)
3 2 TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'tab_1'
(Cost=242 Card=369 Bytes=14022)
4 3 INDEX (RANGE SCAN) OF 'IDX_tab_1_SERVID' (NON
-UNIQUE) (Cost=10 Card=492)
5 1 PARTITION HASH (ALL)
6 5 TABLE ACCESS (FULL) OF 'tab_2' (Cost=20251 Card=7
199100 Bytes=194375700)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
400061 consistent gets
308119 physical reads
560 redo size
20909078 bytes sent via SQL*Net to client
461556 bytes received via SQL*Net from client
41902 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
628514 rows processed
SQL>
a.servid='025001003681' and a.status!='C' and a.mid=b.mid; 2
628514 rows selected.
Elapsed: 00:04:21.90
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=20594 Card=369 Bytes
=23985)
1 0 HASH JOIN (Cost=20594 Card=369 Bytes=23985)
2 1 PARTITION HASH (ALL)
3 2 TABLE ACCESS (BY LOCAL INDEX ROWID) OF 'tab_1'
(Cost=242 Card=369 Bytes=14022)
4 3 INDEX (RANGE SCAN) OF 'IDX_tab_1_SERVID' (NON
-UNIQUE) (Cost=10 Card=492)
5 1 PARTITION HASH (ALL)
6 5 TABLE ACCESS (FULL) OF 'tab_2' (Cost=20251 Card=7
199100 Bytes=194375700)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
400061 consistent gets
308119 physical reads
560 redo size
20909078 bytes sent via SQL*Net to client
461556 bytes received via SQL*Net from client
41902 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
628514 rows processed
SQL>
呀,走hash连接的话,只要4分多钟就能跑出来,且consistent gets也仅仅只有40万,物理读也小了不少。看来oracle确实没有选择合适的执行计划了。
可oracle为什么没有选择合适的执行计划?难道是统计信息的问题?
SQL> select TABLE_NAME,LAST_ANALYZED from user_tables where table_name in ('tab_2','tab_1');
TABLE_NAME LAST_ANAL
------------------------------ ---------
tab_2 29-JAN-08
tab_1 11-JUN-08
TABLE_NAME LAST_ANAL
------------------------------ ---------
tab_2 29-JAN-08
tab_1 11-JUN-08
难道是tab_2的统计信息太久了导致执行计划出错?尝试备份统计信息并重新分析tab_2之后,再次查看执行计划 :
SQL> exec dbms_stats.gather_table_stats(OWNNAME=>'MISC',tabname=>'tab_2',cascade=>TRUE,estimate_percent=> 50);
PL/SQL procedure successfully completed.
Elapsed: 00:18:37.95
SQL>
SQL>
SQL>
SQL> explain plan for
2 select a.startdate,b.subsid from tab_1 a,tab_2 b where
3 a.servid='025001003681' and a.status!='C' and a.mid=b.mid;
Explained.
Elapsed: 00:00:00.00
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 369 | 23985 | 980 | | |
| 1 | NESTED LOOPS | | 369 | 23985 | 980 | | |
| 2 | PARTITION HASH ALL | | | | | 1 | 4 |
|* 3 | TABLE ACCESS BY LOCAL INDEX ROWID| tab_1 | 369 | 14022 | 242 | 1 | 4 |
|* 4 | INDEX RANGE SCAN | IDX_tab_1_SERVID | 492 | | 10 | 1 | 4 |
| 5 | PARTITION HASH ITERATOR | | | | | KEY | KEY |
| 6 | TABLE ACCESS BY LOCAL INDEX ROWID| tab_2 | 1 | 27 | 2 | KEY | KEY |
|* 7 | INDEX UNIQUE SCAN | PK_tab_2_MID | 1 | | 1 | KEY | KEY |
----------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("A"."STATUS"<>'C')
4 - access("A"."SERVID"='025001003681')
7 - access("A"."MID"="B"."MID")
Note: cpu costing is off
22 rows selected.
Elapsed: 00:00:00.02
PL/SQL procedure successfully completed.
Elapsed: 00:18:37.95
SQL>
SQL>
SQL>
SQL> explain plan for
2 select a.startdate,b.subsid from tab_1 a,tab_2 b where
3 a.servid='025001003681' and a.status!='C' and a.mid=b.mid;
Explained.
Elapsed: 00:00:00.00
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 369 | 23985 | 980 | | |
| 1 | NESTED LOOPS | | 369 | 23985 | 980 | | |
| 2 | PARTITION HASH ALL | | | | | 1 | 4 |
|* 3 | TABLE ACCESS BY LOCAL INDEX ROWID| tab_1 | 369 | 14022 | 242 | 1 | 4 |
|* 4 | INDEX RANGE SCAN | IDX_tab_1_SERVID | 492 | | 10 | 1 | 4 |
| 5 | PARTITION HASH ITERATOR | | | | | KEY | KEY |
| 6 | TABLE ACCESS BY LOCAL INDEX ROWID| tab_2 | 1 | 27 | 2 | KEY | KEY |
|* 7 | INDEX UNIQUE SCAN | PK_tab_2_MID | 1 | | 1 | KEY | KEY |
----------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("A"."STATUS"<>'C')
4 - access("A"."SERVID"='025001003681')
7 - access("A"."MID"="B"."MID")
Note: cpu costing is off
22 rows selected.
Elapsed: 00:00:00.02
天,竟然还是走nl!!
难道是因为servid中的倾斜度的问题?再次查了一下tab_1表中servid=’025001003681′
SQL> select count(*) from tab_1 a where a.servid='025001003681'
2 /
COUNT(*)
----------
1564299
#### 而其他的servid的值:
SQL> select servid from tab_1 where rownum<=10;
SERVID
------------
010001000983
010001001058
010001001327
010001001327
010001001327
010001001327
010001001327
010001001327
010001001327
010001001327
10 rows selected.
Elapsed: 00:00:00.02
SQL> select count(*) from tab_1 a where a.servid='010001001327';
COUNT(*)
----------
33
Elapsed: 00:00:00.06
SQL> select count(*) from tab_1 a where a.servid='010001000983';
COUNT(*)
----------
2
Elapsed: 00:00:00.00
SQL> select count(*) from tab_1 a where a.servid='010001000983';
COUNT(*)
----------
2
2 /
COUNT(*)
----------
1564299
#### 而其他的servid的值:
SQL> select servid from tab_1 where rownum<=10;
SERVID
------------
010001000983
010001001058
010001001327
010001001327
010001001327
010001001327
010001001327
010001001327
010001001327
010001001327
10 rows selected.
Elapsed: 00:00:00.02
SQL> select count(*) from tab_1 a where a.servid='010001001327';
COUNT(*)
----------
33
Elapsed: 00:00:00.06
SQL> select count(*) from tab_1 a where a.servid='010001000983';
COUNT(*)
----------
2
Elapsed: 00:00:00.00
SQL> select count(*) from tab_1 a where a.servid='010001000983';
COUNT(*)
----------
2
可以看到其他值的返回数据量确实很小,在返回数据量小的情况下,我们走索引确实是一种高效的查询方式,但是当返回数据很多时,由于nl的比较要花大量的时间,因此时间也就基本消耗在这里了。
对于某一列中的不同数值有不同的倾斜度(skew),要选择不同的执行计划,我们需要在收集直方图:
SQL> exec dbms_stats.gather_table_stats(OWNNAME=>'MISC',tabname=>'tab_1',degree=>4,cascade=>TRUE,estimate_percent=> 50,method_opt => 'FOR COLUMNS servid SIZE auto');
PL/SQL procedure successfully completed.
Elapsed: 00:12:48.22
PL/SQL procedure successfully completed.
Elapsed: 00:12:48.22
此时,我们再次检查其执行计划的时候,看到已经走了hash连接了:
SQL> explain plan for
2 select a.startdate,b.subsid from tab_1 a,tab_2 b where
3 a.servid='025001003681' and a.status!='C' and a.mid=b.mid;
Explained.
Elapsed: 00:00:00.01
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost | Pstart| Pstop |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1172K| 72M| | 39071 | | |
|* 1 | HASH JOIN | | 1172K| 72M| 55M| 39071 | | |
| 2 | PARTITION HASH ALL | | | | | | 1 | 4 |
|* 3 | TABLE ACCESS FULL | tab_1 | 1172K| 42M| | 13663 | 1 | 4 |
| 4 | PARTITION HASH ALL | | | | | | 1 | 4 |
| 5 | TABLE ACCESS FULL | tab_2 | 7199K| 185M| | 20251 | 1 | 4 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("A"."MID"="B"."MID")
3 - filter("A"."SERVID"='025001003681' AND "A"."STATUS"<>'C')
Note: cpu costing is off
19 rows selected.
Elapsed: 00:00:00.02
SQL>
SQL>
2 select a.startdate,b.subsid from tab_1 a,tab_2 b where
3 a.servid='025001003681' and a.status!='C' and a.mid=b.mid;
Explained.
Elapsed: 00:00:00.01
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost | Pstart| Pstop |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1172K| 72M| | 39071 | | |
|* 1 | HASH JOIN | | 1172K| 72M| 55M| 39071 | | |
| 2 | PARTITION HASH ALL | | | | | | 1 | 4 |
|* 3 | TABLE ACCESS FULL | tab_1 | 1172K| 42M| | 13663 | 1 | 4 |
| 4 | PARTITION HASH ALL | | | | | | 1 | 4 |
| 5 | TABLE ACCESS FULL | tab_2 | 7199K| 185M| | 20251 | 1 | 4 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("A"."MID"="B"."MID")
3 - filter("A"."SERVID"='025001003681' AND "A"."STATUS"<>'C')
Note: cpu costing is off
19 rows selected.
Elapsed: 00:00:00.02
SQL>
SQL>
这个时候,我们不加hints,oracle已经正确的选择了执行计划。至此,不走hash join是因为索引列的倾斜度的问题,收集直方图后,问题搞定。
相关推荐
Oracle 执行计划详解是数据库管理系统中一个非常重要的概念。本文将详细介绍 Oracle 执行计划的相关概念、访问数据的存取方法、表之间的连接等内容,并对总结和概述,以便于理解和记忆。 一、相关的概念 1. Rowid ...
在执行计划中,ORACLE 会对 SQL 语句进行hashed,生成一个唯一的hash值,然后将该hash值与共享池中的已有执行计划进行比较。如果找到相同的执行计划,则直接使用该执行计划,否则,ORACLE 将生成新的执行计划。 在...
Oracle 执行计划是数据库管理系统在处理SQL查询时制定的一系列步骤,用于高效地检索和处理数据。它是Oracle数据库优化器(Optimizer)根据统计信息、成本估算和已存在的索引等信息生成的。优化器有两种主要的工作...
在这里,我们可以看到,执行计划的成本(Cost)为 1234,这意味着 Oracle 数据库将使用基于成本的优化器(CBO)来优化查询语句。 复杂的例子 以下是一个复杂的执行计划: PARENT1 FIRST CHILD FIRST GRANDCHILD ...
Oracle执行计划还包括其他访问方法,如索引扫描(Index Scan)、索引唯一扫描(Index Unique Scan)、索引快速全扫描(Index Fast Full Scan)以及各种类型的连接操作,如嵌套循环(Nested Loop)、哈希连接(Hash ...
Oracle执行计划是数据库执行SQL语句前确定的操作步骤序列。优化器根据表的统计信息、存储参数、可用的执行路径等生成执行计划,以实现最快的查询速度。执行计划通常用一系列数据库操作符来表示,这些操作符及其顺序...
综上所述,《Oracle执行计划与SQL优化实例》不仅提供了理论知识,还通过具体实例展示了如何运用执行计划来优化SQL查询,是数据库管理员和开发人员提升Oracle数据库性能不可或缺的指南。理解和应用这些知识,将有助于...
Oracle Cost-Based Optimizer (CBO) 是Oracle数据库中的一种查询优化策略,它通过评估不同查询执行计划的成本来选择最优的执行路径。在这个学习笔记中,我们将深入探讨Oracle中的Hash Join操作,这是一种重要的联接...
### Oracle的执行计划详解 #### 一、背景知识 在深入了解Oracle执行计划之前,我们先来了解一下几个重要的概念,这些概念对于理解执行计划至关重要。 **共享SQL语句**:为了提高性能并减少资源消耗,Oracle在第一...
但如果内存不足,Oracle会采取分区策略,即将数据分割成多个不连续的分区(Si和Bi),并分别对每个分区执行Hash Join。如果某个分区的哈希表仍然过大,Oracle会退化为Nested-Loops Hash Join,逐个对剩余的分区构建...
如果分区后仍然有Hash Table无法完全放入内存,Oracle会采取Nested Loops Hash Join,即对部分Si构建Hash Table,逐个与所有Bi执行连接操作,直到所有Si完成连接。 2. Join阶段:对于每个分区,进行Hash Join操作。...
Hash Join 算法是一种高效的连接算法,自 Oracle 7.3 开始,Oracle 提供了这种新型的 Join 技术。 Hash Join 只能用于相等连接,且只能在 CBO 优化器模式下。相对于 Nested Loop Join,Hash Join 更适合处理大型结果...
### Oracle优化器及执行计划详解 #### 一、性能调整概览 Oracle 数据库作为一款高性能的数据库管理系统,其性能优化对于确保系统高效稳定运行至关重要。性能调整涉及到多个层面,包括但不限于应用程序设计、数据库...
Oracle SQL执行计划是指Oracle数据库为SQL语句制定的一系列执行步骤,这些步骤指导数据库如何高效地访问数据和执行SQL语句。执行计划对于Oracle数据库的性能调优至关重要,因为它能够揭示SQL语句的执行细节,帮助...
### Oracle的执行计划详解 #### 一、执行计划的概念 执行计划是指在Oracle数据库中执行SQL查询时所采用的数据访问路径。它详细描述了数据库如何处理查询请求,包括数据读取的方式、使用的索引以及连接操作等。执行...
Hash Join 算法是 Oracle 数据库中一种高效的连接操作方法,特别适用于处理大数据量的查询。自从 Oracle 7.3 版本开始引入,它主要用于处理相等连接,并且只在 Cost-Based Optimizer (CBO) 模式下运行。相比Nested ...
当执行Hash JOIN时,如果内存不足以容纳整个哈希表,数据库系统会将部分数据写入临时表空间。这个临时表空间通常位于磁盘上,因此在处理大数据量时可能会耗尽空间,导致“临时表空间不足”的错误。 针对这个问题,...