5、方法3:dbms_paralle_execute并行包使用
其他优化手段都用上的时候,并行是可以尝试的方法。并行parallel就是利用多个process同时进行处理,从而提高处理效率的方法。Parallel的使用有一些前提,也有一些不良反应。并行的前提是硬件支持,并行技术本身要消耗很多的资源,相当于是将服务器资源“榨干”来提速。在规划并行策略的时候,首先要看硬件资源是不是支持,单核CPU情况下,也就不需要使用这个技术了。
使用并行之后,必然对其他正在运行程序、作业有影响。所以,笔者的经验是:一般应用不要考虑并行的事情,如果发现特定场景存在并行的需要,可以联系DBA或者运维人员确定可控的技术方案。
在11.2之前,使用并行稍微复杂一些,很多朋友在使用的时候经常是“有名无实”,看似设置了并行,但是实际还是单进程运行。11.2之后,Oracle提供了新的并行操作接口dbms_parallel_execute,这让并行更加简单。
说明:本篇不是专门介绍dbms_parallel_execute接口,只作为介绍。详细内容参见笔者专门介绍这个接口的文章。
dbms_parallel_execute工作采用作业task方式,后台执行。首先是按照特定的原则进行数据分割,将工作数据集合分割为若干chunk。之后启动多个后台job进行工作。在划分工作集合的问题上,Oracle提供了三种方法,rowid、column_value和SQL,分别按照rowid、列值和特定SQL语句进行分割。
注意:使用dbms_parallel_execute接口包有一个前提,就是job_queue_process参数必须设置非空。如果为0,则我们的进程执行之后被阻塞挂起。
恢复数据环境。
SQL> create table t_targettablespace users as select * from t where 1=0;
Table created
Executed in 0.078 seconds
SQL> insert /*+append*/into t_target select * from t;
9318016 rows inserted
Executed in 64.974 seconds
SQL> commit;
Commit complete
Executed in 0.109 seconds
参数环境。
SQL> show parameter job_queue
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
job_queue_processes integer 1000
在这个任务中,我们选择使用create_chunks_by_rowid方法。这种方法通用型强,执行计划稳定性好。
SQL> set serveroutput on;
SQL> declare
2 vc_sqlvarchar2(1000);
3 n_try number;
4 n_status number;
5 begin
6 --create a task
7 dbms_parallel_execute.create_task(task_name => 'Huge_Update');
8
9 --By Rowid
10 dbms_parallel_execute.create_chunks_by_rowid(task_name => 'Huge_Update',
11 table_owner => 'SYS',table_name => 'T_TARGET',by_row =>true,chunk_size => 10000);
12
13 vc_sql := 'update /*+rowid(dda)*/t_target set owner=to_char(length(owner)) where rowid between :start_id and :end_id';
14
15 dbms_parallel_execute.run_task(task_name => 'Huge_Update',sql_stmt =>vc_sql,language_flag =>dbms_sql.native,parallel_level => 3);
16 --防止失败后重启
17 n_try := 0;
18 n_status := dbms_parallel_execute.task_status('Huge_Update');
19 while (n_try<2 and (n_status != dbms_parallel_execute.FINISHED)) loop
20 n_try := n_try + 1;
21 dbms_parallel_execute.resume_task('Huge_Update');
22 n_status := dbms_parallel_execute.task_status('Huge_Update');
23 end loop;
24
25 dbms_output.put_line(''||n_try);
26 dbms_parallel_execute.drop_task('Huge_Update');
27 end;
28 /
0
PL/SQL procedure successfully completed
Executed in 1177.106 seconds
在代码中,需要注意start_id和end_id两个绑定变量。这两个范围值是接口固定的。这种方法使用了1177s来完成工作。
在执行过程中,我们也有很多方法来监督执行过程。Oracle提供了两个视图,关于parallel_execute接口的。Dba_parallel_execute_tasks表示了提交的任务,里面我们可以看到状态信息。
SQL> col task_name for a15;
SQL> select task_name, status from dba_parallel_execute_tasks;
TASK_NAME STATUS
------------------- -------------------
Huge_Update PROCESSING
SQL> select task_name, JOB_PREFIX from dba_parallel_execute_tasks;
TASK_NAME JOB_PREFIX
-------------------- ------------------------------
Huge_Update TASK$_655
执行中,我们从v$session中可能看到后台的进程会话。
SQL> select sid, serial#, status, PROGRAM, SQL_ID, event from v$session where action like 'TASK$_655%';
SID SERIAL# STATUS PROGRAM SQL_ID EVENT
---------- ---------- -------- ------------------------------------------------ ------------- ----------------------------------------------------------------
35 507 ACTIVE oracle@bspdev.localdomain (J003) d7xw8z3nzh5cg db file scattered read
38 119 ACTIVE oracle@bspdev.localdomain (J001) d7xw8z3nzh5cg log buffer space
45 6612 ACTIVE oracle@bspdev.localdomain (J000) d7xw8z3nzh5cg Data file init write
另一个视图更有用dba_parallel_execute_chunks,其中包括了所有的chunk对象。Parallel Execute执行的原则就是数据的划分,这个视图中,可以看到哪些chunk已经完成,哪些没有完成。
SQL> select status, count(*) from dba_parallel_execute_chunks group by status;
STATUS COUNT(*)
-------------------- ----------
ASSIGNED 3
UNASSIGNED 2315
PROCESSED 571
ASSIGNED状态表示多进程状态正在处理,我们设置了三个后台进程执行。UNASSIGNED表示没有完成,正等待处理。PROCESSED表示已经处理完。
这种方法应该是目前找到比较好的方法。缺点就是代码量比较大。优点是处理使用并行,如果物理条件支持,执行效率是很高的。而且,在海量数据表的情况下,这种策略是很值得推荐的。
6、结论
更新大量的数据,是我们常见的一种问题场景。无论对开发人员,还是运维人员,都有不同的挑战。笔者本篇要强调的是:没有绝对好的策略,都是针对特别的场景和背景,选取最适合的策略。从而更好地完成任务。盲目的执行SQL语句,是一种典型不负责任的行为,需要避免杜绝。