如何利用INDEX ROWID的TABLE ACCESS

时间:2013-07-03 07:26:44

标签: performance oracle indexing

我对使用FULL TABLE SCAN的查询有疑问。

当这个查询在我们的UAT环境中运行时,它使用一个TA​​BLE ACCESS BY INDEX ROWID,但是在prod中 它使用FULL TABLE SCAN。 UAT比PROD运行得好得多。

我们在prod和uat中有相同的表和索引结构。

我已经尝试过重建和重新创建索引,但使用了相同的解释计划。

表和索引静态也已更新。

您能帮助我使用INDEX访问而不是FUll表访问吗?

请参阅下面我们的产品和uat的解释计划。

EXPLAIN PLAN PROD 
=====================================================================

SQL> explain plan for
SELECT ASV_ODC_BRANCH.CODE, ASV_ODC_BRANCH.DESCRIPTION, ASV_ODC_BRANCH.BRSTN, DEB.VIEW_DORMANT.ACCTNO AS DORMANT_ACCT,
DEB.VIEW_DORMANT.SHORTNAME AS DORMANT_NAME, DEB.VIEW_DORMANT.OPID_ENTRY, DEB.CUSTOMER.CUSTOMERNO,
DEB.CUSTOMER.TIME_STAMP_ENTRY
FROM ASV_ODC_BRANCH, DEB.VIEW_DORMANT, DEB.CUSTOMER
WHERE trim(ASV_ODC_BRANCH.CODE) = decode(SUBSTR(DEB.VIEW_DORMANT.ACCTNO, 3, 1) || SUBSTR(DEB.VIEW_DORMANT.ACCTNO, 7, 1), ’29’,
SUBSTR(DEB.VIEW_DORMANT.ACCTNO, 4, 3), SUBSTR(DEB.VIEW_DORMANT.ACCTNO, 3, 3)) AND
DEB.VIEW_DORMANT.ACCTNO = DEB.CUSTOMER.CUSTOMERNO AND (DEB.VIEW_DORMANT.ACCTNO = :Xacct)
ORDER BY ASV_ODC_BRANCH.CODE, DORMANT_ACCT;

Explained.


PLAN_TABLE_OUTPUT                                                                                                                                                                     | Id  | Operation                       | Name           | Rows  | Bytes | Cost (%CPU)|
|   0 | SELECT STATEMENT                |                |     3 |   489 |  3601   (2)|
|   1 |  SORT ORDER BY                  |                |     3 |   489 |  3601   (2)|
|   2 |   HASH JOIN                     |                |     3 |   489 |  3600   (2)|
|   3 |    MERGE JOIN CARTESIAN         |                |     1 |    90 |  3595   (2)|
|   4 |     NESTED LOOPS                |                |     1 |    66 |  3592   (2)|
|   5 |      **TABLE ACCESS FULL**      | ACCOUNT        |     1 |    56 |  3590   (2)|
|   6 |      TABLE ACCESS BY INDEX ROWID| EXTENSION1     |     1 |    10 |     2   (0)|
|   7 |       INDEX UNIQUE SCAN         | PKEXT10        |     1 |       |     1   (0)|
|   8 |     BUFFER SORT                 |                |     1 |    24 |  3593   (2)|
|   9 |      TABLE ACCESS BY INDEX ROWID| CUSTOMER       |     1 |    24 |     3   (0)|
|  10 |       INDEX RANGE SCAN          | UXCUST1        |     1 |       |     2   (0)|
|  11 |    TABLE ACCESS FULL            | ASV_ODC_BRANCH |   334 | 24382 |     5   (0)|                                                                                                                                                                                    

**EXPLAIN PLAN UAT**
======================================================================================

SQL> explain plan for
SELECT ASV_ODC_BRANCH.CODE, ASV_ODC_BRANCH.DESCRIPTION, ASV_ODC_BRANCH.BRSTN, DEB.VIEW_DORMANT.ACCTNO AS DORMANT_ACCT,
DEB.VIEW_DORMANT.SHORTNAME AS DORMANT_NAME, DEB.VIEW_DORMANT.OPID_ENTRY, DEB.CUSTOMER.CUSTOMERNO,
DEB.CUSTOMER.TIME_STAMP_ENTRY
FROM ASV_ODC_BRANCH, DEB.VIEW_DORMANT, DEB.CUSTOMER
WHERE trim(ASV_ODC_BRANCH.CODE) = decode(SUBSTR(DEB.VIEW_DORMANT.ACCTNO, 3, 1) || SUBSTR(DEB.VIEW_DORMANT.ACCTNO, 7, 1), ’29’,
SUBSTR(DEB.VIEW_DORMANT.ACCTNO, 4, 3), SUBSTR(DEB.VIEW_DORMANT.ACCTNO, 3, 3)) AND
DEB.VIEW_DORMANT.ACCTNO = DEB.CUSTOMER.CUSTOMERNO AND (DEB.VIEW_DORMANT.ACCTNO = :Xacct)
ORDER BY ASV_ODC_BRANCH.CODE, DORMANT_ACCT;

Explained.

SQL> /

PLAN_TABLE_OUTPUT

| Id  | Operation                       | Name           | Rows  | Bytes | Cost (%CPU)|
|   0 | SELECT STATEMENT                |                |     5 |  5930 |    19  (11)|
|   1 |  SORT ORDER BY                  |                |     5 |  5930 |    19  (11)|
|   2 |   HASH JOIN                     |                |     5 |  5930 |    18   (6)|
|   3 |    MERGE JOIN CARTESIAN         |                |     2 |  2220 |    12   (0)|
|   4 |     NESTED LOOPS                |                |     1 |  1085 |     9   (0)|
|   5 |  **TABLE ACCESS BY INDEX ROWID**| ACCOUNT        |     1 |    57 |     7   (0)|
|   6 |       INDEX SKIP SCAN           | UXACCT2        |     1 |       |     6   (0)|    
|   7 |      TABLE ACCESS BY INDEX ROWID| EXTENSION1     |     1 |  1028 |     2   (0)|
|   8 |       INDEX UNIQUE SCAN         | PKEXT10        |     1 |       |     1   (0)|
|   9 |     BUFFER SORT                 |                |     1 |    25 |    10   (0)|
|  10 |      TABLE ACCESS BY INDEX ROWID| CUSTOMER       |     1 |    25 |     3   (0)|
|  11 |       INDEX RANGE SCAN          | UXCUST1        |     1 |       |     2   (0)|
|  12 |    TABLE ACCESS FULL            | ASV_ODC_BRANCH |   336 | 25536 |     5   (0)|

3 个答案:

答案 0 :(得分:2)

区别在于

EXPLAIN PLAN PROD

|   5 |      **TABLE ACCESS FULL**      | ACCOUNT        |     1 |    56 |  3590   (2)|

EXPLAIN PLAN UAT

|   5 |  **TABLE ACCESS BY INDEX ROWID**| ACCOUNT        |     1 |    57 |     7   (0)|
|   6 |       INDEX SKIP SCAN           | UXACCT2        |     1 |       |     6   (0)|    

它是如何运作的?

不是使用语句中的谓词限制搜索路径,而是通过探测前缀列的不同值的索引来启动Skip Scans。然后将这些不同值中的每一个用作常规索引搜索的起始点。结果是对单个索引进行多次单独搜索,这些索引在组合时消除了前缀列的影响。从本质上讲,索引已从第二级搜索下来。

优化程序使用统计信息来确定跳过扫描是否比全表扫描更有效。

优化器认为他比FTS更有优势,因为

  1. 它减少了支持一系列查询所需的索引数量。这通过减少索引维护并减少与多个索引相关的浪费空间来提高性能。
  2. 前缀列应该是查询中最具辨别力和最广泛使用的列。这两个条件并不总是齐头并进,这使决策变得困难。在这些情况下,跳过扫描可以减少做出“错误”决定的影响。
  3. 您可以考虑以下

    1. 检查环境中的优化程序模式。
    2. 收集查询中使用的所有表的统计信息。例如,如果某个表自创建以来尚未进行分析,并且在高水位线下具有少于DB_FILE_MULTIBLOCK_READ_COUNT个块,则优化程序会认为该表很小并使用全表扫描。查看ALL_TABLES表中的LAST_ANALYZED和BLOCKS列以检查统计信息。
    3. 虽然您的环境相似且代码相同,但优化器会动态检查并选择最佳可用方法。那么你的UAT也有相同的数据设置。由于它是UAT(在大多数公司中几乎都是预生产),因此在尺寸方面应该是最接近生产的。

答案 1 :(得分:1)

从我所看到的情况来看,表DEB.VIEW_DORMANT是关于表ACCOUNTEXTENSION1的视图,您希望使用前者的索引UXACCT2。我想这个请求中的a hint应该允许你做你想做的事情,比如:

SELECT /*+ INDEX(D UXACCT2) */ ASV_ODC_BRANCH.CODE,
...
FROM ASV_ODC_BRANCH, DEB.VIEW_DORMANT D, DEB.CUSTOMER
...

PS:如果这是你管理的查询(不是由任何高级软件生成的),我建议你像我一样使用别名,这使查询更具可读性......

答案 2 :(得分:0)

我可以帮你做一个INDEX-ACCESS而不是FTS吗?大概...

你真的想要吗? - 可能不是

为什么数据库的行为不同?因为您对不同的数据进行操作 - 如您的示例所示,查询返回不同的行数,因此我甚至不必询问您的生产和测试数据是否相同。如果您在索引列中有不同的数据(不同的数量或不同的值),那么Database-Stats将会有所不同,您的索引看起来会有所不同,因此优化程序将会使用不同的查询计划!

你应该怎么做?确保所有索引都是最新的,您的分区是安全的,所有数据库统计信息都是最新的,没有奇怪的调整设置(查询计划,环境设置...)然后优化器在大多数情况下会找到最好的计划 - 在许多情况下,全表扫描是更快的替代方案。

但是如果你测量时间并且优化器显然采用了错误的路径,尽管它具有准确的表统计信息,你应该使用oracle提交bug报告。

如果您有其他理由希望优化器进行索引访问:

如果您无法像Emmanuel那样输入Optimizer-HINT,您可以尝试使用Profiles或Baselines,它们提供了很好的调整功能。您可以使用不同的WHERE-Predicates编写自己的语句,直到获得具有index-access的计划并将其用作SQL-Profile并将此配置文件链接到原始语句,然后将使用相同的查询计划。