索引引发故障的案例(一)

1、感受索引美好的一面

1、索引的各种开销

  • 访问集中导致热块竞争

drop table t purge;
create table t (id number,deal_date date,area_code number,nbr number,contents varchar2(4000));
insert into t(id,deal_date,area_code,nbr,contents)
      select rownum,
             to_date( to_char(sysdate-365,'J')+TRUNC(DBMS_RANDOM.VALUE(0,700)),'J'),
             ceil(dbms_random.value(590,599)),
             ceil(dbms_random.value(18900000001,18999999999)),
             rpad('*',400,'*')
        from dual
      connect by rownum <= 100000;
commit;

create index idx_t_id on t(id);

--由于一般来说,最新的值都是最新产生的,所以访问容易产生热快竞争。
因为
    select * from t where id=100000;
    select * from t where id=99999;
    select * from t where id=99998;
    select * from t where id=99997;
---数据是相邻的,很可能在同一个索引块上,所以很容易产生热点索引块竞争。
    
    
    
--如果方向键索引,刚才的语句等于变成如此查询:    
    select * from t where id=000001;
    select * from t where id=99999;
    select * from t where id=89999;
    select * from t where id=79999;
--他们都被分到很远的地方,不会在同一个索引块上,有效的避免了索引块竞争。
drop index idx_t_id ;
create index idx_t_id on t(id) reverse;

结论:一般来说,由于用户都是访问最新产生的数据的,所以容易产生索引的热点块竞争,
  这也算是索引的开销的弊端了,不过这时可以考虑用方向键索引来将索引的位置转移到不同的地方,只是反向键索引要担心范围查询无法使用,这在案例 的部分会有描述。

  • 回表性能取决于聚合因子

select a.table_name,
       a.index_name,
       a.blevel,
       a.leaf_blocks,
       b.num_rows,
       b.blocks,
       a.clustering_factor,
       trunc(a.clustering_factor / b.num_rows,2) cluster_rate
  from user_indexes a, user_tables b
 where a.table_name = b.table_name
     and index_name in ('COLOCATED_PK', 'DISORGANIZED_PK' )
     and a.clustering_factor is not null
 order by cluster_rate desc;

TABLE_NAME         INDEX_NAME          BLEVEL LEAF_BLOCKS   NUM_ROWS     BLOCKS CLUSTERING_FACTOR CLUSTER_RATE
------------------ -------------------------- ----------- ---------- ---------- ----------------- ------------
DISORGANIZED       DISORGANIZED_PK          1         208     100000       1219             99927          .99
COLOCATED          COLOCATED_PK             1         208     100000       1252              1190          .01

结论:索引查询要尽可能的避免回表,如果不可避免,需要关注聚合因子是否过大。

  • 索引更新方面开销比较大


drop table test1 purge;
drop table test2 purge;
drop table test3 purge;
drop table t purge;
create table t as select * from dba_objects;
create table test1 as select * from t;
create table test2 as select * from t;
create table test3 as select * from t;
create index idx_owner on test1(owner);
create index idx_object_name on test1(object_name);
create index idx_data_obj_id on test1(data_object_id);
create index idx_created on test1(created);
create index idx_last_ddl_time on test1(last_ddl_time);
create index idx_status on test1(status);

create index idx_t2_sta on test2(status);
create index idx_t2_objid on test2(object_id);
set timing on
--语句1(test1表有6个索引)
insert into test1 select * from t;
commit;
--语句2(test2表有2个索引)
insert into test2 select * from t;
commit;
--语句3(test3表有无索引)
insert into test3 select * from t;
commit;

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

一次与出账相关的小故事
drop table t purge;
create table t as select * from dba_objects;
insert into t select * from t;
insert into t select * from t;
commit;
--请从这里开始注意累加的时间(从建索引到插入记录完毕)
set timing on
create index idx_t_owner on t(owner);
create index idx_t_obj_name on t(object_name);
create index idx_t_data_obj_id on t(data_object_id);
create index idx_t_created on t(created);
create index idx_t_last_ddl on t(last_ddl_time);

--语句1(t表有6个索引)
insert into t select * from t;
commit;
 

--以下进行试验2
drop table t purge;
create table t as select * from dba_objects;
insert into t select * from t;
insert into t select * from t;
commit;

---也从这里开始这里开始注意累加的时间(从插入记录完毕到建索引完毕)

set timing on

--语句1(t表有6个索引,此时先不建)
insert into t select * from t;


create index idx_t_owner on t(owner);
create index idx_t_obj_name on t(object_name);
create index idx_t_data_obj_id on t(data_object_id);
create index idx_t_created on t(created);
create index idx_t_last_ddl on t(last_ddl_time);

结论:

  1. 在无索引的情况下,表的记录越大,插入的速度只会受到很小的影响,基本不会越慢。
  2. 在无索引的情况下,分区表的插入要比普通表更慢,因为插入的数据需要做判断落在哪个分区。有这方面的开销。
  3. 在有索引的情况下,表记录越大,索引越多,插入速度越慢。
  4. 在有索引的情况下,如果表非常大,索引也很多,在条件允许下,可以将索引先失效再生效,速度可以更快。
  5. 在有索引的情况下,分区表如果只是有局部索引,一般来说,分区表的插入速度比普通表更快,因为每个分区的分区索引都比较小,更新的开销自然也小。

  • 建立索引的过程开销也较大

  结论:普通的对表建索引将会导致针对该表的更新操作无法进行,需要等待索引建完。更新操作将会被建索引动作阻塞。
        另注:在案例的章节中,大家将会见到ONLINE建索引的方式,
        这种建索引的方式不会阻止针对该表的更新操作,与建普通索引相反的是,ONLINE建索引的动作是反过来被更新操作阻塞

  结论:建索引的过程会产生排序,排序的开销一般比较大,所以要尽量避免在生产缓慢的时候建索引。

set linesize 266
drop table t purge;
create table t as select * from dba_objects;

建立索引前

scott@ORCL>select t1.name,t1.statistic#,t2.value from v$statname t1, v$mystat t2 where t1.statistic#=t2.statistic# and t1.name like '%sort%';

NAME                                                                 STATISTIC#     VALUE
-------------------------------------------------------------------------------------------------------------------------------- ---------- ----------
sorts (memory)                                                                640       361
sorts (disk)                                                                641         0
sorts (rows)                                                                642   11177686

create index idx_object_id on t(object_id);

建立索引后

scott@ORCL>select t1.name,t1.statistic#,t2.value from v$statname t1, v$mystat t2 where t1.statistic#=t2.statistic# and t1.name like '%sort%';

NAME                                                                 STATISTIC#     VALUE
-------------------------------------------------------------------------------------------------------------------------------- ---------- ----------
sorts (memory)                                                                640       362
sorts (disk)                                                                641         0
sorts (rows)                                                                642   11264220

 

 

2、逻辑索引失效

  • 用索引代价反而更高
  • 发生了索引列烈性的转换

案例:

create table t_col_type(id varchar2(20),col2 varchar2(20),col3 varchar2(20));

Table created.

Elapsed: 00:00:00.03
scott@ORCL>insert into t_col_type select rownum,'abc','efg' from dual connect by level<=10000;
commit;

create index idx_id on t_col_type(id);
set linesize 1000

set autotrace traceonly

select * from t_col_type where id=6;(走了全表扫描而没有走索引的执行计划)

Execution Plan
----------------------------------------------------------
Plan hash value: 3191204463

--------------------------------------------------------------------------------
| Id  | Operation      | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |           |     1 |    36 |     9     (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T_COL_TYPE |     1 |    36 |     9     (0)| 00:00:01 |
--------------------------------------------------------------------------------

--实际上只有如下写法才可以用到索引,这个很不应该,是什么类型的取值就设置什么样的字段。
          
select * from t_col_type where id='6';(走索引了没有走全表扫描,走了强制类型转换)

Execution Plan
----------------------------------------------------------
Plan hash value: 3191204463

--------------------------------------------------------------------------------
| Id  | Operation      | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |           |     1 |    36 |     9     (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T_COL_TYPE |     1 |    36 |     9     (0)| 00:00:01 |
--------------------------------------------------------------------------------

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

   1 - filter(TO_NUMBER("ID")=6)

 结论:什么类型就放什么值,否则会发生类型转换,导致系能问题!
(是存放字符的字段就设varchar2类型,是存放数值的字段就设置number类型,是存放日期的字段就设置date类型)
  另外不止是类型转换,如果对列进行了运算,比如substr(列),trunc(列)等等,也会导致用不上索引,具体见案例分析中

  • 对索引进行了各种运算

3、物理索引失效

  • long列调整导致索引失效

drop table t purge;
create table t (object_id number,object_name long);
create index idx_object_id on t(object_id);
insert into t values (1,'ab');
commit;


select t.status,t.index_name from user_indexes t where index_name='IDX_OBJECT_ID';
STATUS   INDEX_NAME
-------- ---------------
VALID    IDX_OBJECT_ID

alter table T modify object_name clob;
set autotrace off

select t.status,t.index_name from user_indexes t where index_name='IDX_OBJECT_ID';
STATUS   INDEX_NAME
-------- --------------
UNUSABLE IDX_OBJECT_ID

 

alter index  idx_object_id rebuild;
set autotrace off

select t.status,t.index_name from user_indexes t where index_name='IDX_OBJECT_ID';
STATUS   INDEX_NAME
-------- ---------------
VALID    IDX_OBJECT_ID

  • move操作导致了索引失效

 结论:很多人想用ALTER TABLE MOVE的方式来降低高水平,结果经常忽略了这个操作会导致索引失效,
 请大家通过下列的试验的回顾,以后多留意这点。
 另外alter table t shrink space; 是否能十全十美呢,请看后面的案例分析
*/

  • 分区表导致索引失效操作

在前面已经描述过了,这里就不再重试验了,请同学们自行回到前面的章节中进行复习总结,只是做一个总结归纳。

1.truncate分区会导致全局索引失效,不会导致局部索引失效。如果truncate 增加update global indexes,全局索引不会失效。
2.drop分区会导致全局索引失效,局部索引因为drop分区,所以也不存在该分区的局部索引了。如果drop分区增加update global indexes,全局索引不会失效。
3.split分区会导致全局索引失效,也会导致局部索引失效。如果split分区增加update global indexes,全局索引不会失效。
4.add 分区不会导致全局索引失效,也不会导致局部索引失效。
5.exchange会导致全局索引失效,不会导致局部索引失效。如果exchange分区增加update global indexes,全局索引不会失效。

重要结论:
1. 所有的全局索引,只要用到update global indexes ,都不会失效,其中add分区甚至不需要增加update global indexes都可以生效。
2. 局部索引的操作都不会失效,除了split分区。切记split分区的时候,要将局部索引进行rebuild;

 

4、索引取舍的控制

  • 避免交叉重复建立索引
  • 删除系统中从没用过的索引

结论:我们可以通过alter index xxx monitoring usage的命令,对具体的索引进行监控,通过查询v$object_usage视图的USED
  字段的取值是YES还是NO,就可以知道该索引是否被使用过,具体如下:
 

  • 组合索引过多的索引很可疑
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值