Oracle聚簇因子导致回表逻辑读高

Oracle聚簇因子导致回表逻辑读高

前言

最近优化SQL过程中,发现一个比较奇怪的现象。

通过索引回表之后的逻辑读远大于直接全表扫描的逻辑读。

16:47:39 SYS@test1(1315)> select count(*) from T_ABCDEFGG_HIJ_KLMN H where "H"."IS_NRTX"='0' AND "H"."ADACMESH_TYPE"='03' AND "H"."CREATE_TIME"<SYSDATE-15;

  COUNT(*)
----------
    108757

Elapsed: 00:00:00.39
16:54:04 SYS@test1(1315)> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------
SQL_ID  85u2yk4xaydr2, child number 0
-------------------------------------
select count(*) from T_ABCDEFGG_HIJ_KLMN H where "H"."IS_NRTX"='0' AND
"H"."ADACMESH_TYPE"='03' AND "H"."CREATE_TIME"<SYSDATE-15

Plan hash value: 614647002

----------------------------------------------------------------------------------------------------
| Id  | Operation          | Name                | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                     |      1 |        |      1 |00:00:00.38 |   40357 |
|   1 |  SORT AGGREGATE    |                     |      1 |      1 |      1 |00:00:00.38 |   40357 |
|*  2 |   TABLE ACCESS FULL| T_ABCDEFGG_HIJ_KLMN |      1 |  28967 |    108K|00:00:00.37 |   40357 |
----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter(("H"."IS_NRTX"='0' AND "H"."ADACMESH_TYPE"='03' AND
              "H"."CREATE_TIME"<SYSDATE@!-15))


21 rows selected.

Elapsed: 00:00:00.33
16:54:05 SYS@test1(1315)> select /*+ index(H IDX_T_ABCDEFGG_HIJ_KLMN_01)*/ count(*) from T_ABCDEFGG_HIJ_KLMN H where "H"."IS_NRTX"='0' AND "H"."ADACMESH_TYPE"='03' AND "H"."CREATE_TIME"<SYSDATE-15;

  COUNT(*)
----------
    108757

Elapsed: 00:00:01.13
16:54:15 SYS@test1(1315)> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------
SQL_ID  6h43tgxns9up2, child number 0
-------------------------------------
select /*+ index(H IDX_T_ABCDEFGG_HIJ_KLMN_01)*/ count(*) from
T_ABCDEFGG_HIJ_KLMN H where "H"."IS_NRTX"='0' AND
"H"."ADACMESH_TYPE"='03' AND "H"."CREATE_TIME"<SYSDATE-15

Plan hash value: 3917194736

---------------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name                       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                            |      1 |        |      1 |00:00:01.12 |     264K|
|   1 |  SORT AGGREGATE              |                            |      1 |      1 |      1 |00:00:01.12 |     264K|
|*  2 |   TABLE ACCESS BY INDEX ROWID| T_ABCDEFGG_HIJ_KLMN        |      1 |  28967 |    108K|00:00:01.12 |     264K|
|*  3 |    INDEX RANGE SCAN          | IDX_T_ABCDEFGG_HIJ_KLMN_01 |      1 |   1013K|   1013K|00:00:00.18 |    2690 |
---------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter(("H"."IS_NRTX"='0' AND "H"."ADACMESH_TYPE"='03'))
   3 - access("H"."CREATE_TIME"<SYSDATE@!-15)


23 rows selected.

Elapsed: 00:00:00.04


16:59:03 SYS@test1(1315)> select segment_name,sum(bytes)/1024/1024 mb from dba_segments where owner='ABCDEFG_HIJKLMNO' and segment_name in ('T_ABCDEFGG_HIJ_KLMN','IDX_T_ABCDEFGG_HIJ_KLMN_01') group by segment_name;

SEGMENT_NAME                                MB
----------------------------------- ----------
IDX_T_ABCDEFGG_HIJ_KLMN_01                  25
T_ABCDEFGG_HIJ_KLMN                        317

Elapsed: 00:00:00.15

从上边可以看出,强制走索引后,逻辑读达264k,即264000*8/1024/1024=2.014G。

但是实际上表和索引加起来的大小也才342M,这就很奇怪了。

探究

由于索引处的逻辑读为2690,在回表后的逻辑读变为264K,加上逻辑读(264K-2690)远大于实际表的大小,因此只有一个可能性。

那就是回表过程中某些块被重复多次读取导致的。

第一反应会以为回表读,已经读取过的块不会再次被读取,现在看来不是这么个情况。

网上百度的话其实找不到相关资料,从网上某些实验结果看,很容易让人误会就是不会再次读取已经被读取过的块。

MOS也无相关资料,那么只有自己构造环境来进行实验证明了。

环境构造

创建一张表tt并插入数据,注意插入数据需要用同一个会话运行。

create table tt (id int,name varchar2(2000)) tablespace users;

insert into tt values(1,rpad('e',1804,'+'));
insert into tt values(5,rpad('e',1804,'+'));
insert into tt values(9,rpad('e',1804,'+'));
insert into tt values(13,rpad('e',1804,'+'));

insert into tt values(2,rpad('e',1804,'+'));
insert into tt values(6,rpad('e',1804,'+'));
insert into tt values(10,rpad('e',1804,'+'));
insert into tt values(14,rpad('e',1804,'+'));

insert into tt values(3,rpad('e',1804,'+'));
insert into tt values(7,rpad('e',1804,'+'));
insert into tt values(11,rpad('e',1804,'+'));
insert into tt values(15,rpad('e',1804,'+'));


insert into tt values(4,rpad('e',1804,'+'));
insert into tt values(8,rpad('e',1804,'+'));
insert into tt values(12,rpad('e',1804,'+'));
insert into tt values(16,rpad('e',1804,'+'));

按照这样子,可以达到每4行数据在一个块,

08:44:59 SYS@zkm(51)> col id for a10
08:45:04 SYS@zkm(51)> select dbms_rowid.rowid_relative_fno(rowid) file#,dbms_rowid.rowid_block_number(rowid) block#,wm_concat(id) id from tt group by dbms_rowid.rowid_relative_fno(rowid),dbms_rowid.rowid_block_number(rowid);

     FILE#     BLOCK# ID
---------- ---------- ----------
         4        923 4,16,12,8
         4        925 1,13,9,5
         4        926 2,14,10,6
         4        927 3,15,11,7

Elapsed: 00:00:00.00

即:

块号 id值
925 1 5 9 13
926 2 6 10 14
927 3 7 11 15
923 4 8 12 16

在id列上创建索引,

08:48:15 SYS@zkm(51)> create index idx_id on tt(id);

Index created.

Elapsed: 00:00:00.01
08:48:17 SYS@zkm(51)> analyze table tt compute statistics;

Table analyzed.

Elapsed: 00:00:00.03

通过如下方式,更改id后的取值,统计处每次回表的逻辑读。

08:50:34 SYS@zkm(51)> set pagesize 9999 long 9999 line 500 timing on
08:50:34 SYS@zkm(51)> alter session set statistics_level=all;

Session altered.

Elapsed: 00:00:00.00
08:50:35 SYS@zkm(51)> select /*+ index(tt idx_id) */ count(name) from tt where id<=1;

COUNT(NAME)
-----------
          1

Elapsed: 00:00:00.00
08:50:38 SYS@zkm(51)> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  1vb1jwbm5fuv9, child number 0
-------------------------------------
select /*+ index(tt idx_id) */ count(name) from tt where id<=1

Plan hash value: 2637792774

-------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |        |      1 |        |      1 |00:00:00.01 |       2 |
|   1 |  SORT AGGREGATE              |        |      1 |      1 |      1 |00:00:00.01 |       2 |
|   2 |   TABLE ACCESS BY INDEX ROWID| TT     |      1 |      1 |      1 |00:00:00.01 |       2 |
|*  3 |    INDEX RANGE SCAN          | IDX_ID |      1 |      1 |      1 |00:00:00.01 |       1 |
-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("ID"<=1)


20 rows selected.

Elapsed: 00:00:00.01

其中,id3访问索引的逻辑读为1,因此回表的逻辑读为2-1=1。

同样的方式,依次统计从id<=1到id<=16。

需要说明一点的是,由于数据分布的特点,id<=1是,访问925号块,id<=2则访问925,926号块,id<=3对应925,926,927,id<=4对应925,926,927,923,

而当id<=5的时候,由于id=1和id=5都在925块上,因此从这开始看后续是否逻辑读增加即可。

id<=n 回表逻辑读
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
11 11
12 12
13 13
14 14
15 15
16 16

从统计结果看,由于925,926,927,923这四个块在条件id<=16处各被访问了4次。

这是由于数据分布特点导致的,即聚簇因子。

如若数据分布如下,那么在id<=16的时候回表逻辑读只会产生4次。

块号 id值
925 1 2 3 4
926 5 6 7 8
927 9 10 11 12
923 13 14 15 16
create table tt (id int,name varchar2(2000)) tablespace users;

insert into tt values(1,rpad('e',1804,'+'));
insert into tt values(2,rpad('e',1804,'+'));
insert into tt values(3,rpad('e',1804,'+'));
insert into tt values(4,rpad('e',1804,'+'));

insert into tt values(5,rpad('e',1804,'+'));
insert into tt values(6,rpad('e',1804,'+'));
insert into tt values(7,rpad('e',1804,'+'));
insert into tt values(8,rpad('e',1804,'+'));

insert into tt values(9,rpad('e',1804,'+'));
insert into tt values(10,rpad('e',1804,'+'));
insert into tt values(11,rpad('e',1804,'+'));
insert into tt values(12,rpad('e',1804,'+'));


insert into tt values(13,rpad('e',1804,'+'));
insert into tt values(14,rpad('e',1804,'+'));
insert into tt values(15,rpad('e',1804,'+'));
insert into tt values(16,rpad('e',1804,'+'));
View Code
09:15:00 SYS@zkm(51)> select dbms_rowid.rowid_relative_fno(rowid) file#,dbms_rowid.rowid_block_number(rowid),wm_concat(id) from tt group by dbms_rowid.rowid_relative_fno(rowid),dbms_rowid.rowid_block_number(rowid);

     FILE# DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) WM_CONCAT(ID)
---------- ------------------------------------ --------------------------------------------------------------------------------
         4                                  923 13,16,15,14
         4                                  925 1,4,3,2
         4                                  926 5,8,7,6
         4                                  927 9,12,11,10

Elapsed: 00:00:00.01
09:15:04 SYS@zkm(51)> select /*+ index(tt idx_id) */ count(name) from tt where id<=16;

COUNT(NAME)
-----------
         16

Elapsed: 00:00:00.00
09:15:11 SYS@zkm(51)> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  90x3ztbc6cd7b, child number 0
-------------------------------------
select /*+ index(tt idx_id) */ count(name) from tt where id<=16

Plan hash value: 2637792774

-------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |        |      1 |        |      1 |00:00:00.01 |       5 |
|   1 |  SORT AGGREGATE              |        |      1 |      1 |      1 |00:00:00.01 |       5 |
|   2 |   TABLE ACCESS BY INDEX ROWID| TT     |      1 |     16 |     16 |00:00:00.01 |       5 |
|*  3 |    INDEX RANGE SCAN          | IDX_ID |      1 |     16 |     16 |00:00:00.01 |       1 |
-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("ID"<=16)


20 rows selected.

Elapsed: 00:00:00.00

因此可以得出结论,在回表时,根据rowid访问对应块的时候,如果此次访问块和上次不同,则会产生新的逻辑读。

如果一样,则不会产生新的逻辑读(实际上被合并成一次取数据了)。

比如前边的当id=1,2,3,4都在925号块的时候,id<=4回表就只会有一次逻辑读。

因此,一开始前言处的问题应该可以从聚簇因子看出,是因为数据分布(索引列)太过于散乱引起的。

从下边数据看出,聚簇因子CF远大于表T_ABCDEFGG_HIJ_KLMN的块数。

09:23:58 SYS@test1(595)> select CLUSTERING_FACTOR from dba_indexes where owner='ABCDEFG_HIJKLMNO' and index_name='IDX_T_ABCDEFGG_HIJ_KLMN_01';

CLUSTERING_FACTOR
-----------------
           264830

Elapsed: 00:00:00.00
09:25:20 SYS@test1(595)> select blocks from dba_tables where owner='ABCDEFG_HIJKLMNO' and table_name='T_ABCDEFGG_HIJ_KLMN';

    BLOCKS
----------
     40320

Elapsed: 00:00:00.04
原文地址:https://www.cnblogs.com/PiscesCanon/p/13841387.html