Oracle Explain Plan分析——Oracle Doing Full Table Access
Analysis of Oracle Explain Plan - Oracle Doing Full Table Access
我们有一个问题 SQL,在查看解释计划时,似乎有很多完全 Table 访问权限。据我了解,这是正常的,特别是如果查询更多地位于计划内部而不是顶部。
每个 table 都有索引并使用了适当的列,因此不清楚为什么 Oracle 不使用这些索引。任何人都可以就我是否应该期待如此多的完整 table 扫描,或者 SQL 是否需要更好的优化提供高级指导?我们在 Oracle 中经常 运行 内存不足,删除 SORT 确实有助于降低成本,但对内存没有帮助。
SQL
SELECT
TRNLOAD.WORK_ORDER_NUMBER,
TRNMANIFESTHDR.CUST_MANIFEST_NUMBER,
TRNMANIFESTHDR.STATE_MANIFEST_NUMBER,
TRNINVENTORY.MAN_PAGE_NO,
...snipped columns for size
TRNINVENTORY.MAN_SYS_NUMBER,
NVL(REFINVSPLITCODES.DOUBLE_COUNT,'N') double_count,
MONTHS_BETWEEN(SYSDATE,NVL(TRNLOAD.CHECKIN_TIME, TRNINVENTORY.GROUPED_DATE)) months,
AESOP.DF_GETINVCOLORCODE
(TRNINVENTORY.INV_STATUS,
TRNLOAD.CHECKOUT_TIME,
DF_IS_ONSITECUST(TRNMANIFESTHDR.LOC_CODE,TRNMANIFESTHDR.BILLING_CUSTOMER))
color_cd,
TRNINVENTORY.CREATED_BY,
TRNMANIFESTDETAIL.EPA_CONSENT_NUMBER
FROM REFGENERATOR,
TRNINVENTORY,
TRNLOAD,
TRNMANIFESTDETAIL,
TRNMANIFESTHDR,
REFWASTESTREAM,
REFRTTAXCODES,
REFINVENTORYSTATUS,
REFINVSPLITCODES,
REFHANDLINGCODES,
reftreatmentgroup,
TRNMANIFRETAILTRIP trip
WHERE (TRNMANIFESTDETAIL.LOC_CODE(+) = TRNINVENTORY.LOC_CODE)
AND (TRNMANIFESTDETAIL.MAN_SYS_NUMBER(+) = TRNINVENTORY.MAN_SYS_NUMBER)
AND (TRNMANIFESTDETAIL.MAN_PAGE_NUMBER(+) = TRNINVENTORY.MAN_PAGE_NO)
AND (TRNMANIFESTDETAIL.MAN_LINE_NUMBER(+) = TRNINVENTORY.MAN_LINE_NO)
AND (TRNMANIFESTHDR.LOC_CODE(+) = TRNMANIFESTDETAIL.LOC_CODE)
AND (TRNMANIFESTHDR.MAN_SYS_NUMBER(+) = TRNMANIFESTDETAIL.MAN_SYS_NUMBER)
AND (TRNMANIFESTHDR.MAN_PAGE_NO(+) = TRNMANIFESTDETAIL.USER_MANPAGE)
AND (TRNMANIFESTHDR.LOC_CODE = TRNLOAD.LOC_CODE(+))
AND (TRNMANIFESTHDR.WORK_ORDER_NUMBER = TRNLOAD.WORK_ORDER_NUMBER(+))
AND (TRNMANIFESTHDR.LOC_CODE = REFGENERATOR.LOC_CODE(+))
AND (TRNMANIFESTHDR.GEN_SYS_NUMBER = REFGENERATOR.GEN_SYS_NUMBER(+))
AND (TRNMANIFESTDETAIL.LOC_CODE = REFWASTESTREAM.LOC_CODE(+))
AND (TRNMANIFESTDETAIL.WASTE_STREAM_NUMBER = REFWASTESTREAM.WASTE_STREAM_NUMBER(+))
AND (TRNMANIFESTDETAIL.PROFILE_NUMBER = REFWASTESTREAM.PROFILE_NUMBER(+))
AND (REFHANDLINGCODES.LOC_CODE(+) = TRNMANIFESTDETAIL.LOC_CODE)
AND (REFHANDLINGCODES.WASTE_STREAM_NUMBER(+) = TRNMANIFESTDETAIL.WASTE_STREAM_NUMBER)
AND (REFHANDLINGCODES.PROFILE_NUMBER(+) = TRNMANIFESTDETAIL.PROFILE_NUMBER)
AND (refhandlingcodes.loc_code = reftreatmentgroup.loc_code(+))
AND (refhandlingcodes.tgroup = reftreatmentgroup.tgroup(+))
AND (REFINVENTORYSTATUS.ACTION_ID = TRNINVENTORY.INV_STATUS)
AND (trninventory.split_code = refinvsplitcodes.code(+))
AND (trninventory.loc_code = refrttaxcodes.loc_code(+))
AND (trninventory.inv_taxcode = REFRTTAXCODES.TAX_ID(+))
AND ( (TRNINVENTORY.LOC_CODE = :ai_loc))
and (trip.loc_code (+) = trninventory.loc_code)
and (trip.inventory_no (+) = trninventory.inventroty_no)
and (trip.split_no (+) = trninventory.split_no)
AND NOT (NVL (TRNINVENTORY.IS_PRODUCT, 'N') = 'Y' OR NVL (REFWASTESTREAM.WASTE_TYPE, 'A') = 'D')
AND nvl(TRNLOAD.WORK_ORDER_TYPE,'STD') = 'STD'
ORDER BY TRNLOAD.WORK_ORDER_NUMBER,
TRNINVENTORY.MAN_SYS_NUMBER,
TRNMANIFESTDETAIL.USER_MANPAGE,
DF_sort_string (TRNMANIFESTDETAIL.USER_MANLINE),
TRNINVENTORY.INVENTROTY_NO,
TRNINVENTORY.SPLIT_NO,
TRNINVENTORY.GROUPED_DATE,
TRNINVENTORY.GROUP_NO
解释计划
为了便于阅读,我不得不将其调整得更窄,这就是为什么有一些缩写的原因。
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes| Cost |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 377K| 198M| 112K|
| 1 | SORT ORDER BY | | 377K| 198M| 112K|
| 2 | HASH JOIN RIGHT OUTER | | 377K| 198M| 68645|
| 3 | TBL ACCESS FULL | TRNMANIFRETAILTRIP | 128 | 2944 | 3 |
| 4 | HASH JOIN RIGHT OUTER | | 377K| 190M|68639 |
| 5 | TBL ACCESS FULL | REFTREATMENTGROUP | 101 | 1313 | 3 |
| 6 | FILTER | | | | |
| 7 | HASH JOIN RIGHT OUTER | | 377K| 186M|68633 |
| 8 | TBL ACCESS FULL | REFWASTESTREAM | 204K| 11M| 755 |
| 9 | HASH JOIN RIGHT OUTER | | 377K| 163M|58820 |
| 10 | TBL ACCESS FULL | REFGENERATOR | 80447| 3535K| 594 |
| 11 | FILTER | | | | |
| 12 | HASH JOIN RIGHT OUTER | | 377K| 147M|50460 |
| 13 | TBL ACCESS FULL | TRNLOAD | 507K| 16M|2920 |
| 14 | HASH JOIN RIGHT OUTER | | 376K| 135M|39501 |
| 15 | TBL ACCESS FULL | TRNMANIFESTHDR | 844K| 49M| 4646 |
| 16 | HASH JOIN RIGHT OUTER | | 376K| 113M|26090 |
| 17 | TBL ACCESS FULL | REFHANDLINGCODES | 183K| 4483K| 631 |
| 18 | HASH JOIN RIGHT OUTER | | 376K| 104M|19737 |
| 19 | TBL ACCESS FULL | TRNMANIFESTDETAIL | 289K| 14M| 4496 |
| 20 | HASH JOIN RIGHT OUTER | | 376K| 85M| 9890 |
| 21 | TBL ACCESS FULL | REFINVSPLITCODES | 48 | 288 | 3 |
| 22 | HASH JOIN RIGHT OUTER | | 376K| 83M| 9883 |
| 23 | TBL ACCESS BY INDEX ROWID | REFRTTAXCODES | 8 | 80 | 3 |
| 24 | INDEX RANGE SCAN | PK_TAXCODE_LOCATION | 8 | | 1 |
| 25 | HASH JOIN | | 376K| 80M| 9877 |
| 26 | INDEX FULL SCAN | IDX_INVSTATUS_TYPE | 22 | 176 | 1 |
| 27 | TBL ACCESS FULL | TRNINVENTORY | 376K| 77M| 9873 |
------------------------------------------------------------------------------------------
问:任何人都可以就我是否应该期待如此多的完整 table 扫描,或者这个 SQL 是否需要更好的优化提供高级指导?
A:数据被子集化的地方很少,这是寻找索引调优机会的第一个地方,但我看到函数应用于列,这可能意味着需要基于函数的索引。
下一个要查看的地方是连接条件,连接列是否已编入索引。如果 Oracle 认为完全 table 扫描同样高效或更高效,则它可以选择忽略连接列上的索引。
一般来说,我会说不要担心完全 table 扫描,除非,如果满足以下条件,我会担心完全 table 扫描:
- 未满足性能要求
- 完整扫描是大 tables
- 会话的等待事件 运行 SQL 显示对 "db file scattered read" 有问题的大对象的高等待(即完全 table 扫描)
我们有一个问题 SQL,在查看解释计划时,似乎有很多完全 Table 访问权限。据我了解,这是正常的,特别是如果查询更多地位于计划内部而不是顶部。
每个 table 都有索引并使用了适当的列,因此不清楚为什么 Oracle 不使用这些索引。任何人都可以就我是否应该期待如此多的完整 table 扫描,或者 SQL 是否需要更好的优化提供高级指导?我们在 Oracle 中经常 运行 内存不足,删除 SORT 确实有助于降低成本,但对内存没有帮助。
SQL
SELECT
TRNLOAD.WORK_ORDER_NUMBER,
TRNMANIFESTHDR.CUST_MANIFEST_NUMBER,
TRNMANIFESTHDR.STATE_MANIFEST_NUMBER,
TRNINVENTORY.MAN_PAGE_NO,
...snipped columns for size
TRNINVENTORY.MAN_SYS_NUMBER,
NVL(REFINVSPLITCODES.DOUBLE_COUNT,'N') double_count,
MONTHS_BETWEEN(SYSDATE,NVL(TRNLOAD.CHECKIN_TIME, TRNINVENTORY.GROUPED_DATE)) months,
AESOP.DF_GETINVCOLORCODE
(TRNINVENTORY.INV_STATUS,
TRNLOAD.CHECKOUT_TIME,
DF_IS_ONSITECUST(TRNMANIFESTHDR.LOC_CODE,TRNMANIFESTHDR.BILLING_CUSTOMER))
color_cd,
TRNINVENTORY.CREATED_BY,
TRNMANIFESTDETAIL.EPA_CONSENT_NUMBER
FROM REFGENERATOR,
TRNINVENTORY,
TRNLOAD,
TRNMANIFESTDETAIL,
TRNMANIFESTHDR,
REFWASTESTREAM,
REFRTTAXCODES,
REFINVENTORYSTATUS,
REFINVSPLITCODES,
REFHANDLINGCODES,
reftreatmentgroup,
TRNMANIFRETAILTRIP trip
WHERE (TRNMANIFESTDETAIL.LOC_CODE(+) = TRNINVENTORY.LOC_CODE)
AND (TRNMANIFESTDETAIL.MAN_SYS_NUMBER(+) = TRNINVENTORY.MAN_SYS_NUMBER)
AND (TRNMANIFESTDETAIL.MAN_PAGE_NUMBER(+) = TRNINVENTORY.MAN_PAGE_NO)
AND (TRNMANIFESTDETAIL.MAN_LINE_NUMBER(+) = TRNINVENTORY.MAN_LINE_NO)
AND (TRNMANIFESTHDR.LOC_CODE(+) = TRNMANIFESTDETAIL.LOC_CODE)
AND (TRNMANIFESTHDR.MAN_SYS_NUMBER(+) = TRNMANIFESTDETAIL.MAN_SYS_NUMBER)
AND (TRNMANIFESTHDR.MAN_PAGE_NO(+) = TRNMANIFESTDETAIL.USER_MANPAGE)
AND (TRNMANIFESTHDR.LOC_CODE = TRNLOAD.LOC_CODE(+))
AND (TRNMANIFESTHDR.WORK_ORDER_NUMBER = TRNLOAD.WORK_ORDER_NUMBER(+))
AND (TRNMANIFESTHDR.LOC_CODE = REFGENERATOR.LOC_CODE(+))
AND (TRNMANIFESTHDR.GEN_SYS_NUMBER = REFGENERATOR.GEN_SYS_NUMBER(+))
AND (TRNMANIFESTDETAIL.LOC_CODE = REFWASTESTREAM.LOC_CODE(+))
AND (TRNMANIFESTDETAIL.WASTE_STREAM_NUMBER = REFWASTESTREAM.WASTE_STREAM_NUMBER(+))
AND (TRNMANIFESTDETAIL.PROFILE_NUMBER = REFWASTESTREAM.PROFILE_NUMBER(+))
AND (REFHANDLINGCODES.LOC_CODE(+) = TRNMANIFESTDETAIL.LOC_CODE)
AND (REFHANDLINGCODES.WASTE_STREAM_NUMBER(+) = TRNMANIFESTDETAIL.WASTE_STREAM_NUMBER)
AND (REFHANDLINGCODES.PROFILE_NUMBER(+) = TRNMANIFESTDETAIL.PROFILE_NUMBER)
AND (refhandlingcodes.loc_code = reftreatmentgroup.loc_code(+))
AND (refhandlingcodes.tgroup = reftreatmentgroup.tgroup(+))
AND (REFINVENTORYSTATUS.ACTION_ID = TRNINVENTORY.INV_STATUS)
AND (trninventory.split_code = refinvsplitcodes.code(+))
AND (trninventory.loc_code = refrttaxcodes.loc_code(+))
AND (trninventory.inv_taxcode = REFRTTAXCODES.TAX_ID(+))
AND ( (TRNINVENTORY.LOC_CODE = :ai_loc))
and (trip.loc_code (+) = trninventory.loc_code)
and (trip.inventory_no (+) = trninventory.inventroty_no)
and (trip.split_no (+) = trninventory.split_no)
AND NOT (NVL (TRNINVENTORY.IS_PRODUCT, 'N') = 'Y' OR NVL (REFWASTESTREAM.WASTE_TYPE, 'A') = 'D')
AND nvl(TRNLOAD.WORK_ORDER_TYPE,'STD') = 'STD'
ORDER BY TRNLOAD.WORK_ORDER_NUMBER,
TRNINVENTORY.MAN_SYS_NUMBER,
TRNMANIFESTDETAIL.USER_MANPAGE,
DF_sort_string (TRNMANIFESTDETAIL.USER_MANLINE),
TRNINVENTORY.INVENTROTY_NO,
TRNINVENTORY.SPLIT_NO,
TRNINVENTORY.GROUPED_DATE,
TRNINVENTORY.GROUP_NO
解释计划
为了便于阅读,我不得不将其调整得更窄,这就是为什么有一些缩写的原因。
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes| Cost |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 377K| 198M| 112K|
| 1 | SORT ORDER BY | | 377K| 198M| 112K|
| 2 | HASH JOIN RIGHT OUTER | | 377K| 198M| 68645|
| 3 | TBL ACCESS FULL | TRNMANIFRETAILTRIP | 128 | 2944 | 3 |
| 4 | HASH JOIN RIGHT OUTER | | 377K| 190M|68639 |
| 5 | TBL ACCESS FULL | REFTREATMENTGROUP | 101 | 1313 | 3 |
| 6 | FILTER | | | | |
| 7 | HASH JOIN RIGHT OUTER | | 377K| 186M|68633 |
| 8 | TBL ACCESS FULL | REFWASTESTREAM | 204K| 11M| 755 |
| 9 | HASH JOIN RIGHT OUTER | | 377K| 163M|58820 |
| 10 | TBL ACCESS FULL | REFGENERATOR | 80447| 3535K| 594 |
| 11 | FILTER | | | | |
| 12 | HASH JOIN RIGHT OUTER | | 377K| 147M|50460 |
| 13 | TBL ACCESS FULL | TRNLOAD | 507K| 16M|2920 |
| 14 | HASH JOIN RIGHT OUTER | | 376K| 135M|39501 |
| 15 | TBL ACCESS FULL | TRNMANIFESTHDR | 844K| 49M| 4646 |
| 16 | HASH JOIN RIGHT OUTER | | 376K| 113M|26090 |
| 17 | TBL ACCESS FULL | REFHANDLINGCODES | 183K| 4483K| 631 |
| 18 | HASH JOIN RIGHT OUTER | | 376K| 104M|19737 |
| 19 | TBL ACCESS FULL | TRNMANIFESTDETAIL | 289K| 14M| 4496 |
| 20 | HASH JOIN RIGHT OUTER | | 376K| 85M| 9890 |
| 21 | TBL ACCESS FULL | REFINVSPLITCODES | 48 | 288 | 3 |
| 22 | HASH JOIN RIGHT OUTER | | 376K| 83M| 9883 |
| 23 | TBL ACCESS BY INDEX ROWID | REFRTTAXCODES | 8 | 80 | 3 |
| 24 | INDEX RANGE SCAN | PK_TAXCODE_LOCATION | 8 | | 1 |
| 25 | HASH JOIN | | 376K| 80M| 9877 |
| 26 | INDEX FULL SCAN | IDX_INVSTATUS_TYPE | 22 | 176 | 1 |
| 27 | TBL ACCESS FULL | TRNINVENTORY | 376K| 77M| 9873 |
------------------------------------------------------------------------------------------
问:任何人都可以就我是否应该期待如此多的完整 table 扫描,或者这个 SQL 是否需要更好的优化提供高级指导?
A:数据被子集化的地方很少,这是寻找索引调优机会的第一个地方,但我看到函数应用于列,这可能意味着需要基于函数的索引。
下一个要查看的地方是连接条件,连接列是否已编入索引。如果 Oracle 认为完全 table 扫描同样高效或更高效,则它可以选择忽略连接列上的索引。
一般来说,我会说不要担心完全 table 扫描,除非,如果满足以下条件,我会担心完全 table 扫描:
- 未满足性能要求
- 完整扫描是大 tables
- 会话的等待事件 运行 SQL 显示对 "db file scattered read" 有问题的大对象的高等待(即完全 table 扫描)