分享

Oracle 并行处理

 姑苏慕容凡 2015-07-20
在一个串行的执行环境中,由单个进行程或线程负责处理SQL的操作,而且每个动作必须在随后的动作开始前完成。在任意给定的时刻,单个Oracle进程只能利用单个CPU的能力以及从一个磁盘读取数据。在并行当中,并行SQL支持多个进程或线程同时处理一条SQL语句。
Oracle支持广泛的操作使用并行处理,包括查询、DDL、DML。可以在以下操作中使用并行:
(1)包含表或者索引范围扫描的查
(2)批量插入、更新或删除。
(3)表或索引的创建。
(4)使用dbms_stats进行对象统计信息的收集。
(5)使用RMAN进行备份或恢复操作。

如果系统中每人并行SQL都试图使用系统的所有资源,并行往往会使用性能变差,而不是更好。相反地,只有我们做这些操作提高性能并且不会降低其他并行操作对数据库请求的性能时,我们才能够有效地使用并行。在下面这些场合中,我们可以有效地使用并行处理:
(1)服务器上有多个CPU
如果运行Oracle数据库的服务器上有多个CPU,并行处理通常是很有效的。这是因为Oracle服务器执行大部分的操作需要CPU,如访问Oracle共享内存,执行排序或磁盘访问等。如果服务器主机只有一个CPU,并行进程可能会争用CPU,从而导致实际性能可能会下降。
(2)被访问的数据在多个磁盘驱动器上
当要访问的数据能在Oracle缓冲区高速缓存中找到时,SQL语句很少或根本不需要访问磁盘就能返回数据。然而大表的全表扫描更倾向于大量的磁盘物理读取。如果被访问的数据位于一个磁盘上,这样,并行进程需要排队等待磁盘,从而导致了并行无法施展其优点。
(3)使用并行的SQL是长时间运行的或是资源敏感的
并行SQL适合长时时运行的或是资源敏感的语句,激活和协调多个并行查询进程以及联合协调这些进程间的信息流动是有开销的。对于一些运行时间很短的SQL,这种开销可能比响应整个SQL的时间还要长。比如,长时间运行的报表,大表的批量更新,创建或重建大表上的索引,为分析处理创建临时表,为提高性能或消除行而重建表。并行处理通常情况下不太适合OLTP。
(4)在查询中,SQL执行至少一次全表,索引或分区扫描
(5)主机有空闲的能力
如果服务器是在满负何的情况下运转,这样并行处理的收益可能不是很好。在没有充分利用多CPU的机器上,并行处理对单个任务工作很好。如果机器上的所有CPU都很忙,并行将遭遇CPU的瓶颈问题,从而导致性能下降。
(6)SQL是调优过的
对一个没有调优过的SQL使用并行可能会极大地减少执行时间,但也加重了那个SQL对数据库服务器与其他会影响。

并行主要用在DDL上,也可以说是DBA的一个强有力的工具,较少使用在查询当中。对于查询,调优SQL是最有力的方法。关于并行的使用很简单,可以通过参数,语法,或提示来进行。下面,我将一一进行介绍,并给出相应的例子(由于我的机器不是很好,可能达不到预想的效果,我会进行知识点与流程的说明)。

要使用并行,首要要确定并行度。并行度(DOP)定义了将创建并行流个数,最优的并行度对于获得好的并行性能到至关重要。Oracle可根据下面的规则来确实并行度。
(1)如果指定或请求并行执行,但没有指定并行度,默认的并行度被设置为系统CPU核数的两倍。通过参数parallel_threads_per_cpu来控制,如下:
SQL> show parameters parallel_thread

NAME                                 TYPE
------------------------------------ ----------------------
VALUE
------------------------------
parallel_threads_per_cpu             integer
2
SQL>
如果没有指定并行度,那么对于上述而言,并行度为4。
(2)如果参数parallel_degree_policy被设置为auto,那么,Oracle将依据要执行的操作的特性和对象的大小来确定并行度。注:该方法用在11gR2。auto也允许并行从缓存中获取数据而不是直接路径IO。参数显示如下:
SQL> show parameters parallel_degree_policy

NAME                                 TYPE
------------------------------------ ----------------------
VALUE
------------------------------
parallel_degree_policy               string
MANUAL
SQL> alter session set parallel_degree_policy = 'auto'
  2  /

Session altered.

SQL>
该参数默认为manual。
(3)如果参数parallel_adaptive_multi_user被设置为true,Oracle将依据系统的总负何调整并行度。
(4)在11g以上的版本中,如果参数parallel_io_cap_enabled被设置为true,Oracle将限制并行度为IO子系统所能够支持的大小。可以使用dbms_resource_manager.calibrate_io进行计算。例如,在我的机器上计算如下:
SQL> set serveroutput on
SQL> declare
  2     max_iops_out pls_integer;
  3     max_mbps_out pls_integer;
  4     actual_latency_out pls_integer;
  5  begin
  6     dbms_resource_manager.calibrate_io(
  7        max_iops=>max_iops_out,
  8        max_mbps=>max_mbps_out,
  9        actual_latency=>actual_latency_out);
 10
 11     dbms_output.put_line('max_iops = ' || max_iops_out
 12                         || ',max_mbps = ' || max_mbps_out
 13                         || ',actual_latency = ' || actual_latency_out);
 14  end;
 15  /
max_iops = 90,max_mbps = 33,actual_latency = 21

PL/SQL procedure successfully completed.

SQL>
计算结果显示:最大IO数(max_iops)为90,最大IO吞吐量为33,数据块的平均潜伏IO数(actual_latency)为21。
(5)可以在表级或索引设定并行度,比如,执行create table或create index 指定parallel语法。例子如下:
SQL> create table t
  2  parallel (degree default)
  3  as
  4  select *
  5  from all_objects
  6  /

Table created.

SQL> create index t_idx on t(object_id) parallel (degree 4)
  2  /

Index created.

SQL>
说明:在创建表时,我使用了默认的并行度,在创建索引时,我手工指定了并行度为4。
(6)在查询或DML中,可以使用提示(hint)来指定并行度,如下:
SQL> select /*+parallel(t 4)*/ count(*)
  2  from t
  3  where object_name like 'D%'
  4  /

  COUNT(*)
----------
      1995

SQL> update /*+parallel(t)*/ t set created = sysdate
  2  /

7851 rows updated.

SQL> commit;

Commit complete.

SQL> merge /*+parallel(t) parallel(o)*/ into t
  2  using all_objects o on (t.object_id = o.object_id)
  3  when matched then
  4  update set t.created = o.created
  5  /

7851 rows merged.

SQL> commit;

Commit complete.

SQL>
(7)在使用并行时,并行度不能超过参数parallel_max_servers指定的大小。

上述我们介绍一些关于并行度的参数与并行的语法,关于并行还有其他一些重要的参数,下面我们来介绍一下:
parallel_degree_limit:在11gR2以上的版本中,对可以实现的并行度增加了一个绝对的限制,CPU的值防止并行度超过参数                                         parallel_threads_per_cpu指定的值。
parallel_execution_message_size:设置为并行处理涉及的进程间通信缓存的大小。
parallel_force_local:在11gR2版本或以上的版本中,如果设置为true,将取消RAC上多个实例的并行化。
parallel_min_percent:如果设置为一个非零值,这个参数确定查询最小可接受的并行度。由于系统负载或其他并行进程正在使                                     用系统服务器进行池,如果请求或确定的并行度不能够得到,并行度将减小为该参数所指定的值。例                                         如:在参数parallel_degree_policy设置为auto的情况下,如果使用并行度为8的查询请求,而现在系统                                       只能并行度为5的是可用的,那么,计算一个百分比,(5/8)%=52%。如果参数parallel_min_percent的                                       值小于或等于62,查询将继续执行,如果大于62,语句要么进入队列等待,要么出错。
parallel_min_time_threshold:指定对SQL语句执行自动并行化要求的时间(单位为秒),如果估算出的SQL语句的耗时超过了                                                这个阀值,Oracle将自动对该SQL使用并行化。默认值为auto,表示Oracle会自动进行计算一个                                                值。

下面,我们来看一下并行执行的执行计划与跟踪信息,并进行说明。
SQL> explain plan for
  2  select /*+parallel*/ *
  3  from t
  4  order by object_name
  5  /

Explained.

SQL> select *
  2  from table(dbms_xplan.display(null,null,'BASIC +PARALLEL'))
  3  /

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 3122197462

-------------------------------------------------------------------------
| Id  | Operation               | Name     |    TQ  |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |          |        |      |            |
|   1 |  PX COORDINATOR         |          |        |      |            |
|   2 |   PX SEND QC (ORDER)    | :TQ10001 |  Q1,01 | P->S | QC (ORDER) |
|   3 |    SORT ORDER BY        |          |  Q1,01 | PCWP |            |
|   4 |     PX RECEIVE          |          |  Q1,01 | PCWP |            |
|   5 |      PX SEND RANGE      | :TQ10000 |  Q1,00 | P->P | RANGE      |

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|   6 |       PX BLOCK ITERATOR |          |  Q1,00 | PCWC |            |
|   7 |        TABLE ACCESS FULL| T        |  Q1,00 | PCWP |            |
-------------------------------------------------------------------------

14 rows selected.

SQL>
说明:在上述,我使用并行对表T进行一个全表扫描,并查看其与并行相关的执行计划。在执行计划有关于PX开头的步骤,下面,我们进行一下介绍。
PX BLOCK ITERATOR:这个操作通常处于并行管道的第一步,BLOCK ITERATOR把表分隔成多个块,每个块由涉及的并行                                          服务器进程中的一个去处理。
PX SEND RANGE:这个操作表明数据正在根据范围值来从一个并行进程发送到另一个并进程。
PX RECEIVE:表示来自另一个进程的数据正在被一个并行进程接收。
PX SEND QC:这是给并行查询协调进程的一个发送操作。
PX COORDINATOR:这个步骤表明并行查询协调进程正在从并行流中接收数据并返回给SQL语句。
关于IN-OUT项的说明:
P->P(parallel_to_parallel):表示并行处理传递结果到并行进程的第二个集合,例如,并行扫描进程可以传递给并行排序进程。
P->S(parallel_to_serial):这个结果通常发生在查询的最上层,将结果并行地提供给查询协调者。
PCWP(parallel_combined_with_parent):这个步骤是以并行的方式执行的,父步骤父步骤或子步骤也由同一个进程并行运行。                                                                  例如在一个并行嵌套循环连接中,并行查询进程扫描驱动表,同时也发出对连接表的                                                                  索引查找。
PCWC(parallel_combined_with_child):含义同PCWP类似。
S->P(parallel_from_serial):串行操作把结果传给并行进程的集合,如果出现这一项,可能暗示并行语句有一个串行的瓶颈,                                               可能在等待串行处理。
关于“PQ Distrib”项(分发选项)的介绍:
RANGE:根据值的范围来分发记录。当使用并行排序操作时,这是一个很典型的选项。
HASH:根据值的散列,把记录分发到并行查询的子进程。适合连接和GROUP BY操作。
RANDOM:记录被随机分配给进行查询的子进程。
ROUND ROBIN:以循环的方式把记录每次一个地分发给子进程。

对SQL使用并行化时,使用SQL trace进行跟踪时,相对比较困难一缜,这是因为在并行中每个进程都有自己的跟踪文件,而且由于这些进程被共享在所有并行化的SQL和会话期间,除了我们要的那部分数据外,还包括了其他SQL的信息。我们可以通过以一下些步骤,仍然能够跟踪并行执行。下面为了方便,我先创建两个示例表:
SQL> create table emp
  2  parallel (degree default)
  3  as
  4  select rownum empno,initcap(dbms_random.string('u',8)) ename,
  5         round(dbms_random.value(4000,10000),2) salary,
  6         trunc(dbms_random.value(1,200)) deptno
  7  from dual
  8  connect by rownum <= 10000
  9  /

Table created.

SQL> alter table emp modify ename varchar2(10)
  2  /

Table altered.

SQL> create table dept
  2  as
  3  select rownum deptno,dbms_random.string('u',6) dname
  4  from dual
  5  connect by rownum <= 200
  6  /

Table created.

SQL> alter table dept modify dname varchar2(6)
  2  /

Table altered.

SQL>
下面,我以例子的形式来介绍跟踪并行的相关步骤:
(1)使用dbms_session.set_identifier设置一个唯一的客户标识
(2)使用dbms_monitor.client_id_trace_enable为设置的客户标识启动跟踪
SQL> begin
  2     dbms_session.set_identifier('parallel_trace_test01');
  3     dbms_monitor.client_id_trace_enable(
  4        client_id=>'parallel_trace_test01',
  5        waits=>true,
  6        binds=>false,
  7        plan_stat=>'all_executions');
  8  end;
  9  /

PL/SQL procedure successfully completed.

SQL>
(3)运行我们想要跟踪的SQL。
(4)使用trcsess工具创建一个只包含客户标识的跟踪条目的新跟踪文件。
(5)使用tkprof工具进行格式化。
完成以后,我们可以将其停止跟踪,如下:
SQL> exec dbms_monitor.client_id_trace_disable('parallel_trace_test01')

PL/SQL procedure successfully completed.

SQL>
这里我说明一下,在windows上,我trcsess工具时,出现打开文件的三个错误,然后我分别看了一下这几个文件,是winows系统的系统文件。生成后的跟踪文件是空白的。这个问题有待解决。在linux下是可以的。
关于并行跟踪,我们还可以使用一些高级特性,比如_px_trace参数,10391事件等。
如:alter session set "_px_trace"="complication","execution","mesaaging"。
alter session set events '10391 trace name context forever,level 128‘.

对于并行处理,我们可以使用v$pq_tqstat视图来查询,v$pq_tqstat视图包含了有关并行查询服务器的每个集合间传递数据的信息,包括发送和接收的行数。如下:
SQL> alter table emp parallel (degree default)
  2  /

Table altered.

SQL> alter table dept parallel (degree default)
  2  /

Table altered.

SQL> explain plan for
  2  select dname,sum(salary)
  3  from emp join dept using(deptno)
  4  group by dname
  5  order by dname
  6  /

Explained.

SQL> select *
  2  from table(dbms_xplan.display(null,null,'BASIC +PARALLEL'))
  3  /

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1282370089

--------------------------------------------------------------------------------
| Id  | Operation                      | Name     |    TQ  |IN-OUT| PQ Distrib |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |          |        |      |            |
|   1 |  PX COORDINATOR                |          |        |      |            |
|   2 |   PX SEND QC (ORDER)           | :TQ10003 |  Q1,03 | P->S | QC (ORDER) |
|   3 |    SORT ORDER BY               |          |  Q1,03 | PCWP |            |
|   4 |     PX RECEIVE                 |          |  Q1,03 | PCWP |            |
|   5 |      PX SEND RANGE             | :TQ10002 |  Q1,02 | P->P | RANGE      |

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|   6 |       SORT GROUP BY            |          |  Q1,02 | PCWP |            |
|   7 |        PX RECEIVE              |          |  Q1,02 | PCWP |            |
|   8 |         PX SEND HASH           | :TQ10001 |  Q1,01 | P->P | HASH       |
|   9 |          HASH GROUP BY         |          |  Q1,01 | PCWP |            |
|  10 |           HASH JOIN            |          |  Q1,01 | PCWP |            |
|  11 |            PX RECEIVE          |          |  Q1,01 | PCWP |            |
|  12 |             PX SEND BROADCAST  | :TQ10000 |  Q1,00 | P->P | BROADCAST  |
|  13 |              PX BLOCK ITERATOR |          |  Q1,00 | PCWC |            |
|  14 |               TABLE ACCESS FULL| DEPT     |  Q1,00 | PCWP |            |
|  15 |            PX BLOCK ITERATOR   |          |  Q1,01 | PCWC |            |
|  16 |             TABLE ACCESS FULL  | EMP      |  Q1,01 | PCWP |            |

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

23 rows selected.

SQL> select dfo_number,tq_id,server_type,min(num_rows),max(num_rows),count(*) dop
  2  from v$pq_tqstat
  3  group by dfo_number,tq_id,server_type
  4  order by dfo_number,tq_id,server_type
  5  /

DFO_NUMBER      TQ_ID SERVER_TYP MIN(NUM_ROWS) MAX(NUM_ROWS)        DOP
---------- ---------- ---------- ------------- ------------- ----------
         1          0 Consumer             200           200          2
         1          0 Producer               0           400          2
         1          1 Consumer             184           214          2
         1          1 Producer             199           199          2
         1          2 Consumer              91           108          2
         1          2 Producer              92           107          2
         1          2 Ranger               182           182          1
         1          3 Consumer             199           199          1
         1          3 Producer              91           108          2

9 rows selected.

SQL>
我们也使用v$px_session视图查询实时得到系统上正在发生的并行执行的信息,这个视图可以显示当前那些并行子进程正在执行SQL。可以关联v$session和v$sql视图来显示相关信息。下面,我给出一个SQL用来显示当前正在执行的并行会话和SQL,如下:
SQL> with px_sess
  2  as(
  3  select qcsid,qcserial#,max(degree) degree,
  4         max(req_degree) req_degree,count(*) no_of_processes
  5  from v$px_session p
  6  group by qcsid,qcserial#
  7  )
  8  select s.sid,s.username,degree,req_degree,no_of_processes,sql_text
  9  from v$session s join px_sess p on(s.sid = p.qcsid and s.serial# = p.qcserial#)
 10      join v$sql a on(a.sql_id = s.sql_id and a.child_number = s.sql_child_number)
 11  /

no rows selected

SQL> 
我这里没有显示相关信息,证明当前没有正在处理的并行。

并行DML是让批量的DML加速的一种非常强大的方法,然而,在单个事务当中应用所有的变更也是有缺点的,这会导致产生长时间的锁,要求大量的回滚段,而且当操作失败时,回滚操作也是很昂贵的。为了解决这个问题,在11gR2中引入了dbms_parallel_execute程序包,它允许使用更小的块来执行DML,且每个块都可以独立提交,在任何一个单块操作失败时,包支持重启动这个任务。下面,我以例子的来介绍该包的使用。
SQL> declare
  2     l_dml_sql varchar2(1000);
  3     l_task_name varchar2(50) := 'dbms_parallel_execute_test';
  4     l_status number;
  5  begin
  6     dbms_parallel_execute.create_task(
  7        task_name=>l_task_name,
  8        comment=>'this is dbms_parallel_execute package demo');
  9
 10     dbms_parallel_execute.create_chunks_by_rowid(
 11        task_name=>l_task_name,
 12        table_owner=>'DJP01',
 13        table_name=>'EMP',
 14        by_row=>true,
 15        chunk_size=>1000);
 16
 17     l_dml_sql := 'update emp set salary = salary - 300 '
 18               || 'where rowid between :start_id and :end_id';
 19
 20     dbms_parallel_execute.run_task(
 21        task_name=>l_task_name,
 22        sql_stmt=>l_dml_sql,
 23        language_flag=>dbms_sql.native,
 24        parallel_level=>2);
 25
 26     l_status := dbms_parallel_execute.task_status(l_task_name);
 27     if l_status = dbms_parallel_execute.finished
 28     then
 29        dbms_parallel_execute.drop_task(l_task_name);
 30        dbms_output.put_line('execute dbms_parallel_execute demo success');
 31     else
 32        dbms_output.put_line('execute dbms_parallel_execute demo failed');
 33     end if;
 34  end;
 35  /
execute dbms_parallel_execute demo success

PL/SQL procedure successfully completed.

SQL>
说明:我们首先创建一个任务,其实就是为要处理的表定义表块,创建表块有多种方式,我这里使用的是rowid进行表块的创建,参数chunk_size指定所创建表块的大小。接着就是书写相应的DML语句,如果是使用的create_chunks_by_rowid进行表块的创建,在DML语句中必须指定rowid的一个范围,而且要使用绑定变量的形式。之后就是执行该任务与相应的SQL语句。

这种方法不但可以用于DML语句,还可以用在过程中,比如使用过程做并行化。另外有一个并行化的过程就是管道化函数,它可以通过自己的语法来实现,关于管道化函数可以参考http://blog.163.com/donfang_jianping/blog/static/1364739512012112113444435/中的介绍。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多