Oracle索引原理及应用实践

686 阅读7分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情

📢📢📢📣📣📣
哈喽!大家好,我是【IT邦德】,江湖人称jeames007,10年DBA工作经验
中国DBA联盟(ACDU)成员,目前从事DBA及程序编程😜😜😜
擅长Oracle、MySQL、PG 运维开发,备份恢复,安装迁移,性能优化、故障应急处理等。
❤️❤️❤️感谢各位大可爱小可爱!❤️❤️❤️

摘要:本人透彻的讲解了Oracle数据库的索引原理及应用场景

1.概念

    对于OLTP系统而言,其操作特点是每次操作,
几乎都是访问和处理极少的数据,比如访问某一张订单,查看某一位客户的资料等。如果通过在检索条件的相关列上,
在选择性较好的列或列的组合上创建索引,
就可以利用索引快迅定位相关记录。
这要比从头至尾扫描整张表的资源开销小得多,
效率也自然高得多,执行时长也会短得多。
  此外,由于索引只是由一列或少数几列构成,
其相比于表中十几,甚至几十上百列而言,其体积要小得多。
如果所需访问的列(检索条件列和返回的列)均在索引上,
则可以避免对相对大的表的访问,而只需要访问体积小得多的索引。
这也会带来访问开销的降低,从而提升SQL执行效率的效果。

2.索引分类

2.1 B- tree 索引

索引页块中存储键值和 rowid,
常用于 OLTP 系统,
针对基数比较高(high cardinality)的列 (重复值较少)
2.1.1 查看 rowid
alter user scott identified by tiger account unlock;  --解锁用户
select rowid,
dbms_rowid.rowid_object(rowid) object#,
dbms_rowid.rowid_relative_fno(rowid) datafile#,
dbms_rowid.rowid_block_number(rowid) block#,
dbms_rowid.rowid_row_number(rowid) row#,
empno,ename from emp;

2.1.2 创建测试表 test1 、索
SCOTT@PROD> create table test1 as select * from emp;
SCOTT@PROD> create index test_idx1 on test1 (ename);
SCOTT@PROD> explain plan for select * from test1 where ename ='SCOTT';
SCOTT@PROD> @?/rdbms/admin/utlxplp.sql

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 3447293396
-----------------------------------------------------------------------------------------
| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |           |     1 |    87 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST1     |     1 |    87 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | TEST_IDX1 |     1 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

select * from table(dbms_xplan.display());
SCOTT@PROD> drop index test_idx1;  --删除索引

2.2 位图索引

一个键值对应很多行(rowid), 
格式:键值 start_rowid end_rowid 位图,
索引页块中通过位图的 01 标识键值和表中行的关系,
页块中存储起始 rowid 和结束,
rowid,占用空间比较少,针对基数比较低的列(low cardinality),
DML 操作锁定索引 entry,更新代价比较高,
适合只读表或 OLAP/DSS 系统 (never updated)
2.2.1 test1 创建位图索引
SCOTT@PROD> create bitmap index test_idx1 on test1(job);
SCOTT@PROD> explain plan for select * from test1 where job='CLERK';
SCOTT@PROD> select * from table(dbms_xplan.display());
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 2884149098
------------------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |           |     4 |   348 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID | TEST1     |     4 |   348 |     1   (0)| 00:00:01 |
|   2 |   BITMAP CONVERSION TO ROWIDS|           |       |       |            |          |
|*  3 |    BITMAP INDEX SINGLE VALUE | TEST_IDX1 |       |       |            |          |
------------------------------------------------------------------------------------------

2.3 函数索引

基于表达式或函数包括的列创建索引,它将一个函数计算得到的结果存贮在索引中
2.3.1 创建测试表、普通索引
SCOTT@PROD> create table test as select empno,initcap(ename) ename,job from emp;
SCOTT@PROD> select * from test;

2.3.2 创建普通索引 查看执行计划
SCOTT@PROD> create index ind_test_ename on test(ename);
SCOTT@PROD> explain plan for select * from test where ename='Scott';
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 418585065
----------------------------------------------------------------------------------------------
| Id  | Operation                   | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                |     1 |    26 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST           |     1 |    26 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IND_TEST_ENAME |     1 |       |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
数据库中存储的数据大小写敏感,应用程序已经将用户输入的数据转换为大写
SCOTT@PROD> explain plan for select * from test where upper(ename)='SCOTT';
SCOTT@PROD> select * from table(dbms_xplan.display());
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 1357081020
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    26 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TEST |     1 |    26 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------

2.4 反向索引

 将正常的键值头尾调换 后再进行存储,RAC 环境中,
 如果索引列通过序列产生,
 并发 insert 操作时容易产生索引热块(index hot spots)
buffer busy wait,
将字节倒置后组织键值,可以防止叶节点出现热块现象,
反向索引不支持索引范围扫描(index range scan)

2.4.1 创建测试表、序列
SCOTT@PROD> create table test (id number,name varchar2(20));
SCOTT@PROD> create sequence seq1 start with 1 increment by 1;

2.4.2 插入数据
begin
for i in 1..10 loop
insert into test values (seq1.nextval,'OCM');
end loop;
commit;
end;
/

2.4.3 创建反向索引
SCOTT@PROD> create index ind_test_id on test(id) reverse;
SCOTT@PROD> select index_name,index_type from user_indexes where table_name='TEST';
INDEX_NAME                     INDEX_TYPE
------------------------------ ---------------------------
IND_TEST_ID                    NORMAL/REV

2.4.4 查询执行计划
SCOTT@PROD> explain plan for select * from test where id=2;
SCOTT@PROD> select * from table(dbms_xplan.display());
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 1064545891
-------------------------------------------------------------------------------------------
| Id  | Operation                   | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |             |     1 |    25 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST        |     1 |    25 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IND_TEST_ID |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

SCOTT@PROD> explain plan for select * from test where id<2;
SCOTT@PROD> select * from table(dbms_xplan.display());
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 1357081020
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    25 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TEST |     1 |    25 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------

2.5 全局哈希分区索引

分区索引:
local :针对分区表,索引分区与表分区对应。
global:可以是分区表或非分区表,整张表只有一个索引,索引分区与表分区没有直接对应关系。
全局哈希分区索引 (hash-partitioned global index)
通过 hash 运算将键值分散到每一个分区;
解决索引热块冲突,支持 index range sca

2.5.1 创建全局哈希分区索引
SCOTT@PROD> create index ind_test_id on test(id) global partition by hash(id) partitions 4;
SCOTT@PROD> select segment_name,segment_type,partition_name from user_segments where segment_name='IND_TEST_ID';
SEGMENT_NAME         SEGMENT_TYPE       PARTITION_NAME
-------------------- ------------------ ------------------------------
IND_TEST_ID          INDEX PARTITION    SYS_P101
IND_TEST_ID          INDEX PARTITION    SYS_P102
IND_TEST_ID          INDEX PARTITION    SYS_P103
IND_TEST_ID          INDEX PARTITION    SYS_P104

2.5.2 批量插入数据
begin
for i in 1..1000 loop
insert into test values (seq1.nextval,'OCM');
end loop;
commit;
end;
/

2.5.3 查看执行计划
SCOTT@PROD> explain plan for select * from test where id<2;
SCOTT@PROD> select * from table(dbms_xplan.display());
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------
Plan hash value: 3148664816
------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name        | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |             |     1 |    25 |     2   (0)| 00:00:01 |       |       |
|   1 |  PARTITION HASH ALL          |             |     1 |    25 |     2   (0)| 00:00:01 |     1 |     4 |
|   2 |   TABLE ACCESS BY INDEX ROWID| TEST        |     1 |    25 |     2   (0)| 00:00:01 |       |       |
|*  3 |    INDEX RANGE SCAN          | IND_TEST_ID |     1 |       |     1   (0)| 00:00:01 |     1 |     4 |
------------------------------------------------------------------------------------------------------------

SCOTT@PROD> drop table test purge;

2.6 复合压缩索引

对多个字段的组合创建索引,可以对索引字段进行压缩,
减少空间占用(use less space)
组合索引的第一个字段称为先导列(leading column),
将 where 子句中常用的、选择性好的列作为先导列
单个列选择性低,使用复合索引可以提高选择性
查询访问的复合索引列,可以只访问索引,减少物理 I/O
2.6.1 创建测试表
SCOTT@PROD> create table test as select * from emp;

2.6.2 创建复合压缩索引
SCOTT@PROD> create index ind_test on test(job,deptno) compress 1;  --对第一个列进行压缩
SCOTT@PROD> select index_name,index_type,COMPRESSION from user_indexes where table_name='TEST';
INDEX_NAME                     INDEX_TYPE                  COMPRESS
------------------------------ --------------------------- --------
IND_TEST                       NORMAL                      ENABLED
SCOTT@PROD> select index_name,COLUMN_NAME,COLUMN_POSITION from user_ind_columns where table_name='TEST';
INDEX_NAME                     COLUMN_NAM COLUMN_POSITION
------------------------------ ---------- ---------------
IND_TEST                       JOB                      1
IND_TEST                       DEPTNO                   2

2.6.3 查看执行计划
SCOTT@PROD> explain plan for select * from test where job='CLERK' and deptno=10;
SCOTT@PROD> select * from table(dbms_xplan.display());
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------
Plan hash value: 3856466897
----------------------------------------------------------------------------------------
| Id  | Operation                   | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |          |     1 |    87 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST     |     1 |    87 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IND_TEST |     1 |       |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

相关推荐阅读: MySQL8.0新特性抢先看,性能又双叒提升了
MySQL武林秘籍,SQL学废必过考试
Linux7.6源码安装Mysql8
Oracle巡检脚本大全,服务器可直接部署
MySQL root密码忘记找回妙招
监控神器Zabbix,从部署到应用
Oracle监听日志清除

大家可以点赞、收藏、关注、评论我啦 、有数据库相关的问题随时联系我或交流哟~!

image.png