在以前我们处理大数据量的时候,一般机器硬件都足够强悍,单线程的处理怎么也消耗不了多少的系统资源,而整个处理的过程又比较慢,这个时候并行就出现了。 并行可以通过ORACLE的PARALLEL来指定和执行,而在11G R2版本中,这个功能得到了加强,ORACLE推出了一个dbms_parallel_execute包,专门来处理这些内容。这个包其实原理也比较简 单:首先是把数据分成一个个CHUNK,然后指定并行度来同时执行这些任务。就类似手工的按照主键、ROWID或者数据的日期等等进行并行处理类似。下面 来看看这个咚咚怎么使用[@more@]
使用dbms_parallel_execute一般分为3个步骤:创建一个TASK;然后创建CHUNK把数据进行分批;最后是执行这个TASK。下面来做一个演示,演示更新一个表的过程:
1、首先创建一个TASK,名称为update sql:
exec dbms_parallel_execute.create_task('update sql');
这时可以通过视图来查看任务的创建情况:
SQL> select task_name,chunk_type,status from dba_parallel_execute_tasks;
TASK_NAME CHUNK_TYPE STATUS
--------------------------------------- ------------ -------------------
update sql UNDELARED CREATED
说明CHUNK还没有创建,所以CHUNK TYPE未知;状态是刚创建完成状态
2、把将要更新的表按照ROWID进行分批,分到各个CHUNK中:
exec dbms_parallel_execute.create_chunks_by_rowid(task_name => 'update sql',table_owner => 'CGZHANG',table_name => 'BORECEIVEDLOG',by_row => false,chunk_size => 10000);
BY_ROW:分CHUNK的类型。如果为TRUE,则后面的CHUNK_SIZE表示是行;如果是FALSE,则后面的CHUNK_SIZE表示的是BLOCK。
CHUNK_SIZE:CHUNK大小。如果BY_ROW为TRUE,表示多少行分为一个CHUNK;如果BY_ROW为FALSE,则表示多少块分为一个CHUNK。
注意:这里的TABLE_OWNER和TABLE_NAME是只能用大写的,不知道是否是BUG,小写的时候会报ORA-29491错误。
分好后的CHUNK可以使用视图查看:
SQL> select chunk_id,task_name,status,start_rowid,end_rowid from dba_parallel_execute_chunks where rownum<=2;
CHUNK_ID TASK_NAME STATUS START_ROWID END_ROWID
---------- -------------------- -------------------- ------------------ ------------------
112627 update sql UNASSIGNED AAASKjAAUAAAHiAAAA AAASKjAAUAAAH1/CcP
112628 update sql UNASSIGNED AAASKkAAUAAAH2AAAA AAASKkAAUAAAIJ/CcP
这里可以看到分好的每个CHUNK的ID,还有TASK_NAME,以及每个CHUNK的状态,每个CHUNK从哪个ROWID开始,到哪个ROWID结束。在数据真正开始处理的时候,也可以通过这个视图来查看每个CHUNK的执行情况以及还有多少CHUNK没有执行,大概可以评估出整个任务的进度。
3、执行并行任务,这里更新其中一个字段为原先的10倍:
EXEC DBMS_PARALLEL_EXECUTE.run_task(task_name => 'update sql',sql_stmt => 'update /*+ ROWID(e) */ cgzhang.boreceivedlog e SET e.bankid = e.bankid * 10 WHERE rowid BETWEEN :start_id AND :end_id',language_flag => DBMS_SQL.native,parallel_level => 4);
start_id和end_id是两个占位符,用来标识CHUNK的开始和结束;PARALLEL表示平行度;LANGUAGE_FLAG意义如下:
V6 (or 0) specifies version 6 behavior
NATIVE (or 1) specifies normal behavior for the database to which the program is connected
V7 (or 2) specifies Oracle database version 7 behavior
执行结果可以通过视图查看:
SQL> select task_name,status,job_prefix,sql_stmt,language_flag from dba_parallel_execute_tasks;
TASK_NAME STATUS JOB_PREFIX SQL_STMT LANGUAGE_FLAG
----------- ------------------- ------------- -------------------------------------------------- -------------
update sql FINISHED_WITH_ERROR TASK$_145 update /*+ ROWID(e) */ cgzhang.boreceivedlog e SET 1
可以看到任务的状态时带错误的完成。其中JOB_PREFIX表示后来进行并行时候所开启的JOB的名称的前缀。那么下面可以通过查看CHUNK相关视图来查看具体的错误信息:
SQL> select chunk_id,task_name,status,start_rowid,end_rowid,job_name,start_ts,end_ts,error_code,error_message from dba_parallel_execute_chunks where chunk_id in (112110,112211);
CHUNK_ID TASK_NAME STATUS START_ROWID END_ROWID JOB_NAME START_TS END_TS ERROR_CODE ERROR_MESSAGE
---------- ------------- -------------------- ------------------ ------------------ -------------- -------------------------------- -------------------------------- ---------- -------------------------------------
112211 update sql PROCESSED_WITH_ERROR AAASKVAAIAAD9eAAAA AAASKVAAIAAD9x/CcP TASK$_145_3 11-1月 -10 03.20.37.647108 下午 11-1月 -10 03.20.37.930320 下午 -1438 ORA-01438: 值大于为此列指定的允许精度
112110 update sql PROCESSED AAASKRAANAABZaAAAA AAASKRAANAABZt/CcP TASK$_145_4 11-1月 -10 03.20.40.640925 下午 11-1月 -10 03.20.40.680616 下午
这里可以看到每个CHUNK开始和结束的时间,以及每个CHUNK完成的状态,以及有错误的CHUNK的错误原因。
注意:这里的每个CHUNK是在单独一个事务中提交的。所以某几个CHUNK发生错误后,可以修正错误,并使用下面的方法来继续未完成的CHUNK的任务:
exec dbms_parallel_execute.resume_task(task_name => 'update sql',sql_stmt => 'update /*+ ROWID(e) */ cgzhang.boreceivedlog e SET e.bankid = e.bankid * 10 WHERE rowid BETWEEN :start_id AND :end_id',language_flag => 1);
执行完后,查看视图:
SQL> select task_name,status,job_prefix,sql_stmt,language_flag from dba_parallel_execute_tasks;
TASK_NAME STATUS JOB_PREFIX SQL_STMT LANGUAGE_FLAG
----------- ------------------- -------------- -------------------------------------------------------------------------------- -------------
update sql FINISHED TASK$_145 update /*+ ROWID(e) */ cgzhang.boreceivedlog e SET e.bankid = e.bankid * 10 WHER 1
状态已经变成完成,而且CHUNK视图中的所有的CHUNK状态已经变成PROCESSED。
4、最后可以删除完成的任务:
exec dbms_parallel_execute.drop_task(task_name => 'update sql');
删除任务时,任务对应的CHUNK也会被删除
5、如果中途想停止任务,可以使用:
exec dbms_parallel_execute.stop_task('update sql');
除了按照ROWID来进行分CHUNK外,ORACLE还提供了按照列来CHUNK以及按照SQL执行的结果来进行CHUNK。
按照列来进行CHUNK的时候,跟手工按照主键或者其他值来进行分区的方法类似,示例如下:
exec dbms_parallel_execute.create_chunks_by_number_col(task_name => 'update sql',table_owner => 'CGZHANG',table_name => 'BORECEIVEDLOG',table_column => 'BANKID',chunk_size => 100000);
这个时候的CHUNK是先查询列的一个MAX和MIN值,然后按照CHUNK的粒度来进行CHUNK。
START_ID END_ID
--------------------------- ---------------------------
min_id_val min_id_val+1*chunk_size-1
min_id_val+1*chunk_size min_id_val+2*chunk_size-1
… …
min_id_val+i*chunk_size max_id_val
注意:ORACLE并不是那么智能的,所以如果进行CHUNK的列中如果有异常数据导致某一两个值特别大,那么就会因此产生很多很多空的CHUNK,导致整个CHUNK过程需要很长时间。而且,接下来的RUN_TASK的时候,传入的START_ID和END_ID将是这里CHUNK使用的值,那么如果这个值上没有索引或者索引的可选择性不高,那整个执行过程就是噩梦。
之前的都是针对全表进行操作的,如果只需要对表中的部分数据,或者基于某个查询的结果集进行并行操作,那根据SQL结果进行CHUNK就派上用场了:
exec dbms_parallel_execute.create_chunks_by_SQL(task_name => 'update sql',sql_stmt => 'SELECT bankid,bankid FROM CGZHANG.BORECEIVEDLOG where bankorderresult=1',by_rowid => false);
这是只针对bankorderresult为1的,按照BANKID进行CHUNK,每个BANKID是一个单独的CHUNK,然后START_ID和END_ID都根据单独的BANKID去批量操作。
exec dbms_parallel_execute.create_chunks_by_SQL(task_name => 'update sql',sql_stmt => 'SELECT rowid,rowid FROM CGZHANG.BORECEIVEDLOG where bankorderresult=1',by_rowid => true);
这是根据ROWID进行CHUNK划分,每行是一个单独的CHUNK
exec dbms_parallel_execute.create_chunks_by_SQL(task_name => 'update sql',sql_stmt => 'SELECT MIN(DEALID), MAX(DEALID) FROM (SELECT ROW_NUMBER() OVER(ORDER BY DEALID) RW, DEALID FROM CGZHANG.BORECEIVEDLOG WHERE BANKORDERRESULT = 1) GROUP BY CEIL(RW / 100000)',by_rowid => false);
这个是把BANKORDERRESULT = 1的记录中,按照DEALID去进行CHUNK,然后CHUNK的大小为100000,得到的结果如下:
SQL> SELECT CHUNK_ID,TASK_NAME,STATUS,START_ROWID,END_ROWID,START_ID,END_ID FROM DBA_PARALLEL_EXECUTE_CHUNKS;
CHUNK_ID TASK_NAME STATUS START_ROWID END_ROWID START_ID END_ID
---------- ------------ -------------------- ------------------ ------------------ ---------- ----------
1775907 update sql UNASSIGNED 3337739000 3349852900
1775908 update sql UNASSIGNED 3252487000 3273417000
1775909 update sql UNASSIGNED 3294196300 3316557400
1775910 update sql UNASSIGNED 3273417000 3294196000
1775905 update sql UNASSIGNED 3007610000 3252486600
1775906 update sql UNASSIGNED 3316558300 3337738800
可以看到,这里是以START_ID和END_ID进行CHUNK的,所以在RUN_TASK的时候,传入的WHERE条件要是DEALID,示例如下:
EXEC DBMS_PARALLEL_EXECUTE.run_task(task_name => 'update sql',sql_stmt => 'update cgzhang.boreceivedlog e SET e.bankid = e.bankid * 10 WHERE dealid BETWEEN to_char(:start_id) AND to_char(:end_id)',language_flag => DBMS_SQL.native,parallel_level => 4);
这时因为DEALID为VARCHAR类型,虽然里面存的是NUMBER数值,所以要对START_ID和END_ID变量进行TO_CHAR转换,使得整个更新任务可以正确的使用到DEALID上的索引。
exec dbms_parallel_execute.create_chunks_by_SQL(task_name => 'update sql',sql_stmt => 'SELECT MIN(ROWID), MAX(ROWID) FROM (SELECT ROW_NUMBER() OVER(ORDER BY ROWID) RW, ROWID FROM CGZHANG.BORECEIVEDLOG WHERE BANKORDERRESULT = 1) GROUP BY CEIL(RW / 100000)',by_rowid => true);
这个是按照ROWID进行CHUNK(因为BY_ROWID参数为TRUE),每个CHUNK大小为100000行,得到的CHUNK结果如下:
SQL> SELECT CHUNK_ID,TASK_NAME,STATUS,START_ROWID,END_ROWID,,START_ID,END_ID FROM DBA_PARALLEL_EXECUTE_CHUNKS;
CHUNK_ID TASK_NAME STATUS START_ROWID END_ROWID START_ID END_ID
---------- --------------- -------------------- ------------------ ------------------ ---------- ----------
1775901 update sql UNASSIGNED AAASKVAAIAAD/dwAAE AAASKVAAIAAEELeAAB
1775902 update sql UNASSIGNED AAASKVAAIAAENxeAAC AAASKVAAIAAESlOAAJ
1775903 update sql UNASSIGNED AAASKVAAIAAEI+0AAC AAASKVAAIAAENxeAAA
1775904 update sql UNASSIGNED AAASKVAAIAAEELeAAC AAASKVAAIAAEI+0AAB
1775899 update sql UNASSIGNED AAASKVAAIAAD6qWAAA AAASKVAAIAAD/dwAAD
1775900 update sql UNASSIGNED AAASKVAAIAAESlPAAA AAASKVAAIAAEVh7AAI
这里是按照START_ROWID和END_ROWID进行CHUNK的,所以START_ID和END_ID字段都为空,这是RUN_TASK的时候要使用ROWID作为WHERE条件
测试过程中发现如果CHUNK很小,则导致分出来的CHUNK过多,那么CHUNK_ID是使用一个SEQUENCE叫 DBMS_PARALLEL_EXECUTE_SEQ$来生成的,而这个SEQUENCE的默认CACHE值只有20,可以通过加大这里的CACHE值解决一部分性能问题。
总结:
DMBS_PARALLEL_EXECUTE使用步骤基本是:创建任务、把数据进行CHUNK、执行任务三大步骤。
其中CHUNK中的BY_ROWID和BY_COL都比较容易理解,BY_SQL是不大容易理解也是最灵活的方式。其实BY_SQL就是执行一个查询,但最重要的是查询返回的结果要是能够进行CHUNK的区间,这里怎么写这个SQL就是非常头疼的问题了(这里感谢PUB的newkid为了提供的SQL),这个CHUNK写好了,后面的问题就简单了。
除了对自己进行数据更新外,也可以进行数据的并行迁移(只要把UPDATE改成INSERT另一个表就好了)等等许多复杂的任务,而且DBMS_PARALLEL_EXECUTE提供了set_chunk_status、 get_rowid_chunk等多种更灵活的方式来控制整个任务执行的过程,从而实现复杂任务的并行执行。
UPDATE:
“逆袭的W”网友提出了一个很好的问题,就是使用BY_SQL进行CHUNK的时候,如果数据不是唯一的,那么使用ROW_NUMBER来进行CHUNK就会出现CHUNK之间有重叠,比如我10000条记录一个CHUNK,结果里面有10万条都是相同的,那就会有这样的问题存在。
使用RANK替代ROW_NUMBER可以解决这个问题,因为RANK会过滤掉重复的数据。
这也正说明了一点,CHUNK是并行操作很重要的一个步骤,而且CHUNK完成后,最好对CHUNK的结果进行校验,保证数据不多不少。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/25016/viewspace-1030489/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/25016/viewspace-1030489/