一,没有优化的速度:Executed in 69.436 seconds
drop table t purge;
create table t(x int);
/*清空共享池,注意在生产环境中千万不能做这步操作*/
alter system flush shared_pool;
create or replace procedure proc1
as
begin
for i in 1 .. 100000
loop
execute immediate
'insert into t values('||i||')';
commit;
end loop;
end;
/
下面查看下proc1插入100000记录的执行时间
SQL> set timing on;
SQL> exec proc1;
PL/SQL procedure successfully completed
Executed in 69.436 seconds
/*我们可以通过下面的语句查看此存储过程执行的具体步骤*/
select t.sql_text,t.sql_id,t.parse_calls,t.executions from v$sql t where sql_text like '%insert into t values%';
为了方便查看我用PL/SQL DEVELOPER 执行的上面语句,如下图:
从上面可以看出,每个语句都只是解析了一次,执行了一次,一共解析了10万次,也许你会问你上面只有7136行记录啊,你怎么说是解析了10万次呢。我可以告诉你肯定是解析了10万次,因为我的共享池空间不大,容纳不小10万条信息,根据FIFO 的原理你可以看出,现在我查出来的都是从92000多开始的SQL STATEMENT记录。我们知道这些SQL语句都是相似的没有必要解析10万次,即每一条语句都解析一次。这个PROC1 没有用绑定变量,这就是我们可以优化的地方。我们用绑定变量来重新测试下,下面的PROC2就只用解析一次就可以了,当然速度肯定会提高不少。
二,使用绑定变量优化后的速度:Executed in 26.505 seconds
drop table t purge;
create table t(x int);
/*清空共享池,注意在生产环境中千万不能做这步操作*/
alter system flush shared_pool;
create or replace procedure proc2
as
begin
for i in 1 .. 100000
loop
execute immediate
'insert into t values(:x)' using i;
commit;
end loop;
end;
/
SQL> set timing on;
SQL> exec proc2;
PL/SQL procedure successfully completed
Executed in 26.505 seconds
从上面可以看出,时间基本上减少了一半。
/*我们可以通过下面的语句查看此存储过程执行的具体步骤*/
select t.sql_text,t.sql_id,t.parse_calls,t.executions from v$sql t where sql_text like '%insert into t values%' order by 1;
从上面的执行情况可以知道,解析了一次,执行了10万次。完全符合我们的猜想,所以速度大大提升了。
execute immediate是一种动态SQL的写法,常用于表名字段名是变量,入参的情况,由于表名不知道,所以不能直接写SQL ,所以要靠动态SQL语句传人表名和字段名参数拼接成SQLSTATEMENT,有execute immediate调用执行。但是我的这个例子完全可以不需要动态的,可以用静态的写好。
三,用静态改写后的速度:Executed in 19.391 seconds
drop table t purge;
create table t(x int);
/*清空共享池,注意在生产环境中千万不能做这步操作*/
alter system flush shared_pool;
create or replace procedure proc3
as
begin
for i in 1 .. 100000
loop
insert into t values(i);
commit;
end loop;
end;
/
SQL> set timing on;
SQL> exec proc3;
PL/SQL procedure successfully completed
Executed in 19.391 seconds
从上面可以看出,proc3也实现了绑定变量,而且动态的特点是执行过程中再解析,而静态的SQL的特点是编译的过程是解析好的,所以上面的PRARSE_CALLS是0。注意这个和上面一个图比较,上面的时PARSE_CALLS 是1,而这个是0,所以静态的少了一个执行的时候的解析过程。
我们可以看出上面的三个PROC都是一条语句就commit一次,我们完全没有必要这样做,我们可以一起提交。如下例: commit的时把log_buffer里的信息通过LGWR写到online redo log里,触发LGWR写10万次,而且我们知道LGWR写的太频繁了。
四,批量提交的速度:Executed in 11.42 seconds
drop table t purge;
create table t(x int);
/*清空共享池,注意在生产环境中千万不能做这步操作*/
alter system flush shared_pool;
create or replace procedure proc4
as
begin
for i in 1 .. 100000
loop
insert into t values(i);
end loop;
commit;
end;
/
SQL> set timing on;
SQL> exec proc4;
PL/SQL procedure successfully completed
Executed in 11.42 seconds
可以看出我们用的时间更少了。
五,集合写法的速度:Executed in 0.452 seconds
drop table t purge;
create table t(x int);
/*清空共享池,注意在生产环境中千万不能做这步操作*/
alter system flush shared_pool;
/*下面的语句是由上面的一条一条的插入改为一整批的写进data buffer区里,所以比上面的快,批处理肯定比一个一个的执行快*/
insert into t select rownum from dual connect by level<=100000;
SQL> set timing on;
SQL> insert into t select rownum from dual connect by level<=100000;
100000 rows inserted
Executed in 0.452 seconds
这个是上面的前四种都是一条一条的插入的,我这个集合写法是一整批地写进到DATA BUFFER里,所以比上面的四种情况要快的多。
六,用直接路径写法速度(100万条记录):Executed in 1.514 seconds
/*下面用直接路径的方式来操作,速度会比上面更快,所谓直接路径就是数据不经过database buffer,而是直接写到磁盘,少了一步写到数据缓冲区(database buffer)的动作*/
drop table t purge;
alter system flush shared_pool;
SQL> set timing on;
SQL> create table t as select rownum x from dual connect by level<=1000000;
Table created
Executed in 1.514 seconds
注意此时我插入的记录数十上面的10倍,我是插入100万条记录只用了1.514 seconds.
注意:直接路径的写法比集合写法快事因为,insert into select .... 的方式是将数据首先写到data buffer里,然后再刷到磁盘里。而create as t 的方式跳过了数据缓冲区(data buffer), 直接写进磁盘中,这种方式称之为直接路径读写方式。本来是先到内存,在到磁盘,更改为直接到磁盘,少了一个步骤,所以速度快了。
七,并行写法的速度(100万条记录):Executed in 0.733 seconds
/*并行加直接路径,而且是不写日志的,所以速度比上面的更快*/
drop table t purge;
alter system flush shared_pool;
set timing on;
create table t nologging parallel 64 as select rownum x from dual connect by level<=100000;
SQL> set timing on;
SQL> create table t nologging parallel 4 as select rownum x from dual connect by level<=1000000;
Table created
Executed in 0.733 seconds
我上面只用了parallel 4,如果更多的话,还会更快!!!
[z]
三个提高Oracle处理大量数据效率的有效途径
Oracle性能话题涉及面非常广,市场上有很多书籍专门介绍Oracle调优。对性能的追求是无止境的,需要长期不懈的努力;但要避免性能成为问题却不难,甚至可以说很简单。本文从简单实用的角度出发,给出几个提高Oracle处理大量数据效率的有效途径。
一、配置数据库初始化参数
数据库的全部初始化参数可以在OEM中看到。参见下图:
在新建一数据库时,如果不配置这些初始化参数,Oracle会给这些参数以默认值。当数据库规模不大时,采用Oracle的默认值通常不会遇到性能问题。下面介绍对处理大量数据效率有“举足轻重”影响,并且,默认值会带来性能问题的几个参数:
1) db_block_size
该参数设置了Oracle进行一次I/O的基本单位——数据库块的大小 (以字节计)。毫不夸张的说,该参数对于大数据量处理是最重要的一个参数。该参数值设置的越大,对大数据量处理越有利。受操作系统所限,NT4最大只能设置为8K,Win2k最大只能设置为16K。Oracle本身允许的最大值是64K。忘了说了,db_block_size应设置为2的幂。
对于分析型数据库,设置为32K是不错的选择。对于OLTP系统,笔者没有多少经验,网上的说法是设置8K是个比较好的平衡点。Oracle是有许多“神话”的,8K的说法未必符合现在的软硬件情况,更未必符合我们企业的实际情况。如果有时间有机会,比较一下8K好还是16K好总是不会错的。
db_block_size是最基本的一个参数,也是最容易被忽视的一个参数。该参数只能在创建数据库时设置,此后不能更改;一旦有所失误,只能通过重建数据库的方法补救。因此,您建库时应当慎重考虑该参数。
2)
db_file_multiblock_read_count
Oracle官方的说明:在涉及一个完全连续扫描的一次 I/O 操作过程中读取的块的最大数量。对于大的查询来说,进行全表扫描往往比使用索引效率高很多。全表扫描操作是典型的“完全连续扫描”。如果db_block_size设置为32K,db_file_multiblock_read_count设置为8;则一次I/O操作最多可以连续读8个数据库块,即256K。
db_file_multiblock_read_count并非越大越好。对于数据分析系统,db_file_multiblock_read_count和db_block_size的乘积为256K足够了。对于建立在Unix上的OLTP系统,根据网上的说法,二者的乘积为64K是不错的选择。
根据笔者的经验,让数据连续分布在物理磁盘上比考量该参数更加有效。
3) sort_area_size
sort_area_size的重要性可以说是和db_block_size并列的。该参数指出数据库执行一个查询时最多可以使用多大内存来排序。受系统资源所限,我们无法将该参数设置太大。特别是当我们采用独立模式建库时,每个Session都可能会申请一个或多个排序空间。如果我们设置sort_area_size为8M,同时登上来100个用户并发查询,则可能会占去800M内存甚至更多。当主存不够用时,就要用虚拟内存了。如果Oracle*使用虚拟内存,则数据库的性能将急剧下降。
对于该参数的设置,网上有人建议至少应超过用于排序记录数的平方根。也就是说,对100万条记录进行排序,每条记录占用1K空间,则sort_area_size至少应设置为1M。对1000万条记录进行排序,每条记录占用1K空间,sort_area_size设置为4M应该够用了。
根据上述数字,OLTP系统的sort_area_size不妨设置为1M或2M;数据分析系统的sort_area_size不妨设置为4M或8M。
db_file_multiblock_read_count和sort_area_size在数据库建立好以后是可以修改的。修改方法很简单。搜索Oracle的安装目录,找到PFILE文件夹(可能会找到多个,其父目录的名字会给我们提示),里面有一个init文本文件,照着里面的内容修改就可以了(找不到相关参数就自己加一个)。修改完毕后重启数据库方能生效。Oracle9i以上版本可以做到不用重启数据库,本文就不介绍了。
二、编写高效的SQL
一般说来,看起来简单的SQL通常都不会遇到性能问题。SQL的执行效率通常比程序的执行效率要高。因此,尽量用SQL解决问题和尽量用简单的SQL解决问题应当是我们开发的指导原则。
编写高效的SQL需要一定的基本功。本文不讨论SQL的理论基础。本文仅介绍一个有用的技术:人为干预SQL的执行计划。当SQL较复杂时,执行计划的可能性会非常多。用最短的时间选择一个最优的执行计划是Oracle奋斗的目标。Oracle数据库有相关的参数来调整挑选执行计划的算法,这些参数本文不讨论,有兴趣的读者可以自己上网去搜。
所谓“人为干预SQL的执行计划”实际上是提示Oracle如何去挑选最优的执行计划。SQL提示的语法很简单:用“/*+”和“*/”将提示包括起来,中间写上关键字就可以了。SQL:提示的关键字有很多,下面介绍几个典型的关键字,更多的用法可以自己上网搜。
1)指定全表扫描:
SELECT /*+FULL(table_name)*/ field1,field2
FROM table_name;
一个大查询如果用到了一个大表中相当一部分的数据,则采用全表扫描的执行计划会比采用索引的执行计划效率高很多。
2)数据直接插入到表的最后,可以提高速度:
INSERT /*+append*/
INTO table_name
select * from table_name1;
Oracle中很多数据块因为曾经做过delete操作而有空闲空间,如果使用append关键字,则Oracle不会去寻找这些有空闲空间的数据块,从而提高了insert语句的执行速度。需注意append关键字只适合于大数据量插入。
三、分区
分区技术相对前面介绍的技术而言要复杂一些。分区实际上是据根据某(些)个字段在物理上将一个大表的数据分开存储,从而,能提高我们查询的效率,同时也能加强我们对数据的管理。典型例子的是根据日期字段分区,从而,当我们查询某个时期的数据时,只需要扫描某个分区的数据而不需要扫描整表的数据。
当我们决定采用分区技术时,只需要在create table语句以及create index语句中增加一些语法。一般的面向DBA的书籍都会有专门的章节介绍分区技术。这里不再赘述。
一个表是否被分区并不影响我们使用:对普通表的操作可以用在分区表上。相反,分区表增加了我们使用该表的灵活性,创建分区表后,我们可以使用Alter table命令来增加、删除、交换、移动、修改、重命名、划分、截断一个已存在分区的结构。一个典型的例子,如果用月份分区,我们可以使用truncate命令在瞬间删除某个月份的全部数据。
我们需要注意的是在创建局部唯一索引时,索引字段应包括分区字段,否则会创建失败。创建全局索引则没有这样的限制。