有索引却走全表扫描的实验分析

最近在查询某范围的数据时,返回结果仅占整个表数据的0.05%,在相应字段上有对应索引,然而这部分数据并没有走索引,
将根据如下实验证明此种情况的原因
一 构建环境
1建立测试表 有两个字段,主键ID和手机号,手机号上有B树索引
drop table TEST_HIS
-- Create table
create table TEST_HIS
(
  id    number,
  phone varchar2(32)
)
;
-- Add comments to the table
comment on table TEST_HIS
  is '测试直方图';
-- Add comments to the columns
comment on column TEST_HIS.phone
  is '手机号,用于测试上面的直方图是否影响走索引';
2 建立主键和索引
-- Create/Recreate indexes
create index IDX_PHONE on TEST_HIS (phone);
-- Create/Recreate primary, unique and foreign key constraints
alter table TEST_HIS
  add constraint PK_ID primary key (ID);
3 构造测试数据,根据如下脚本,可以手工修改循环次数,向表中插入13开头的电话号码11000个,15开头的电话号码10个,
以及18开头的电话号码10个,null的空号码11010个 
declare
 v_phone varchar2(32);
begin
 v_phone :='136818978';
 for i in 1..10000
 loop
  insert into test_his
    (id, phone)
  values
    (SEQ_TEST_HIST.NEXTVAL, v_phone||i);
    --(SEQ_TEST_HIST.NEXTVAL,null);
 end loop;
end;
4 收集统计信息
SELECT t.NUM_ROWS,t.AVG_ROW_LEN,t.BLOCKS,t.TABLE_NAME,T.LAST_ANALYZED,t.EMPTY_BLOCKS FROM USER_TABLES T WHERE TABLE_NAME ='TEST_HIS';

begin
  dbms_stats.gather_table_stats(ownname => 'TEST',tabname => 'TEST_HIS',cascade => TRUE);
end;

SELECT t.NUM_ROWS,t.AVG_ROW_LEN,t.BLOCKS,t.TABLE_NAME,T.LAST_ANALYZED,t.EMPTY_BLOCKS FROM USER_TABLES T WHERE TABLE_NAME ='TEST_HIS';

二 查找13开头和15开头的电话号码的sql执行计划

select  * from test.test_his H where h.phone like '13%';
执行计划
----------------------------------------------------------
Plan hash value: 2828532586

------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |  1754 | 17540 |    14   (8)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TEST_HIS |  1754 | 17540 |    14   (8)| 00:00:01 |
------------------------------------------------------------------------------

select  * from test.test_his H where h.phone like '15%';
执行计划
----------------------------------------------------------
Plan hash value: 2828532586

------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |  2227 | 22270 |    14   (8)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TEST_HIS |  2227 | 22270 |    14   (8)| 00:00:01 |
------------------------------------------------------------------------------
根据执行计划我们看到查询15开头的数据返回的rows是2227,这和实际数据相差较大,于是打开10053查看相关信息
三 查看10053
alter system flush buffer_cache;
ALTER SESSION SET EVENTS '10053 trace name context forever, level 1';
explain plan for select * from test.test_his where phone like '15%';
alter session set events  '10053 trace name context off';
观察10053中针对表的访问路径信息
SINGLE TABLE ACCESS PATH
  Column (#2): PHONE(VARCHAR2)
    AvgLen: 6.00 NDV: 10867 Nulls: 11107 Density: 9.2022e-005
  Table: TEST_HIS  Alias: TEST_HIS    
    Card: Original: 22235  Rounded: 2227  Computed: 2226.62  Non Adjusted: 2226.62
  Access Path: TableScan
    Cost:  14.09  Resp: 14.09  Degree: 0
      Cost_io: 13.00  Cost_cpu: 6359522
      Resp_io: 13.00  Resp_cpu: 6359522
kkofmx: index filter:"TEST_HIS"."PHONE" LIKE '15%'
  Access Path: index (RangeScan)
    Index: IDX_PHONE
    resc_io: 604.00  resc_cpu: 5125340
    ix_sel: 0.20203  ix_sel_with_filters: 0.20203
    Cost: 604.88  Resp: 604.88  Degree: 1
  Best:: AccessPath: TableScan
         Cost: 14.09  Degree: 1  Resp: 14.09  Card: 2226.62  Bytes: 0
TEST_HIS中 NDV: 高达10867个,然而sql中要查询的是以15开头的电话号码,15开头,13开头,18开头的数据只有三种
因此统计信息和实际sql要查询的语义也不相符,因此虽然phone上有索引,但是sql的查询条件是查询某号码开头的数据时,已有索引
就不能准确描述15开头的数据分布情况,因此即便15开头的数据只有10个,优化器也会按照普通索引上的统计信息NDV的值计算
返回rows,而这个返回rows远超过实际数据的分布情况,因此建立直方图收集正确的分布信息
四 建立直方图
analyze table test.test_his compute statistics for columns phone;

alter system flush buffer_cache;
ALTER SESSION SET EVENTS '10053 trace name context forever, level 1';
explain plan for select * from test.test_his where phone like '15%';
alter session set events  '10053 trace name context off';

SINGLE TABLE ACCESS PATH
  Column (#2): PHONE(VARCHAR2)
    AvgLen: 5.00 NDV: 10021 Nulls: 11010 Density: 1.0720e-004
    Histogram: HtBal  #Bkts: 75  UncompBkts: 75  EndPtVals: 76
  Table: TEST_HIS  Alias: TEST_HIS    
    Card: Original: 22235  Rounded: 30  Computed: 29.93  Non Adjusted: 29.93
  Access Path: TableScan
    Cost:  14.09  Resp: 14.09  Degree: 0
      Cost_io: 13.00  Cost_cpu: 6359522
      Resp_io: 13.00  Resp_cpu: 6359522
kkofmx: index filter:"TEST_HIS"."PHONE" LIKE '15%'
  Access Path: index (RangeScan)
    Index: IDX_PHONE
    resc_io: 10.00  resc_cpu: 82314
    ix_sel: 0.002716  ix_sel_with_filters: 0.002716
    Cost: 10.01  Resp: 10.01  Degree: 1
******** Begin index join costing ********
  ****** trying bitmap/domain indexes ******
  Access Path: index (IndexOnly)
    Index: IDX_PHONE
    resc_io: 2.00  resc_cpu: 20243
    ix_sel: 0.002716  ix_sel_with_filters: 0.002716
    Cost: 2.00  Resp: 2.00  Degree: 0
    SORT resource      Sort statistics
      Sort width:         477 Area size:      417792 Max Area size:    83676160
      Degree:               1
      Blocks to Sort:       1 Row size:           21 Total Rows:             30
      Initial runs:         1 Merge passes:        0 IO Cost / pass:          0
      Total IO sort cost: 0      Total CPU sort cost: 5826332
      Total Temp space used: 0
  Access Path: index (FullScan)
    Index: PK_ID
    resc_io: 43.00  resc_cpu: 4712422
    ix_sel: 1  ix_sel_with_filters: 1
    Cost: 43.81  Resp: 43.81  Degree: 0
******** Cost index join ********
Index join: Considering index join to index IDX_PHONE
Index join: Joining index PK_ID
Ix HA Join
  Outer table:
    resc: 2.00  card 29.93  bytes: 15  deg: 1  resp: 2.00
  Inner table:
    resc: 54.76  card: 22235.00  bytes: 15  deg: 1  resp: 54.76
    using dmeth: 2  #groups: 1
    Cost per ptn: 0.88  #ptns: 1
    hash_area: 0 (max=0)   Hash join: Resc: 57.65  Resp: 57.65  [multiMatchCost=0.00]
******** Index join cost ********
Cost: 57.65 
******** End index join costing ********
  Best:: AccessPath: IndexRange  Index: IDX_PHONE
         Cost: 10.01  Degree: 1  Resp: 10.01  Card: 29.93  Bytes: 0

查看对应执行计划
---------

| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| T
ime     |

--------------------------------------------------------------------------------
---------

|   0 | SELECT STATEMENT            |           |    30 |   300 |    10   (0)| 0
0:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID| TEST_HIS  |    30 |   300 |    10   (0)| 0
0:00:01 |

|*  2 |   INDEX RANGE SCAN          | IDX_PHONE |    30 |       |     2   (0)| 0
0:00:01 |

--------------------------------------------------------------------------------

 

上一篇:oracle数据库连接后,hang机一例


下一篇:数据仓库-按周的累加和统计月数据