故归根结底,效率的根源在于I/O的情况,随着查询数据量的增多,通过索引更新的I/O大于了通过遍历的I/O,所以会变慢。
索引改进性能的程度取决于数据的选择性,以及在表的块之间分布数据的方式。如果数据非常具有选择性,则表中的将只有很少的行匹配索引值(例如身份证号码),oracle将能够快速查询匹配的索引值的ROWID的索引,并且可以快速查询少量的相关数据块。如果数据不是非常具有选择性(例如国家名),则索引值可能返回很多rowid,导致从表中查询许多单独的块。
如果数据非常具有选择性,但是相关的行在表中的存储位置并不互相靠近(非聚集索引),则会进一步减少索引的益处。如果匹配索引值的数据分散在表的多个块中,则必须从表中选择多个单独的块以满足查询。在一些情况中,当数据分散在表的多个块中时(其实就是查询大量数据时,比如大于表中总数据的5%),最好是不使用索引,而是执行权表扫描,执行权表扫描时,oracle使用多块读取以快速扫描表。基于索引的读取是单块读取,因此在使用索引时的目标是减少解决查询所需的单个块的数量。
-- 部门表
CREATE TABLE DEPARTMENT (
dept_id NUMBER(16),
dept_name VARCHAR2(32),
primary key(dept_id)
);
-- 员工表
CREATE TABLE STAFF (
user_id NUMBER(16),
user_name VARCHAR2(32),
dept_id NUMBER(16),
primary key(user_id),
foreign key(dept_id) references DEPARTMENT(dept_id)
--另一种写法,可以给这个约束命名,而不使用系统默认名字:constraint FK_DEPT_ID foreign key (dept_id) references DEPARTMENT(dept_id)
--自己命名比较容易管理
);
语法:create index 索引名 on 表名(列名)
-- 添加外键索引
CREATE INDEX INDEX_STAFF_DEPT_ID ON STAFF(DEPT_ID);
-- 部门表2
CREATE TABLE DEPARTMENT2 (
dept_id NUMBER(16),
dept_name VARCHAR2(32),
primary key(dept_id)
);
-- 员工表2
CREATE TABLE STAFF2 (
user_id NUMBER(16),
user_name VARCHAR2(32),
dept_id NUMBER(16),
primary key(user_id),
foreign key(dept_id) references DEPARTMENT(dept_id)
);
-- 创建sequence作为STAFF、STAFF2表的主键user_id数据
CREATE SEQUENCE "WLMEDICAL"."TEST_INDEX_SEQ" MINVALUE 100 MAXVALUE 9999999999999999 INCREMENT BY 1 START WITH 101 CACHE 20 NOORDER NOCYCLE
CREATE SEQUENCE "WLMEDICAL"."TEST_INDEX_SEQ2" MINVALUE 100 MAXVALUE 9999999999999999 INCREMENT BY 1 START WITH 101 CACHE 20 NOORDER NOCYCLE
procedure p_department
IS
cIndex NUMBER;
dName varchar(32);
begin
while cIndex<50000 loop
cIndex:=TEST_DEPARTMENT_SEQ.nextval;
dName:='department'||cIndex;
insert into DEPARTMENT(DEPT_ID, DEPT_ID) values (cIndex, dName);
insert into DEPARTMENT2(DEPT_ID, DEPT_ID) values (cIndex, dName);
end loop;
dbms_output.put_line('department-----over');
commit;
end p_department;
1、
当前表是空表,所以我们如果添加一条数据是看不到什么实际效果的,故我们首先添加大批量数据进行测试,
分四次每次添加30w条数据
为了尽量模拟一致,我们每次添加的数据都是一摸一样的包括sequence等,添加数据存储过程(添加时候注释、解注释一下另一条语句):
procedure p_test
IS
cIndex NUMBER;
uName varchar(20);
depNum NUMBER;
idNum NUMBER;
begin
cIndex:=0;
depNum:=0;
while cIndex<300000 loop
cIndex:=cIndex+1;
depNum:=mod(cIndex,50000);--取余得出 部门id
IF depNum=0 then
depNum:=50000;
END IF;
idNum:=TEST_INDEX_SEQ.NEXTVAL;
uName:='n'||idNum;
insert into STAFF (user_id, user_name, DEPT_ID) values (idNum, uName, depNum);
--insert into STAFF2 (user_id, user_name, DEPT_ID) values (idNum, uName, depNum);
end loop;
dbms_output.put_line('---over');
commit;
end p_test;
2、在表中已存在百万级数据的时候,给STAFF、STAFF2表各添加一条数据
insert into STAFF (user_id, user_name, DEPT_ID) values (1, '花花', 1);
耗时:0.01s
insert into STAFF (user_id, user_name, DEPT_ID) values (2, '小明', 2);
耗时:0.01s
insert into STAFF (user_id, user_name, DEPT_ID) values (3, '小常', 3);
耗时:0.01s
insert into STAFF2 (user_id, user_name, DEPT_ID) values (1, '花花', 1);
耗时:0.01s
insert into STAFF2 (user_id, user_name, DEPT_ID) values (2, '小明', 2);
耗时:0.00s
insert into STAFF2 (user_id, user_name, DEPT_ID) values (3, '小常', 3);
耗时:0.00s
4、再多加两个字段,然后再加一个索引,测试下
给表一添加字段:info_id、info_desc,然后给info_id 添加索引
alter table STAFF add info_id NUMBER(16);
CREATE INDEX INDEX_STAFF_INFO_ID ON STAFF(INFO_ID);
alter table STAFF add info_desc VARCHAR2(2048);
给表一添加字段:info_id、info_desc
alter table STAFF2 add info_id NUMBER(16);
alter table STAFF2 add info_desc VARCHAR2(2048);
把表1、表2的info_id全部赋值为USER_ID以方便测试
update STAFF set info_id=user_id; --耗时 Affected rows : 1901203, Time: 130.06sec
update STAFF2 set info_id=user_id; --耗时 Affected rows : 1901203, Time: 103.01sec
添加一条数据,可以给info_desc一个长点的字符串,篇幅有点长,一共执行了三次这里只贴出来一次
insert into STAFF (user_id, user_name, DEPT_ID, INFO_ID, INFO_DESC) values (10, '花花', 1, 10, '第一个方法就是判断索引中唯一键或不同键的数量。可以对表或索引进行分析来确定不同键的数量。可以查询 视图的 列来研究分析的结果。3、二元高度其实数据查询性能,这次查询系统所进行的I/O次数起到很关键作用。索引有一个二元高度(binary height)特性:索引的二元高度对把ROWID 返回给用户进程时所要求的 I/O 数量起到关键作用。二元高度的每个级别都会增加一个额外的读取块,而且由于这些块不能按顺序读取,它们都会要求一个独立的 I/O 操作。在下图(装作有图的样子,手机相册中)中,我们检索一个二元高度为3的索引,这样会返回一行数据给用户,同时有4个数据块碑读取:3个来自索引,1个来自表。随着索引的二元高度的增加,索引数据所要求的 I/O 数量也会随之增加。在对索引进行分析后,可以通过查询 的 列;一般来说,数据库块尺寸越大,索引的二元高度就越低。二元高度的每个额外级别()在DML操作期间会增加额外的性能成本。4、oracle 索引类型B树索引位图索引hash索引索引组织表索引反转键(reverse key)索引基于函数的索引(本地和全局索引)分区索引位图链接索引4.1 B树索引B树索引,在oracle中是一个通用索引,创建索引时他就是默认的索引类型,即我们创建的normal索引就是这种数据结构。B树索引可以是一个单列索引也可以是复合索引,b树索引最多包含32列。B树索引的查询顺序是:1树根----》2枝干----》3叶子----》4表其中2枝干可能有多级,3叶子节点保存的是值和rowid,得到对应的rowid后根据rowid进行4从表中查询数据。技巧:索引列的值都存储在索引中。因此,可以建立一个复合索引,这些索引可以直接满足查询,而不用访问表。这就不用从表中检索数据,从而减少了 I/O 量。');
耗时:0.01s
insert into STAFF2 (user_id, user_name, DEPT_ID, INFO_ID, INFO_DESC) values (10, '花花', 1, 10, '第一个方法就是判断索引中唯一键或不同键的数量。可以对表或索引进行分析来确定不同键的数量。可以查询 视图的 列来研究分析的结果。3、二元高度其实数据查询性能,这次查询系统所进行的I/O次数起到很关键作用。索引有一个二元高度(binary height)特性:索引的二元高度对把ROWID 返回给用户进程时所要求的 I/O 数量起到关键作用。二元高度的每个级别都会增加一个额外的读取块,而且由于这些块不能按顺序读取,它们都会要求一个独立的 I/O 操作。在下图(装作有图的样子,手机相册中)中,我们检索一个二元高度为3的索引,这样会返回一行数据给用户,同时有4个数据块碑读取:3个来自索引,1个来自表。随着索引的二元高度的增加,索引数据所要求的 I/O 数量也会随之增加。在对索引进行分析后,可以通过查询 的 列;一般来说,数据库块尺寸越大,索引的二元高度就越低。二元高度的每个额外级别()在DML操作期间会增加额外的性能成本。4、oracle 索引类型B树索引位图索引hash索引索引组织表索引反转键(reverse key)索引基于函数的索引(本地和全局索引)分区索引位图链接索引4.1 B树索引B树索引,在oracle中是一个通用索引,创建索引时他就是默认的索引类型,即我们创建的normal索引就是这种数据结构。B树索引可以是一个单列索引也可以是复合索引,b树索引最多包含32列。B树索引的查询顺序是:1树根----》2枝干----》3叶子----》4表其中2枝干可能有多级,3叶子节点保存的是值和rowid,得到对应的rowid后根据rowid进行4从表中查询数据。技巧:索引列的值都存储在索引中。因此,可以建立一个复合索引,这些索引可以直接满足查询,而不用访问表。这就不用从表中检索数据,从而减少了 I/O 量。');
耗时:0.01s
1、先更新一条数据
update STAFF SET info_desc='呵呵' where info_id=1800001;
耗时 0.01s
update STAFF SET info_desc='呵呵' where info_id=1800002;
耗时 0.01s
update STAFF SET info_desc='呵呵' where info_id=1800003;
耗时 0.01s
update STAFF2 SET info_desc='呵呵' where info_id=1800001;
耗时 0.09s
update STAFF2 SET info_desc='呵呵' where info_id=1800002;
耗时 0.08s
update STAFF2 SET info_desc='呵呵' where info_id=1800003;
耗时 0.09s
可以看出,如果更新一条数据,条件列有索引会比没有索引的快很多。
update STAFF SET info_desc='呵呵' where info_id<1000000;
55.02s
update STAFF SET info_desc='呵呵' where info_id>1000000;
43.90sec
update STAFF2 SET info_desc='呵呵' where info_id<1000000;
42.34s
update STAFF2 SET info_desc='呵呵' where info_id>1000000;
37.63sec
可以得出,当前表中共190w数据,更新过半数据时,条件列有索引会比没有索引的慢很多。
update STAFF SET info_desc='呵呵' where info_id<190000;
Elapsed: 00:00:07.42
update STAFF SET info_desc='呵呵' where info_id<190000;
Elapsed: 00:00:05.26
update STAFF SET info_desc='呵呵' where info_id<190000;
Elapsed: 00:00:07.89
update STAFF2 SET info_desc='呵呵' where info_id<190000;
Elapsed: 00:00:04.18
update STAFF2 SET info_desc='呵呵' where info_id<190000;
Elapsed: 00:00:04.77
update STAFF2 SET info_desc='呵呵' where info_id<190000;
Elapsed: 00:00:06.21
可以得出,当前表中共190w数据,更新10%数据时,条件列有索引仍然会比没有索引的慢很多。
4、更新索引列
这个其实刚才添加测试数据的时候已经测试过了,
"
把表1、表2的info_id全部赋值为USER_ID以方便测试
update STAFF set info_id=user_id; --耗时 Affected rows : 1901203, Time: 130.06sec
update STAFF2 set info_id=user_id; --耗时 Affected rows : 1901203, Time: 103.01sec
"
更新索引列是会变慢的。
1、查询一条数据,效率增加非常多
select * from STAFF where info_id=1800001;
0.01s
select * from STAFF2 where info_id=1800001;
0.09s
2、查询大量数据,where子句中的列含有索引会比不含索引的还要慢
select * from STAFF where info_id<300000;
4.29s 4.08s 4.10s 4.07s
select * from STAFF2 where info_id<300000;
3.98s 3.81s 3.85s 3.82s
DELETE from STAFF where info_id=1;
0.01
DELETE from STAFF2 where info_id=1;
0.12
删除和更新规律差不多,删除数据少的时候,有索引会非常快。删除数据量大的时候有索引的就会变慢,因为删除索引也是一部分I/O和引起索引节点裂变排序。