首页 » Cloud, ORACLE 9i-23ai » 隐藏问题: Oracle 11g存在index full scan替代index fast full scan的低效执行计划

隐藏问题: Oracle 11g存在index full scan替代index fast full scan的低效执行计划

在Oracle数据库中,索引是提高查询性能的关键工具之一。通过使用索引,数据库可以快速地定位和检索数据,从而加快查询速度并降低系统资源消耗。在索引扫描过程中,有两种主要的方法:索引快速全扫描(Index Fast Full Scan)和索引全扫描(Index Full Scan)。然而,在某些情况下,数据库可能会出现错误的执行计划,选择索引全扫描而不是预期的索引快速全扫描,导致性能下降和资源浪费。该类问题可能不容易发现,仅是SQL性能差,或主要的等待事件为db file sequential read,在本文中,我们将探讨Oracle 11g中出现索引全扫描替代索引快速全扫描的情况。

案例:env Oracle 11.2.0.4 4-nodes RAC
一日客户反馈SQL运行效率慢,1个多小时未执行完成,且没有sql monitor的信息。

SQL Text
------------------------------
DELETE /*+parallel(8) */ FROM ANBOB_1.MID***************_REL_DAY PARTITION(P20240408) A 
    WHERE EXISTS (SELECT 1 FROM TEMP_**************_REL B WHERE A.OFFER_INST_ID = B.OFFER_INST_ID AND A.INSTANCE_ID = B.INSTANCE_ID )

Global Information
------------------------------
 Status                                 :  DONE (ERROR)
 Instance ID                            :  1
 Session                                :  ANBOB_1 (2661:175)
 SQL ID                                 :  75z55ptrvn0ux
 SQL Execution ID                       :  16777216
 Execution Started                      :  04/09/2024 00:19:25
 First Refresh Time                     :  04/09/2024 00:19:25
 Last Refresh Time                      :  04/09/2024 00:19:25
 Duration                               :  0s
 Module/Action                          :  JDBC Thin Client/-
 Service                                :  dayodsdb
 Program                                :  JDBC Thin Client
 PLSQL Entry Ids (Object/Subprogram)    :  42224025,1
 PLSQL Current Ids (Object/Subprogram)  :  42224025,1

Global Stats
SQL Plan Monitoring Details (Plan Hash Value=34080627)
================================================================================================================================================================
| Id |              Operation               |            Name            |  Rows   | Cost |   Time    | Start  | Execs |   Rows   | Activity | Activity Detail |
|    |                                      |                            | (Estim) |      | Active(s) | Active |       | (Actual) |   (%)    |   (# samples)   |
================================================================================================================================================================
|  0 | DELETE STATEMENT                     |                            |         |      |           |        |       |          |          |                 |
|  1 |   PX COORDINATOR                     |                            |         |      |           |        |       |          |          |                 |
|  2 |    PX SEND QC (RANDOM)               | :TQ10003                   |    597K |  564 |           |        |       |          |          |                 |
|  3 |     DELETE                           | MID***************_REL_DAY |         |      |           |        |       |          |          |                 |
|  4 |      PX RECEIVE                      |                            |    597K |  564 |           |        |       |          |          |                 |
|  5 |       PX SEND HASH (BLOCK ADDRESS)   | :TQ10002                   |    597K |  564 |           |        |       |          |          |                 |
|  6 |        HASH JOIN RIGHT SEMI BUFFERED |                            |    597K |  564 |           |        |       |          |          |                 |
|  7 |         PX RECEIVE                   |                            |    597K |  114 |           |        |       |          |          |                 |
|  8 |          PX SEND HASH                | :TQ10000                   |    597K |  114 |           |        |       |          |          |                 |
|  9 |           PX BLOCK ITERATOR          |                            |    597K |  114 |           |        |       |          |          |                 |
| 10 |            TABLE ACCESS FULL         | TEMP_**************_REL    |    597K |  114 |           |        |       |          |          |                 |
| 11 |         PX RECEIVE                   |                            |    818M |   36 |           |        |       |          |          |                 |
| 12 |          PX SEND HASH                | :TQ10001                   |    818M |   36 |           |        |       |          |          |                 |
| 13 |           PX PARTITION LIST ALL      |                            |    818M |   36 |           |        |       |          |          |                 |
| 14 |            INDEX FULL SCAN           | IDX_MID_***********RELB2   |    818M |   36 |           |        |       |          |          |                 |
================================================================================================================================================================

Note:
从上面的sql monitor report发现几个问题
1,SQL执行了一个多小时,但是状态是DONE (ERROR),表示中断,duration 又是0秒。
2,执行计划的stats部分多数为空
3,表上亿级别的数据cost如此小

当时真的hang死了吗?

break on inst_id skip page on  etime skip 1
col etime for a20
col event for a25
COL PCT FOR 9999999
col cpus for a5
col load for a30
col db_time for a15
col in_parse for a10
col qc for a10
col sql_id for a22
col avg_waited for 999,990.90 head avg_ms
col sesid for a15
col bs for a10
col TOP_LEVEL_SQL_ID for a22
col SQL_ID for a18
col p1_p2 for a15

select to_char(SAMPLE_TIME,'yyyymmdd hh24:mi:ss') etime,event,p1||':'||p2 p1_p2,inst_id||':'||SESSION_ID||':'||SESSION_SERIAL# sesid,IS_SQLID_CURRENT,--SQL_OPCODE,
sql_id --,TOP_LEVEL_SQL_ID ,TOP_LEVEL_SQL_OPCODE  ,SQL_PLAN_HASH_VALUE,SQL_PLAN_LINE_ID,SQL_PLAN_OPERATION
,SQL_EXEC_ID,SQL_EXEC_START
--,PLSQL_ENTRY_OBJECT_ID,PLSQL_ENTRY_SUBPROGRAM_ID,PLSQL_OBJECT_ID
,QC_INSTANCE_ID||':'||QC_SESSION_ID||':'||QC_SESSION_SERIAL#  qc,SEQ# ,
SESSION_STATE,TIME_WAITED,BLOCKING_INST_ID||':'||BLOCKING_SESSION bs,CURRENT_OBJ# ,TOP_LEVEL_CALL_NAME
IN_PARSE,IN_SQL_EXECUTION,IN_PLSQL_EXECUTION --,IN_BIND,IN_CURSOR_CLOSE,MACHINE,PROGRAM
--,PGA_ALLOCATED,TEMP_SPACE_ALLOCATED
 from system.ASH_20240409 
where  QC_SESSION_ID=2661 and QC_SESSION_SERIAL#=175 
 order by 1
 /
 
 ETIME                EVENT                     P1_P2           SESID           IS SQL_ID                 SQL_EXEC_ID SQL_EXEC_START      QC               SEQ# SESSION_STATE  TIME_WAITED BS         CURRENT_OBJ# IN_PARSE   IN IN
-------------------- ------------------------- --------------- --------------- -- ---------------------- ----------- ------------------- ---------- ---------- -------------- ----------- ---------- ------------ ---------- -- --
20240409 00:41:33    db file sequential read   296:2466880     3:1921:719      Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      11627 WAITING              11986 :              78633106 VERSION2   Y  N
                     db file sequential read   307:1549207     3:1937:785      Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      12392 WAITING              42647 :              78633108 VERSION2   Y  N
                     db file sequential read   129:2889654     3:1953:705      Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      15144 WAITING              39689 :              78633109 VERSION2   Y  N
                     db file sequential read   605:1452705     3:2178:3535     Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      18043 WAITING              59995 :              78633103 LOGOFF     Y  N
                     db file sequential read   219:2707008     3:1987:1629     Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      19671 WAITING              69086 :              78633104 LOGOFF     Y  N
                     db file sequential read   279:1342039     3:2130:2897     Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      20740 WAITING              43806 :              78633101 VERSION2   Y  N
                     db file sequential read   650:1753692     3:2147:1417     Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      21085 WAITING              57736 :              78633102            Y  N
                     db file sequential read   19:1403508      3:2162:391      Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      25971 WAITING              71274 :              78633100 VERSION2   Y  N

20240409 00:41:34    gc cr disk read           296:2467668     3:1921:719      Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      11683 WAITING               7414 :              78633106 VERSION2   Y  N
                     db file sequential read   307:1548682     3:1937:785      Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      12427 WAITING              22742 :              78633108 VERSION2   Y  N
                     db file sequential read   551:2326697     3:1953:705      Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      15192 WAITING             128802 :              78633109 VERSION2   Y  N
                     db file sequential read   194:1359784     3:2130:2897     Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      20814 WAITING              81848 :              78633101 VERSION2   Y  N
                     db file sequential read   650:1753871     3:2147:1417     Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      21119 WAITING             136779 :              78633102            Y  N
                     db file sequential read   542:1733688     3:2162:391      Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      26022 WAITING              78606 :              78633100 VERSION2   Y  N
                     db file sequential read   605:1453519     3:2178:3535     Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      18069 WAITING              63887 :              78633103 LOGOFF     Y  N
                     db file sequential read   536:2329737     3:1987:1629     Y  75z55ptrvn0ux             16777216 2024-04-09 00:19:25 1:2661:175      19707 WAITING              82553 :              78633104 LOGOFF     Y  N
...

Note:
注意实际上SQL一直在运行,也未堵塞,因为没有配置parallel_force_local,所以QC在node1,但SLAVE在node3,当然还可以ASH信息查看时间花费在执行计划哪一步,或使用asqlmon.sql脚本。 这个SQL比较简单,从执行计划也能猜出应该是Index full scan那步。

为什么使用index full scan,而不是index fast full scan?
我们知道index fast full scan和full table scan 一样使用多块读,index fast full scan无序索引读,对于index full scan是有序的block-by-block单块读,而大量的时间在db file sequential read单块读效率是低下的,对于两者的区别TOM在OTN上也有描述.

An index fast full scan reads the ENTIRE index, unsorted, as it exists on disk. It is basically using the index as a “skinny” version of the table. The query in question would only be accessing attributes in the index (we are not using the index as a way to get to the table, we are using the index INSTEAD of the table) We use multiblock IO and read all of the leaf, branch and the root block. We ignore the branch and root blocks and just process the (unordered) data on the leaf blocks.

An index full scan is when we read the index a block at a time – from start to finish. We’ll read the root block, navigate down the left hand side of the index (or right if we are doing a descending full scan) and then when we hit the leaf block – we’ll read across the entire bottom of the index – a block at a time – in sorted order. We use single block IO, not multiblock IO for this operation.

Index full scan is a mechanism where Oracle does not read all the required entries from the index by traversing the tree from top to leaf for the rows. Instead, it traverses the tree from top to bottom on “the left side” of the index. When the left-most leaf level is found, it continues to read the index horizontally in the order defined by the index keys, block-by-block.

Both access paths read the whole leaf level but with index fast full scan the leaf level of the index is read in the order the blocks are located on the disk, not in the order of the indexed data as index full scan did.

那么用IFFS好事还是坏事呢?不同的人可能会以不同的方式回答这个问题。
一方面,当您进行IFFS时,它可能会执行直接路径读取,而IFS将执行缓存读取,并且缓存可能已经包含了很多索引叶块,因此有些人可能更喜欢当前行为;
另一方面,Exadata 系统在执行直接路径段扫描时可以使用存储索引(即使该段是索引),因此 Exadata  IFFS可能比部分缓存的IFS更可取。

在11g的测试库先还原了该问题

SQL> create table test1 as select rownum id ,rownum||'anbob' name,lpad('x',4000,'xxxx') addr from dual connect by rownum<=1e4; 
 
SQL> @gts test1
Gather Table Statistics for table test1...

SQL> create table test2 as select rownum id ,rownum||'anbob' name,lpad('x',4000,'xxxx') addr from dual connect by rownum<=1e5;
 Table created. 

SQL> create index idx_test2_id on test2(id);
Index created.

SQL> @gts test2
Gather Table Statistics for table test2...
PL/SQL procedure successfully completed.

SQL> explain plan for delete test2 where exists(select 1 from test1 where test1.id=test2.id);
Explained.

SQL> @x2
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2072194808

--------------------------------------------------------------------------------------
| Id  | Operation             | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | DELETE STATEMENT      |              |  9999 |   175K|  2937   (1)| 00:00:36 |
|   1 |  DELETE               | TEST2        |       |       |            |          |
|*  2 |   HASH JOIN RIGHT SEMI|              |  9999 |   175K|  2937   (1)| 00:00:36 |
|   3 |    VIEW               | VW_SQ_1      | 10000 |   126K|  2713   (1)| 00:00:33 |
|   4 |     TABLE ACCESS FULL | TEST1        | 10000 | 40000 |  2713   (1)| 00:00:33 |
|   5 |    INDEX FULL SCAN    | IDX_TEST2_ID |   100K|   488K|   224   (1)| 00:00:03 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ITEM_1"="TEST2"."ID")

Note:
如果您在你的生产环境使用我相同的测试用例相同现象,恭喜你,也存在相同的BUG. 对于数据量很大的表,使用index full scan单块读再回表效率确实不是最好的,该问题不容易被发现,如果你有SQL审核产品不妨看看有没有相同的场景,我们可以加hint让CBO判断IFFS能不能使用?

增加IFFS hint

SQL> explain plan for delete  /*+index_ffs(test2)*/ test2 where exists(select 1 from test1 where test1.id=test2.id);
Explained.

SQL> @x2
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2566228551

---------------------------------------------------------------------------------------
| Id  | Operation              | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | DELETE STATEMENT       |              |  9999 |   175K|  2776   (1)| 00:00:34 |
|   1 |  DELETE                | TEST2        |       |       |            |          |
|*  2 |   HASH JOIN RIGHT SEMI |              |  9999 |   175K|  2776   (1)| 00:00:34 |
|   3 |    VIEW                | VW_SQ_1      | 10000 |   126K|  2713   (1)| 00:00:33 |
|   4 |     TABLE ACCESS FULL  | TEST1        | 10000 | 40000 |  2713   (1)| 00:00:33 |
|   5 |    INDEX FAST FULL SCAN| IDX_TEST2_ID |   100K|   488K|    63   (2)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("ITEM_1"="TEST2"."ID")
17 rows selected.

Note:
说明IFFS确实也可以使用,并且估的cost比IFS还要低。难道是这显示问题?我们可以做10053

10053 trace

SQL> @p multib
NAME                                     VALUE
---------------------------------------- ----------------------------------------
db_file_multiblock_read_count            128

SQL> @p block_si
NAME                                     VALUE
---------------------------------------- ----------------------------------------
db_block_size                            8192

SQL> show parameter index_cost
PARAMETER_NAME                                               TYPE                              VALUE
------------------------------------------------------------ --------------------------------- ----------------------------------
optimizer_index_cost_adj                                     integer                           100


SQL> @53on 
alter session set events '10053 trace name context forever, level 1';
alter session set "_optimizer_trace"=all;

SQL> explain plan for delete from test2 where exists(select 1 from test1 where test1.id=test2.id);
Explained.

SQL> @53off
alter session set events '10053 trace name context off';

对于上面的SQL和加IFFS HINT的SQL分别做了10053, trace片段如下

Access path analysis for TEST2
***************************************
SINGLE TABLE ACCESS PATH
  Single Table Cardinality Estimation for TEST2[TEST2]
  Table: TEST2  Alias: TEST2
    Card: Original: 100000.000000  Rounded: 100000  Computed: 100000.00  Non Adjusted: 100000.00
  Access Path: index (index (FFS))
    Index: IDX_TEST2_ID
    resc_io: 62.00  resc_cpu: 13580960
    ix_sel: 0.000000  ix_sel_with_filters: 1.000000
  Access Path: index (FFS)
    Cost:  62.56  Resp: 62.56  Degree: 1
      Cost_io: 62.00  Cost_cpu: 13580960
      Resp_io: 62.00  Resp_cpu: 13580960
  Best:: AccessPath: IndexFFS
  Index: IDX_TEST2_ID
         Cost: 62.56  Degree: 1  Resp: 62.56  Card: 100000.00  Bytes: 0


Access path analysis for TEST2
***************************************
SINGLE TABLE ACCESS PATH
  Single Table Cardinality Estimation for TEST2[TEST2]
  Table: TEST2  Alias: TEST2
    Card: Original: 100000.000000  Rounded: 100000  Computed: 100000.00  Non Adjusted: 100000.00
  Access Path: TableScan
    Cost:  27115.01  Resp: 27115.01  Degree: 0
      Cost_io: 27085.00  Cost_cpu: 727144000
      Resp_io: 27085.00  Resp_cpu: 727144000
  Access Path: index (FullScan)
    Index: IDX_TEST2_ID
    resc_io: 223.00  resc_cpu: 21588081
    ix_sel: 1.000000  ix_sel_with_filters: 1.000000
    Cost: 223.89  Resp: 223.89  Degree: 1
  Best:: AccessPath: IndexRange
  Index: IDX_TEST2_ID
         Cost: 223.89  Degree: 1  Resp: 223.89  Card: 100000.00  Bytes: 0

***************************************

NOTE:
由于索引快速完全扫描执行多块读取,实际也确实是IFFS的cost要低于IFS。 那oracle 19c是什么样的表现呢?

相同的用例在oracle 19c的执行计划

SQL> explain plan for delete from test2 where exists(select 1 from test1 where test1.id=test2.id);
Explained.

SQL> @x2
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2566228551
---------------------------------------------------------------------------------------
| Id  | Operation              | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | DELETE STATEMENT       |              | 10000 |   175K|  2775   (1)| 00:00:01 |
|   1 |  DELETE                | TEST2        |       |       |            |          |
|*  2 |   HASH JOIN RIGHT SEMI |              | 10000 |   175K|  2775   (1)| 00:00:01 |
|   3 |    VIEW                | VW_SQ_1      | 10000 |   126K|  2712   (1)| 00:00:01 |
|   4 |     TABLE ACCESS FULL  | TEST1        | 10000 | 40000 |  2712   (1)| 00:00:01 |
|   5 |    INDEX FAST FULL SCAN| IDX_TEST2_ID |   100K|   488K|    62   (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("ITEM_1"="TEST2"."ID")

Note:
在19c默认就可以使用IFFS. 那说明11.2.0.4确认可能存在某BUG或计算方法的错误。 另外也注意我们是没有调整optimizer_index_cost_adj参数值的,比如从100调到1,可能会产生IFFS切换到IFS的错误成本。JL大师在Index FFS Cost演示该问题,我这里不再复述。

单块读与多块读的COST计算
没有找到IFS/IFFS cost计算方法,但FTS的资料挺多可以用于多块读参考.

FTS Cost = ((BLOCKS/MBRC) x MREADTIM)/ SREADTIM + “CPU-costs”

而索引扫描单块读COST几乎就是IO次数,这也是为什么FTS多块读的cost要/SREADTIM单块读的时间,因为两个公式都*SREADTIM刚好就是多块读次数*多块读单次时间和单块读次数与单块读的时间,两个总时间的对比。

系统的统计信息记录在 aux_stats$

SQL> select sname,pname,pval1 from aux_stats$;

SNAME                                                        PNAME                                                             PVAL1
------------------------------------------------------------ ------------------------------------------------------------ ----------
SYSSTATS_INFO                                                STATUS
SYSSTATS_INFO                                                DSTART
SYSSTATS_INFO                                                DSTOP
SYSSTATS_INFO                                                FLAGS                                                                 1
SYSSTATS_MAIN                                                CPUSPEEDNW                                                         2067
SYSSTATS_MAIN                                                IOSEEKTIM                                                            10
SYSSTATS_MAIN                                                IOTFRSPEED                                                         4096
SYSSTATS_MAIN                                                SREADTIM
SYSSTATS_MAIN                                                MREADTIM
SYSSTATS_MAIN                                                CPUSPEED
SYSSTATS_MAIN                                                MBRC
SYSSTATS_MAIN                                                MAXTHR
SYSSTATS_MAIN                                                SLAVETHR
13 rows selected.

这里突出显示的 3 个指标正如我们所看到的没有设置。默认情况下,我们需要计算这些值,或者知道在哪里可以找到这些值的默认值才能获得它们。一旦有了这 3 个指标,我们就可以计算优化器用于将 MULTIBLOCK READ 转换为 SINGLE BLOCK READS 的成本指标的 RATIO。
CPU 速度用于计算查询的 COST(以所需的 CPU 为单位)。这只是查询成本的一小部分,几乎可以忽略, 但它可能有助于决定选择哪个计划。这也从 CPU 成本转换为单块读取的成本指标。

MBRC很容易。如果未显式设置,则使用 init.ora 参数 “db_file_multiblock_read_count” 。但是默认情况下不应设置此值,这意味着 Oracle 将使用隐藏参数“_db_file_optimizer_read_count”来计算查询费用。默认值为 8。[注意:这不是执行中使用的值。Oracle 尝试执行 1MB 读取,并使用“_db_file_exec_read_count”中的值来控制执行时的多块读取。对于 8K 块大小,此值设置为 128。

 

CPUSPEED – CPU Speed
IOTFRSPEED – I/O transfer speed
IOSEEKTIM – I/O seek time
SREADTIM – average single block read time
MREADTIM – average multiblock read time
MBRC – average multiblock read count
MAXTHR – maximum I/O system throughput
SLAVETHR – average slave I/O throughput

SREADTIM = IOSEEKTIM + DB_BLOCK_SIZE / IOTFRSPEED = 10 + 8192 / 4096 = 12 (ms)
MREADTIM = IOSEEKTIM + (DB_BLOCK_SIZE * MBRC) / IOTFRSPEED = 10 + (8192 * 8 ) / 4096 = 10 + 16 = 26 (ms)

# 删除系统统计信息
exec dbms_stats.delete_system_stats;

# 收集系统统计信息
exec dbms_stats.gather_system_stats;

# 手动配置统计信息
exec dbms_stats.set_system_stats('MREADTIM',1000);
exec dbms_stats.set_system_stats('SREADTIM',10);
exec dbms_stats.set_system_stats('MBRC',128);

Note:
注意这里我们也没有手动配置系统信息,因为SREADTIM和MREADTIM的时间差异也会影响单块读与多块读的差异。

– 如果您收集或设置了系统统计数据,请务必检查它们是否合理。
– 如果您确实使用非常低的 SREADTIM 和高 MREADTIM 来支持索引访问(不使用低值的 OPTIMIZER_INDEX_COST_ADJ),那么请确保 MREADTIM <= SREADTIM * MBRC。

关于 这两个计算方法ACED Neil Chandler在他的blog也有记录

select pname,pval1,calculated,formula from sys.aux_stats$ where sname='SYSSTATS_MAIN'
model
reference sga on (
select 'Database Buffers' name,sum(bytes) value from v$sgastat where name in ('shared_io_pool','buffer_cache')
) dimension by (name) measures(value)
reference parameter on (
select name,decode(type,3,to_number(value)) value from v$parameter where name='db_file_multiblock_read_count' and ismodified!='FALSE'
union all
select name,decode(type,3,to_number(value)) value from v$parameter where name='sessions'
union all
select name,decode(type,3,to_number(value)) value from v$parameter where name='db_block_size'
union all
SELECT a.ksppinm name, to_number(b.ksppstvl) value FROM x$ksppi a, x$ksppsv b WHERE a.indx=b.indx AND ksppinm like '_db_file_optimizer_read_count'
)
dimension by (name) measures(value)
partition by (sname) dimension by (pname) measures (pval1,pval2,cast(null as number) as calculated,cast(null as varchar2(120)) as formula) rules(
calculated['MBRC']=coalesce(pval1['MBRC'],parameter.value['db_file_multiblock_read_count'],parameter.value['_db_file_optimizer_read_count'],8),
calculated['MREADTIM']=coalesce(pval1['MREADTIM'],pval1['IOSEEKTIM'] + (parameter.value['db_block_size'] * calculated['MBRC'] ) / pval1['IOTFRSPEED']),
calculated['SREADTIM']=coalesce(pval1['SREADTIM'],pval1['IOSEEKTIM'] + parameter.value['db_block_size'] / pval1['IOTFRSPEED']),
calculated['_multi block Cost per block']=round(1/calculated['MBRC']*calculated['MREADTIM']/calculated['SREADTIM'],4),
calculated['_single block Cost per block']=1,
formula['MBRC']=case when pval1['MBRC'] is not null then 'MBRC' when parameter.value['db_file_multiblock_read_count'] is not null then 'db_file_multiblock_read_count' when parameter.value['_db_file_optimizer_read_count'] is not null then '_db_file_optimizer_read_count (db_file_multiblock_read_count not set, which is good!)' else '= not sure so used 8' end,
formula['MREADTIM']=case when pval1['MREADTIM'] is null then '= IOSEEKTIM + db_block_size * MBRC / IOTFRSPEED' end||' = '||pval1['IOSEEKTIM']||'+('||parameter.value['db_block_size']||'*'||calculated['MBRC']||'/'||pval1['IOTFRSPEED']||')',
formula['SREADTIM']=case when pval1['SREADTIM'] is null then '= IOSEEKTIM + db_block_size / IOTFRSPEED' end||' = '||pval1['IOSEEKTIM']||'+('||parameter.value['db_block_size']||'/'||pval1['IOTFRSPEED']||')',
formula['_multi block Cost per block']='= 1/MBRC * MREADTIM/SREADTIM = 1/'||calculated['MBRC']||' * '||calculated['MREADTIM']||'/'||calculated['SREADTIM'],
calculated['_maximum mbrc']=sga.value['Database Buffers']/(parameter.value['db_block_size']*parameter.value['sessions']),
formula['_maximum mbrc']='= buffer cache blocks/sessions (small cache limiter) = ' || sga.value['Database Buffers']/parameter.value['db_block_size']||'/'||parameter.value['sessions'],
formula['_single block Cost per block']='relative to the multi blovk cost per block. Always 1!',
formula['CPUSPEED']='overrides CPUSPEEDNW when set',
formula['CPUSPEEDNW']='CPU speed Mhz - non workload',
formula['IOSEEKTIM']='IO seek time in ms',
formula['IOTFRSPEED']='IO transfer speed in KB/s',
formula['MAXTHR']='Maximum IO system throughput',
formula['SLAVETHR']='average parallel slave IO throughput'
) order by 1;

PNAME                                        PVAL1 CALCULATED FORMULA
--------------------------------------- ---------- ---------- ---------------------------------------------------------------------------------------------- 
CPUSPEED                                                      overrides CPUSPEEDNW when set
CPUSPEEDNW                                    2067            CPU speed Mhz - non workload
IOSEEKTIM                                       10            IO seek time in ms
IOTFRSPEED                                    4096            IO transfer speed in KB/s
MAXTHR                                                        Maximum IO system throughput
MBRC                                                        8 _db_file_optimizer_read_count (db_file_multiblock_read_count not set, which is good!)
MREADTIM                                                   26 = IOSEEKTIM + db_block_size * MBRC / IOTFRSPEED = 10+(8192*8/4096)
SLAVETHR                                                      average parallel slave IO throughput
SREADTIM                                                   12 = IOSEEKTIM + db_block_size / IOTFRSPEED = 10+(8192/4096)
_maximum mbrc                                      238.575916 = buffer cache blocks/sessions (small cache limiter) = 364544/1528
_multi block Cost per block                             .2708 = 1/MBRC * MREADTIM/SREADTIM = 1/8 * 26/12
_single block Cost per block                                1 relative to the multi blovk cost per block. Always 1!

另外Oracle CBO: Calculate Full Table Scan cost也有FULL table scan的计算方法,可以参考. 我转载附在后面.

目前是参数没有问题,系统统计信息也正常,接下来查找是否有相关BUG? 发现在11.2.0.4中使用IFS而没有使用IFFS的bug还确实有几个,目前使用11.2.0.4的生产还是挺多,虽然当前已主推19c很多年,以下BUG 都建议您修复。

FTS,IFS,IFFS错误切换已知BUG

Bug 17863980 Parallel query slow; not using better full table scan or index fast full scan
affected Versions >= 11.2.0.4 but BELOW 12.2
Description
  This bug is only relevant when using Parallel Query (PQO)
  With fix of bug 13097308 cost of parallel scan can become unreasonably high leading to sub-optimal plan.

Rediscovery Notes
  If parallel queries run slowly due to plan changes, avoiding full table scans
  and/or index fast full scans (index_ffs), workload system stats have been gathered,
  MAXTHR and/or SLAVETHR are small when measured in bytes/sec, and setting
  OFE=11.2.0.3 or earlier does not fix the issue, then you may be affected by this bug.

Workaround
  Increase system stats value of SREADTIM

Bug 17908541  CBO does not consider INDEX_FFS for DELETE
Versions confirmed as being affected 	
    11.2.0.4
    11.2.0.3 
Description

    DELETE (on a non-IOT) does not choose to execute with index fast full scan 
    unless hinted.
     
    Rediscovery Notes
     If a DELETE gets suboptimal performance because it is not executing with an
     index fast full scan but using a hint does enable this access path then you
     may have encountered this bug.


Bug 13097308 MAXTHR and SLAVETHR System Statistics Not Working

the fix for 13097308 is first included in
12.1.0.1 (Base Release)
12.1.0.1 Release
11.2.0.4 (Server Patch Set)

Description
   The optimizer does not correctly account for the system statistics
   MAXTHR and SLAVETHR when evaluating parallel queries, using
   values 1000x too high and effectively ignoring the limits.

Rediscovery Notes
  Queries have poor performance due to choosing a parallel table access
  over an index access due to a low I/O cost.

Workaround
  Manually set MAXTHR and SLAVETHR to 1/1000th the value.


 Bug 8926483  Running full table scan on two node RAC does "db file sequential read" - slow performance
 affected 	Versions >= 10.2.0.4 but BELOW 11.1
Description

    This bug is only relevant when using Real Application Clusters (RAC)

    In a RAC environment Multi-block read may get converted to single-block 
    reads when the cursor gets invalidated.
     
    Rediscovery Notes
    - High disk read for queries.
    - dumps using the events 10078 and 10046 shows:

Optimizer Chooses Expensive Index Full Scan over Index Fast Full Scan or Full Table Scan from 12.1 (Doc ID 2382922.1)
Unpublished Bug 24513980 : OPTIMIZER SHOULD IGNORE SYSTEM STATS WHEN MREADTIM > SREADTIM*MBRC
The above bug is closed as duplicate of below:
Unpublished Bug 27643128 : SQL NOT PICKING INDEX FAST FULL SCAN IN 12.1.0.2 - fixed in 19.1
Problem is due to MREADTIM system statistics value which is not correct leading to the optimizer choosing bad plans using single block IO(IFS) over good plans using
multi-block IO(IFFS/FTS).

Query Using Full Table Scan Over Index Scan After Upgrade to 12c (Doc ID 2362906.1)
Bug 24758816 - Query Slower after Upgrade to 12c Favoring Full Table Scan (FTS) instead of Index Scan (Doc ID 24758816.8)
Bug 27392876  Fusionapps: SQL_ID 70k3h0pgsn62j Shows Regression with Fix for Bug 24758816 Enabled
Scalar subquery unnesting taking place, but CBQT JPPD is not tried due 
to the fact that query is not eligible for CBQT. 

note:
可见错误的使用FTS,IFS在10g,11g,12-18c都有相关的bug. 我们当前11g r2匹配的范围的是17863980和17908541.

19c中复现问题

SQL> show parameter feature
PARAMETER_NAME                                               TYPE                              VALUE
------------------------------------------------------------ --------------------------------- ----------------------------------------------
disable_pdb_feature                                          big integer                       0
optimizer_features_enable                                    string                            19.1.0.1

SQL> @fix "FFS"

SESSION_ID      BUGNO      VALUE SQL_FEATURE                         DESCRIPTION                                                            OPTIMIZER_FEATU      EVENT IS_DEFAULT     CON_ID
---------- ---------- ---------- ----------------------------------- ---------------------------------------------------------------------- --------------- ---------- ---------- ----------
       260    4483286          0 QKSFM_CBO_4483286                   Discount FFS cost using optimizer_index_cost_adj.                                           38085          1          0
       260    7155968          1 QKSFM_ACCESS_PATH_7155968           Restrict use of disabled FFS on IOTs                                   11.1.0.7                 0          1          0
       260   19563657          1 QKSFM_ACCESS_PATH_19563657          no IFFS on SELECT FOR UPDATE, add table access cost to index sca       12.2.0.1                 0          1          0
       260   25995431          1 QKSFM_INDEX_FFS_25995431            adjust leaf blocks for index fast full scan                            18.1.0                   0          1          0
       260   27643128          1 QKSFM_CBO_27643128                  put system stats sanity check to correct IFFS cost computation         19.1.0                   0          1          0

SQL> @fix "fast full scan"

SESSION_ID      BUGNO      VALUE SQL_FEATURE                         DESCRIPTION                                                            OPTIMIZER_FEATU      EVENT IS_DEFAULT     CON_ID
---------- ---------- ---------- ----------------------------------- ---------------------------------------------------------------------- --------------- ---------- ---------- ----------
       260    6980350          1 QKSFM_CBO_6980350                   fix fast full scan index only determination                            11.2.0.1                 0          1          0
       260   17908541          1 QKSFM_ACCESS_PATH_17908541          consider index fast full scan on DELETE to non-IOT                     12.1.0.2                 0          1          0
       260   25995431          1 QKSFM_INDEX_FFS_25995431            adjust leaf blocks for index fast full scan                            18.1.0                   0          1          0

SQL> alter session set "_fix_control"="17908541:off";
Session altered.

SQL> explain plan for delete test2 where exists(select 1 from test1 where test1.id=test2.id);
Explained.

SQL> @x2
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2547202327

----------------------------------------------------------------------------------------
| Id  | Operation               | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | DELETE STATEMENT        |              | 10000 |   175K|   231   (1)| 00:00:01 |
|   1 |  DELETE                 | TEST2        |       |       |            |          |
|*  2 |   HASH JOIN RIGHT SEMI  |              | 10000 |   175K|   231   (1)| 00:00:01 |
|   3 |    VIEW                 | VW_SQ_1      | 10000 |   126K|     7   (0)| 00:00:01 |
|   4 |     INDEX FAST FULL SCAN| IDX_TEST1_ID | 10000 | 40000 |     7   (0)| 00:00:01 |
|   5 |    INDEX FULL SCAN      | IDX_TEST2_ID |   100K|   488K|   224   (1)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("ITEM_1"="TEST2"."ID")

Note:
从高版本复现低版本的问题,我们可以更改optimizer_features_enable到低版本,但并不一定总是好使,如果复现,可以对比两个优化器版本的参数和bug fixed差异,这里关闭17908541 bug fixed复现了问题,说明这个案例匹配的是这个bug. 然后就可以安装oneoff patch解决该问题,或使用index_ffs解决。

Summary:
这个案例因为SQL运行时间较长,sql monitor显示错误,引起我们的关注,发现SQL花费大量的时间在db file sequential read , 对应的执行计划为index  full scan(IFS), 并不是最佳的执行计划,使用index_ffs确认也可以使用index fast full scan(IFFS),并且cost也更低,确认了参数和系统统计信息并没有错误配置,和19c对比确认存在bug, 最后明确了为bug 17908541.

— 脚本1

DECLARE
   v_owner_name   VARCHAR2 (30) := 'SYS';       -- Schema
   v_table_name   VARCHAR2 (30) := 'TEST2';         -- Table name
   v_cost_model   VARCHAR2 (30);
   v_block_size   NUMBER;                                                                               -- db_block_size
   v_dbforc       NUMBER;                                                                --_db_file_optimizer_read_count
   v_dbfmbrc      NUMBER;                                                               -- db_file_multiblock_read_count
   v_blocks       NUMBER;
   v_cost         NUMBER;
   v_io_cost      NUMBER;                                                                           -- from explain plan
   v_#MRDs        NUMBER;                                                                 -- number of multi block reads
   v_sreadtim     NUMBER;                                                                      -- single block read time
   v_mreadtim     NUMBER;                                                                       -- multi block read time
   v_msg          VARCHAR2 (2000);
   TYPE typ_sys_stat_tab IS TABLE OF NUMBER
      INDEX BY VARCHAR2 (30);
   v_sys_stat     typ_sys_stat_tab;
BEGIN
   -- db_block_size: 8192 (default)
   SELECT VALUE
     INTO v_block_size
     FROM v$parameter
    WHERE name = 'db_block_size';
   -- Session Value of _db_file_optimizer_read_count: 8 (default)
   SELECT b.ksppstvl "Session Value"
     INTO v_dbforc
     FROM x$ksppi a, x$ksppcv b
    WHERE a.indx = b.indx AND a.ksppinm = '_db_file_optimizer_read_count';
   SELECT b.ksppstvl "Session Value"
     INTO v_dbfmbrc
     FROM x$ksppi a, x$ksppcv b
    WHERE a.indx = b.indx AND a.ksppinm = 'db_file_multiblock_read_count';
   -- table block# used
   SELECT blocks
     INTO v_blocks
     FROM all_tables
    WHERE OWNER = v_owner_name AND table_name = v_table_name;
   -- get system statistics parameters
   FOR r1 IN (SELECT pname, pval1
                FROM sys.aux_stats$)
   LOOP
      v_sys_stat (r1.pname) := r1.pval1;
   END LOOP;
   /*
    _OPTIMIZER_COST_MODEL (hidden) parameter:
     'CHOOSE' - default: chooses CPU costing based on the presence of statistics in aux_stats$,
     'CPU'    - forces CPU costing based on statistics in aux_stats$,
     'IO'     - forces IO costing
   */
   SELECT b.ksppstvl "Session Value"
     INTO v_cost_model
     FROM x$ksppi a, x$ksppcv b
    WHERE a.indx = b.indx AND a.ksppinm = '_optimizer_cost_model';
   v_msg := 'Full Table Scan: ' || v_owner_name || '.' || v_table_name;
   v_msg := v_msg || CHR (10) || '  number of used table blocks:   ' || v_blocks;
   v_msg := v_msg || CHR (10) || '  db_block_size: ' || v_block_size;
   v_msg := v_msg || CHR (10) || '  _optimizer_cost_model: ' || v_cost_model;
   v_msg := v_msg || CHR (10) || '  _db_file_optimizer_read_count (session value): ' || v_dbforc;
   v_msg := v_msg || CHR (10) || '  db_file_multiblock_read_count (session value): ' || v_dbfmbrc;
   IF v_cost_model = 'IO'
   THEN
      v_cost := CEIL (v_blocks * 0.5965 / POWER (v_dbforc, 0.6582));
      v_msg :=
            v_msg
         || CHR (10)
         || '  Formaula: io_cost = ceil(blocks * 0.5965/ _db_file_optimizer_read_count ^ 0.6582'
         || CHR (10)
         || '  _db_file_optimizer_read_count: '
         || v_dbforc;
   ELSE                                                                                             -- 'CHOOSE' or 'CPU'
      IF v_sys_stat ('FLAGS') = 0 OR v_sys_stat ('MBRC') IS NULL
      THEN
         IF v_sys_stat ('FLAGS') = 0
         THEN
            v_msg := v_msg || CHR (10) || 'System Statistics type: Default NOWORKLOAD';
         ELSE
            v_msg := v_msg || CHR (10) || 'System Statistics type: Gathered NOWORKLOAD';
         END IF;
         v_msg := v_msg || CHR (10) || '  IOSEEKTIM : ' || v_sys_stat ('IOSEEKTIM');
         v_msg := v_msg || CHR (10) || '  IOTFRSPEED: ' || v_sys_stat ('IOTFRSPEED');
         v_sreadtim := v_sys_stat ('IOSEEKTIM') + v_block_size / v_sys_stat ('IOTFRSPEED');
         v_msg := v_msg || CHR (10) || 'SREADTIM = IOSEEKTIM + db_block_size / IOTFRSPEED';
         v_msg := v_msg || CHR (10) || '  single-block I/O read time:    ' || v_sreadtim;
         v_mreadtim := v_sys_stat ('IOSEEKTIM') + v_dbforc * v_block_size / v_sys_stat ('IOTFRSPEED');
         v_msg := v_msg || CHR (10) || 'MREADTIM = IOSEEKTIM + mbrc * db_block_size / IOTFRSPEED';
         v_msg := v_msg || CHR (10) || '  multi-block I/O read time:     ' || v_mreadtim;
         v_#MRDs := v_blocks / v_dbforc;
         v_msg := v_msg || CHR (10) || '#MRDs = table block# used / _db_file_optimizer_read_count';
         v_msg := v_msg || CHR (10) || '  #MRDs: ' || v_#MRDs;
         v_cost := GREATEST (1, CEIL (v_#MRds * v_mreadtim / v_sreadtim));
      ELSE
         v_msg := v_msg || CHR (10) || 'System Statistics type: Gathered WORKLOAD';
         v_msg := v_msg || CHR (10) || '  use SYS.AUX_STATS$.MBRC to calculate #MRDs';
         v_msg := v_msg || CHR (10) || '  SREADTIM  : ' || v_sys_stat ('SREADTIM');
         v_msg := v_msg || CHR (10) || '  MREADTIM  : ' || v_sys_stat ('MREADTIM');
         v_msg := v_msg || CHR (10) || '  MBRC      : ' || v_sys_stat ('MBRC');
         v_#MRDs := v_blocks / v_sys_stat ('MBRC');
         v_msg := v_msg || CHR (10) || '#MRDs = table block# used / MBRC';
         v_msg := v_msg || CHR (10) || '  #MRDs: ' || v_#MRDs;
         v_cost := GREATEST (1, CEIL (v_#MRds * v_sys_stat ('MREADTIM') / v_sys_stat ('SREADTIM')));
      END IF;
      DBMS_OUTPUT.put_line (v_msg);
      DBMS_OUTPUT.put_line ('-----------------');
   END IF;
   DBMS_OUTPUT.PUT_LINE ('io_cost = MAX(1, CEIL(blocks / MBRC * MREADTIM / SREADTIM))');
   DBMS_OUTPUT.put_line ('COST calculated: ' || v_cost);
   DBMS_OUTPUT.put_line ('-----------------');
   -- EXPLAIN PLAN
   --EXECUTE IMMEDIATE 'ALTER SESSION SET "_table_scan_cost_plus_one" = FALSE';
   -- EXECUTE IMMEDIATE 'alter session set db_file_multiblock_read_count=' || v_mbrc;
   EXECUTE IMMEDIATE 'EXPLAIN PLAN FOR SELECT * FROM ' || v_owner_name || '.' || v_table_name;
   SELECT IO_COST
     INTO v_io_cost
     FROM plan_table
    WHERE     operation = 'TABLE ACCESS'
          AND options = 'FULL'
          AND object_name = UPPER (v_table_name)
          AND plan_id = (SELECT MAX (plan_id) FROM plan_table);
   DBMS_OUTPUT.put_line ('COST from explain plan: ' || v_io_cost);
END;

Full Table Scan: SYS.TEST2
  number of used table blocks:   100000
  db_block_size: 8192
  _optimizer_cost_model: CHOOSE
  _db_file_optimizer_read_count (session value): 8
  db_file_multiblock_read_count (session value): 8
System Statistics type: Gathered NOWORKLOAD
  IOSEEKTIM : 10
  IOTFRSPEED: 4096
SREADTIM = IOSEEKTIM + db_block_size / IOTFRSPEED
  single-block I/O read time:    12
MREADTIM = IOSEEKTIM + mbrc * db_block_size / IOTFRSPEED
  multi-block I/O read time:     26
#MRDs = table block# used / _db_file_optimizer_read_count
  #MRDs: 12500
-----------------
io_cost = MAX(1, CEIL(blocks / MBRC * MREADTIM / SREADTIM))
COST calculated: 27084
-----------------
COST from explain plan: 27085

打赏

, ,

对不起,这篇文章暂时关闭评论。