11g包dbms_parallel_execute在海量資料處理過程中的應用

Appleses發表於2016-01-30

11gdbms_parallel_execute在海量資料處理過程中的應用





1.1  BLOG文件結構圖

 

wpsE42C.tmp 

 

1.2  前言部分

 

1.2.1  導讀

各位技術愛好者,看完本文後,你可以掌握如下的技能,也可以學到一些其它你所不知道的知識,~O(∩_∩)O~:

① 11g包dbms_parallel_execute在海量資料處理過程中的應用

 

注意:本篇BLOG中程式碼部分需要特別關注的地方我都用黃色背景和紅色字型來表示,比如下邊的例子中,thread 1的最大歸檔日誌號為33thread 2的最大歸檔日誌號為43是需要特別關注的地方。

  List of Archived Logs in backup set 11

  Thrd Seq     Low SCN    Low Time            Next SCN   Next Time

  ---- ------- ---------- ------------------- ---------- ---------

  1    32      1621589    2015-05-29 11:09:52 1625242    2015-05-29 11:15:48

  1    33      1625242    2015-05-29 11:15:48 1625293    2015-05-29 11:15:58

  2    42      1613951    2015-05-29 10:41:18 1625245    2015-05-29 11:15:49

  2    43      1625245    2015-05-29 11:15:49 1625253    2015-05-29 11:15:53

 

 

 

 

 

本文如有錯誤或不完善的地方請大家多多指正,ITPUB留言或QQ皆可,您的批評指正是我寫作的最大動力。

1.2.2  實驗環境介紹

 

11.2.0.1  RHEL6.5

 

 

1.2.3  相關參考文章連結

 

Oracle中如何更新一張大表記錄

http://blog.itpub.net/26736162/viewspace-1684095/

使用11g dbms_parallel_execute執行並行更新(下)

http://blog.itpub.net/26736162/viewspace-1683913/

使用11g dbms_parallel_execute執行並行更新(上)

http://blog.itpub.net/26736162/viewspace-1683912/

 

 

1.2.4  本文簡介

 

一個朋友own_my要處理批次資料,但是指令碼跑的太慢了,於是網上搜到了dbms_parallel_execute這個包,用完後給我說這個包非常強大,於是我也學習學習,關於最佳化一直是我喜歡的內容,在參考了大神realkid4 的blog後,我自己也做了做實驗,感覺很強大,記錄在此。

 

 

1.3  相關知識點掃盲

 

參考大神的bloghttp://blog.itpub.net/17203031/

 

 

1.4  實驗部分

 

1.4.1  實驗目標

 

測試dbms_parallel_execute包在海量資料處理過程中的應用。

1.4.2  實驗過程

 

 

[oracle@etlhost206 ~]$ sqlplus / as sysdba

 

SQL*Plus: Release 11.2.0.1.0 Production on Wed Jun 3 13:40:34 2015

 

Copyright (c) 1982, 2009, Oracle.  All rights reserved.

 

 

Connected to:

Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production

With the Partitioning, OLAP, Data Mining and Real Application Testing options

 

SQL> CONN  LHR/lhr

Connected.

SQL> CREATE TABLE T AS SELECT * FROM DBA_OBJECTS;

 

Table created.

 

SQL> insert into t select * from t;

 

76369 rows created.

 

SQL> insert into t select * from t;

 

152738 rows created.

 

SQL> insert into t select * from t;

 

305476 rows created.

 

SQL> COMMIT;

 

Commit complete.

 

SQL> insert into t select * from t;

 

610952 rows created.

 

SQL> insert into t select * from t;

 

1221904 rows created.

 

SQL> insert into t select * from t;

 

2443808 rows created.

 

SQL> insert into t select * from t;

 

4887616 rows created.

 

SQL> COMMIT;

 

Commit complete.

 

SQL> insert into t select * from t;

 

9775232 rows created.

 

SQL> COMMIT;

 

Commit complete.

 

SQL> insert into t select * from t;

 

19550464 rows created.

 

SQL> COMMIT;

 

Commit complete.

 

SQL> select bytes/1024/1024 from dba_segments a where a.segment_name='T';

 

BYTES/1024/1024

---------------

           4341

 

SQL> SELECT COUNT(1) FROM T;

 

  COUNT(1)

----------

  39100928

 

SQL> show parameter job

 

NAME                                 TYPE        VALUE

------------------------------------ ----------- ------------------------------

job_queue_processes                  integer     1000

SQL> show parameter cpu

 

NAME                                 TYPE        VALUE

------------------------------------ ----------- ------------------------------

cpu_count                            integer     8

parallel_threads_per_cpu             integer     2

resource_manager_cpu_allocation      integer     8

 

SQL> set timing on

SQL> set time on;

15:50:01 SQL>

15:50:02 SQL> show parameter job

 

NAME                                 TYPE        VALUE

------------------------------------ ----------- ------------------------------

job_queue_processes                  integer     1000

15:50:09 SQL>  select bytes/1024/1024 from dba_segments a where a.segment_name='T';

 

BYTES/1024/1024

---------------

           4341

 

Elapsed: 00:00:00.41

15:50:31 SQL> declare

15:50:39   2    vc_task  varchar2(100);

15:50:39   3    vc_sql   varchar2(1000);

15:50:39   4    n_try    number;

15:50:39   5    n_status number;

15:50:39   6  begin

15:50:39   7    --Define the Task

15:50:39   8    vc_task := 'Task 1: By Rowid'; --Task名稱

15:50:39   9    dbms_parallel_execute.create_task(task_name => vc_task); --手工定義一個Task任務;

15:50:39  10 

15:50:39  11    --Define the Spilt

15:50:39  12    dbms_parallel_execute.create_chunks_by_rowid(task_name   => vc_task,

15:50:39  13                                                 table_owner => 'LHR',

15:50:39  14                                                 table_name  => 'T',

15:50:39  15                                                 by_row      => true,

15:50:39  16                                                 chunk_size  => 10000); --定義Chunk

15:50:39  17 

15:50:39  18    vc_sql := 'update /*+ ROWID(dda) */ t set DATA_OBJECT_ID=object_id+1 where rowid between :start_id and :end_id';

15:50:40  19    --Run the task

15:50:40  20    dbms_parallel_execute.run_task(task_name      => vc_task,

15:50:40  21                                   sql_stmt       => vc_sql,

15:50:40  22                                   language_flag  => dbms_sql.native,

15:50:40  23                                   parallel_level => 4); --執行任務,確定並行度

15:50:40  24 

15:50:40  25    --Controller

15:50:40  26    n_try    := 0;

15:50:40  27    n_status := dbms_parallel_execute.task_status(task_name => vc_task);

15:50:40  28    while (n_try < 2 and n_status != dbms_parallel_execute.FINISHED) loop

15:50:40  29      dbms_parallel_execute.resume_task(task_name => vc_task);

15:50:40  30      n_status := dbms_parallel_execute.task_status(task_name => vc_task);

15:50:40  31    end loop;

15:50:40  32 

15:50:40  33    --Deal with Result

15:50:40  34    dbms_parallel_execute.drop_task(task_name => vc_task);

15:50:40  35  end;

15:50:40  36  /

 

 

PL/SQL procedure successfully completed.

 

Elapsed: 00:03:50.78

15:58:05 SQL>

15:58:06 SQL> create index idx_t_id on t(object_id) nologging parallel 4;

 

Index created.

 

Elapsed: 00:01:35.12

16:00:05 SQL> alter index idx_t_id noparallel;

 

Index altered.

 

Elapsed: 00:00:00.07

16:00:15 SQL>

16:02:51 SQL> declare

16:02:52   2    vc_task  varchar2(100);

16:02:52   3    vc_sql   varchar2(1000);

16:02:52   4    n_try    number;

16:02:52   5    n_status number;

16:02:52   6  begin

16:02:52   7    --Define the Task

16:02:52   8    vc_task := 'Task 2: By Number Col';

16:02:52   9    dbms_parallel_execute.create_task(task_name => vc_task);

16:02:52  10 

16:02:52  11    --Define the Spilt

16:02:52  12    dbms_parallel_execute.create_chunks_by_number_col(task_name    => vc_task,

16:02:52  13                                                      table_owner  => 'LHR',

16:02:52  14                                                      table_name   => 'T',

16:02:52  15                                                      table_column => 'OBJECT_ID',

16:02:52  16                                                      chunk_size   => 100000); --定義chunk

 

16:02:53  17  16:02:53  18    vc_sql := 'update /*+ ROWID(dda) */ t set DATA_OBJECT_ID=object_id+1 where object_id between :start_id and :end_id';

16:02:53  19    --Run the task

16:02:53  20    dbms_parallel_execute.run_task(task_name      => vc_task,

16:02:53  21                                   sql_stmt       => vc_sql,

16:02:53  22                                   language_flag  => dbms_sql.native,

16:02:53  23                                   parallel_level => 4);

16:02:53  24 

16:02:53  25    --Controller

16:02:53  26    n_try    := 0;

16:02:53  27    n_status := dbms_parallel_execute.task_status(task_name => vc_task);

16:02:53  28    while (n_try < 2 and n_status != dbms_parallel_execute.FINISHED) loop

16:02:53  29      dbms_parallel_execute.resume_task(task_name => vc_task);

16:02:53  30      n_status := dbms_parallel_execute.task_status(task_name => vc_task);

16:02:53  31    end loop;

16:02:53  32 

16:02:53  33    --Deal with Result

16:02:53  34    dbms_parallel_execute.drop_task(task_name => vc_task);

16:02:53  35  end;

16:02:53  36  /

^Cdeclare

*

ERROR at line 1:

ORA-01013: user requested cancel of current operation

ORA-06512: at "SYS.DBMS_LOCK", line 201

ORA-06512: at "SYS.DBMS_PARALLEL_EXECUTE", line 44

ORA-06512: at "SYS.DBMS_PARALLEL_EXECUTE", line 390

ORA-06512: at "SYS.DBMS_PARALLEL_EXECUTE", line 417

ORA-06512: at line 20

 

 

Elapsed: 00:07:12.08

 

16:11:36 SQL>

16:11:36 SQL> EXEC   dbms_parallel_execute.drop_task(task_name => 'Task 2: By Number Col');

 

PL/SQL procedure successfully completed.

 

Elapsed: 00:00:00.11

16:31:53 SQL> declare

16:32:05   2    vc_task   varchar2(100);

16:32:05   3    vc_sql    varchar2(1000);

16:32:05   4    vc_sql_mt varchar2(1000);

16:32:05   5    n_try     number;

16:32:05   6    n_status  number;

16:32:05   7  begin

16:32:05   8    --Define the Task

16:32:05   9    vc_task := 'Task 3: By SQL';

16:32:05  10    dbms_parallel_execute.create_task(task_name => vc_task);

16:32:05  11 

16:32:05  12    --Define the Spilt

16:32:05  13    vc_sql_mt := 'select distinct object_id, object_id from t';

16:32:05  14    dbms_parallel_execute.create_chunks_by_SQL(task_name => vc_task,

16:32:05  15                                               sql_stmt  => vc_sql_mt,

16:32:05  16                                               by_rowid  => false);

16:32:05  17 

16:32:05  18    vc_sql := 'update /*+ ROWID(dda) */t set DATA_OBJECT_ID=object_id+1 where object_id between :start_id and :end_id';

16:32:05  19    --Run the task

16:32:05  20    dbms_parallel_execute.run_task(task_name      => vc_task,

16:32:05  21                                   sql_stmt       => vc_sql,

16:32:05  22                                   language_flag  => dbms_sql.native,

16:32:05  23                                   parallel_level => 4);

16:32:05  24 

16:32:05  25    --Controller

16:32:05  26    n_try    := 0;

16:32:05  27    n_status := dbms_parallel_execute.task_status(task_name => vc_task);

16:32:05  28    while (n_try < 2 and n_status != dbms_parallel_execute.FINISHED) loop

16:32:05  29      dbms_parallel_execute.resume_task(task_name => vc_task);

16:32:05  30      n_status := dbms_parallel_execute.task_status(task_name => vc_task);

16:32:05  31    end loop;

16:32:05  32 

16:32:05  33    --Deal with Result

16:32:05  34    dbms_parallel_execute.drop_task(task_name => vc_task);

16:32:05  35  end;

16:32:05  36  /

 

^Cdeclare

*

ERROR at line 1:

ORA-01013: user requested cancel of current operation

ORA-06512: at "SYS.DBMS_PARALLEL_EXECUTE_INTERNAL", line 634

ORA-06512: at "SYS.DBMS_PARALLEL_EXECUTE", line 163

ORA-06512: at line 14

 

 

Elapsed: 00:01:09.08

 

16:33:14 SQL>  EXEC   dbms_parallel_execute.drop_task(task_name => 'Task 3: By SQL');

 

PL/SQL procedure successfully completed.

 

1.4.2.1  相關字典檢視查詢

一、 create_chunks_by_rowid過程

 

SELECT * FROM DBA_PARALLEL_EXECUTE_TASKS;

wpsE43D.tmp 

 

 

SELECT * FROM DBA_PARALLEL_EXECUTE_CHUNKS;

wpsE43E.tmp 

 

 

SELECT count(1) FROM DBA_PARALLEL_EXECUTE_CHUNKS;

 

wpsE43F.tmp 

 

 

 select status, count(*) from user_parallel_execute_chunks group by status;

wpsE440.tmp 

 

 

select D.owner,D.job_name,D.JOB_STYLE,D.JOB_TYPE,D.JOB_ACTION from dba_scheduler_jobs d where d.owner='LHR';

wpsE441.tmp 

 

 

告警日誌:

Wed Jun 03 15:53:48 2015

Archived Log entry 1202 added for thread 1 sequence 2669 ID 0x6779dfc4 dest 1:

Thread 1 advanced to log sequence 2671 (LGWR switch)

  Current log# 4 seq# 2671 mem# 0: /app/oracle/flash_recovery_area/CNYDB/onlinelog/o1_mf_4_bpxd8g7v_.log

Wed Jun 03 15:53:49 2015

Archived Log entry 1203 added for thread 1 sequence 2670 ID 0x6779dfc4 dest 1:

Wed Jun 03 15:53:57 2015

Thread 1 advanced to log sequence 2672 (LGWR switch)

  Current log# 5 seq# 2672 mem# 0: /app/oracle/flash_recovery_area/CNYDB/onlinelog/o1_mf_5_bpxdbwdz_.log

Wed Jun 03 15:53:58 2015

Archived Log entry 1204 added for thread 1 sequence 2671 ID 0x6779dfc4 dest 1:

Thread 1 advanced to log sequence 2673 (LGWR switch)

  Current log# 1 seq# 2673 mem# 0: /app/oracle/oradata/CNYDB/redo01.log

Wed Jun 03 15:54:04 2015

Archived Log entry 1205 added for thread 1 sequence 2672 ID 0x6779dfc4 dest 1:

Thread 1 advanced to log sequence 2674 (LGWR switch)

  Current log# 6 seq# 2674 mem# 0: /app/oracle/flash_recovery_area/CNYDB/onlinelog/o1_mf_6_bpxdcjx2_.log

Wed Jun 03 15:54:05 2015

Archived Log entry 1206 added for thread 1 sequence 2673 ID 0x6779dfc4 dest 1:

 

 

由告警日誌可以看出redo切換非常迅速,歸檔來不及,所以還是需要在空閒的時候來做實驗。

 

 

 

 

 

二、 create_chunks_by_number_col過程

SELECT * FROM DBA_PARALLEL_EXECUTE_CHUNKS;

wpsE451.tmpwpsE452.tmp 

 

SELECT * FROM DBA_PARALLEL_EXECUTE_TASKS;

 

 

 

wpsE453.tmp 

 

 

 

select status, count(*) from dba_parallel_execute_chunks group by status;

wpsE454.tmp 

 

 

 select sid, serial#, status, PROGRAM, SQL_ID, event from v$session where action like 'TASK$%';

wpsE455.tmp 

 

 

 

 

 

select D.owner,D.job_name,D.JOB_STYLE,D.JOB_TYPE,D.JOB_ACTION from dba_scheduler_jobs d where d.owner='LHR';

wpsE466.tmp 

 

 

 

 

1.4.3  實驗總結

 

由實驗可以看出,採用dbms_parallel_execute.create_chunks_by_rowid方法,4千萬的資料量大約4G大小的表更新完大約4分鐘,這個速度還是可以的,另外2種方式更新下來速度太慢就沒有測試了,具體可以參考這裡:http://blog.itpub.net/26736162/viewspace-1683912/http://blog.itpub.net/26736162/viewspace-1683913/

 

 

1.4.4  實驗指令碼

 

1.4.4.1  create_chunks_by_rowid方式

declare

  vc_task  varchar2(100);

  vc_sql   varchar2(1000);

  n_try    number;

  n_status number;

begin

  --Define the Task

  vc_task := 'Task 1: By Rowid'; --Task名稱

  dbms_parallel_execute.create_task(task_name => vc_task); --手工定義一個Task任務;

 

  --Define the Spilt

  dbms_parallel_execute.create_chunks_by_rowid(task_name   => vc_task,

                                               table_owner => 'LHR',

                                               table_name  => 'T',

                                               by_row      => true,

                                               chunk_size  => 10000); --定義Chunk

 

  vc_sql := 'update /*+ ROWID(dda) */ t set DATA_OBJECT_ID=object_id+1 where rowid between :start_id and :end_id';

  --Run the task

  dbms_parallel_execute.run_task(task_name      => vc_task,

                                 sql_stmt       => vc_sql,

                                 language_flag  => dbms_sql.native,

                                 parallel_level => 4); --執行任務,確定並行度

 

  --Controller

  n_try    := 0;

  n_status := dbms_parallel_execute.task_status(task_name => vc_task);

  while (n_try < 2 and n_status != dbms_parallel_execute.FINISHED) loop

    dbms_parallel_execute.resume_task(task_name => vc_task);

    n_status := dbms_parallel_execute.task_status(task_name => vc_task);

  end loop;

 

  --Deal with Result

  dbms_parallel_execute.drop_task(task_name => vc_task);

end;

/

 

1.4.4.2  create_chunks_by_number_col

declare

  vc_task  varchar2(100);

  vc_sql   varchar2(1000);

  n_try    number;

  n_status number;

begin

  --Define the Task

  vc_task := 'Task 2: By Number Col';

  dbms_parallel_execute.create_task(task_name => vc_task);

 

  --Define the Spilt

  dbms_parallel_execute.create_chunks_by_number_col(task_name    => vc_task,

                                                    table_owner  => 'LHR',

                                                    table_name   => 'T',

                                                    table_column => 'OBJECT_ID',

                                                    chunk_size   => 10000); --定義chunk

 

  vc_sql := 'update /*+ ROWID(dda) */ t set DATA_OBJECT_ID=object_id+1 where object_id between :start_id and :end_id';

  --Run the task

  dbms_parallel_execute.run_task(task_name      => vc_task,

                                 sql_stmt       => vc_sql,

                                 language_flag  => dbms_sql.native,

                                 parallel_level => 4);

 

  --Controller

  n_try    := 0;

  n_status := dbms_parallel_execute.task_status(task_name => vc_task);

  while (n_try < 2 and n_status != dbms_parallel_execute.FINISHED) loop

    dbms_parallel_execute.resume_task(task_name => vc_task);

    n_status := dbms_parallel_execute.task_status(task_name => vc_task);

  end loop;

 

  --Deal with Result

  dbms_parallel_execute.drop_task(task_name => vc_task);

end;

/

 

1.4.4.3  create_chunks_by_SQL

declare

  vc_task   varchar2(100);

  vc_sql    varchar2(1000);

  vc_sql_mt varchar2(1000);

  n_try     number;

  n_status  number;

begin

  --Define the Task

  vc_task := 'Task 3: By SQL';

  dbms_parallel_execute.create_task(task_name => vc_task);

 

  --Define the Spilt

  vc_sql_mt := 'select distinct object_id, object_id from t';

  dbms_parallel_execute.create_chunks_by_SQL(task_name => vc_task,

                                             sql_stmt  => vc_sql_mt,

                                             by_rowid  => false);

 

  vc_sql := 'update /*+ ROWID(dda) */t set DATA_OBJECT_ID=object_id+1 where object_id between :start_id and :end_id';

  --Run the task

  dbms_parallel_execute.run_task(task_name      => vc_task,

                                 sql_stmt       => vc_sql,

                                 language_flag  => dbms_sql.native,

                                 parallel_level => 4);

 

  --Controller

  n_try    := 0;

  n_status := dbms_parallel_execute.task_status(task_name => vc_task);

  while (n_try < 2 and n_status != dbms_parallel_execute.FINISHED) loop

    dbms_parallel_execute.resume_task(task_name => vc_task);

    n_status := dbms_parallel_execute.task_status(task_name => vc_task);

  end loop;

 

  --Deal with Result

  dbms_parallel_execute.drop_task(task_name => vc_task);

end;

/




 Oracle中如何更新一張大表記錄 

 

SQL語句是一種方便的語言,同樣也是一種“迷惑性”的語言。這個主要體現在它的集合操作特性上。無論資料表資料量是1條,還是1億條,更新的語句都是完全相同。但是,實際執行結果(或者能否出現結果)卻是有很大的差異。

筆者在開發DBA領域的一個理念是:作為開發人員,對資料庫、對資料要有敬畏之心,一個語句發出之前,起碼要考慮兩個問題:目標資料表的總資料量是多少(投產之後)?你這個操作會涉及到多大的資料量?不同的回答,處理的方案其實是不同的。

更新大表資料,是我們在開發和運維,特別是在資料遷移領域經常遇到的一種場景。上面兩個問題的回答是:目標資料表整體就很大,而且更新範圍也很大。一個SQL從理論上可以處理。但是在實際中,這種方案會有很多問題。

本篇主要介紹幾種常見的大表處理策略,並且分析出他們的優劣。作為我們開發人員和DBA,選取的標準也是靈活的:根據你的操作型別(運維操作還是系統日常作業)、程式執行環境(硬體環境是否支援並行)和程式設計環境(是否可以完全獨佔所有資源)來綜合考量決定。

首先,我們需要準備出一張大表。

 

1、環境準備

 

我們選擇Oracle 11.2版本進行試驗。

 

 

SQL> select * from v$version;

 

BANNER

--------------------------------------------------------------------------------

Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production

PL/SQL Release 11.2.0.1.0 - Production

CORE     11.2.0.1.0     Production

 

TNS for Linux: Version 11.2.0.1.0 - Production

NLSRTL Version 11.2.0.1.0 – Production

 

 

準備一張大表。

 

 

SQL> create table t as select * from dba_objects;

Table created

 

SQL> insert into t select * from t;

72797 rows inserted

 

SQL> insert into t select * from t;

145594 rows inserted

 

(篇幅原因,中間過程略……

SQL> commit;

Commit complete

 

 

SQL> select bytes/1024/1024/1024 from dba_segments where owner='SYS' and segment_name='T';

 

BYTES/1024/1024/1024

--------------------

 1.0673828125

 

SQL> select count(*) from t;

 

COUNT(*)

----------

 9318016

 

Executed in 14.711 seconds

 

 

資料表T作為資料來源,一共包括9百多萬條記錄,合計空間1G左右。筆者實驗環境是在虛擬機器上,一顆虛擬CPU,所以後面進行並行Parallel操作的方案就是示意性質,不具有代表性。

下面我們來看最簡單的一種方法,直接update

 

2、方法1:直接Update

 

最簡單,也是最容易出問題的方法,就是“不管三七二十一”,直接update資料表。即使很多老程式設計師和DBA,也總是選擇出這樣的策略方法。其實,即使結果能出來,也有很大的僥倖成分在其中。

我們首先看筆者的實驗,之後討論其中的原因。先建立一張實驗資料表t_target

 

 

SQL> create table t_targettablespace users as select * from t;

Table created

 

 

SQL> update t_target set owner=to_char(length(owner));

(長時間等待……

 

 

在等待期間,筆者發現如下幾個現象:

ü  資料庫伺服器執行速度奇慢,很多連線操作速度減緩,一段時間甚至無法登陸;

ü  後臺會話等待時間集中在資料讀取、log space buffer、空間分配等事件上;

ü  長期等待,作業系統層面開始出現異常。Undo表空間膨脹;

ü  日誌切換頻繁;

此外,選擇這樣策略的朋友還可能遇到:前臺錯誤丟擲異常、客戶端連線被斷開等等現象。

筆者遇到這樣的場景也是比較糾結,首先,長時間等待(甚至一夜)可能最終沒有任何結果。最要命的是也不敢輕易的撤銷操作,因為Oracle要進行update操作的回滾動作。一個小時之後,筆者放棄。

 

 

updatet_target set owner=to_char(length(owner))

ORA-01013: 使用者請求取消當前的操作

(接近一小時未完成)

 

 

之後就是相同時間的rollback等待,通常是事務執行過多長時間,回滾進行多長時間。期間,可以透過x$ktuxe後臺內部表來觀察、測算回滾速度。這個過程中,我們只有“乖乖等待”。

 

 

SQL> select KTUXESIZ from x$ktuxe where KTUXESTA<>'INACTIVE';

 

  KTUXESIZ

----------

     62877

……

 

SQL> select KTUXESIZ from x$ktuxe where KTUXESTA<>'INACTIVE';

 

  KTUXESIZ

----------

       511

 

 

綜合這種策略的結果通常是:同業抱怨(影響了他們的作業執行)、提心吊膽(不知道執行到哪裡了)、資源耗盡(CPU、記憶體或者IO佔到滿)、勞而無功(最後還是被rollback)。如果是正式投產環境,還要承擔影響業務生產的責任。

我們詳細分析一下這種策略的問題:

首先,我們需要承認這種方式的優點,就是簡單和片面的高效。相對於在本文中其他介紹的方法,這種方式程式碼量是最少的。而且,這種方法一次性的將所有的任務提交給資料庫SQL引擎,可以最大程度的發揮系統一個方面(CPUIO或者記憶體)的能力。

如果我們的資料表比較小,經驗值在幾萬一下,這種方法是比較合適的。我們可以考慮使用。

另一方面,我們要看到Oracle Update的另一個方面,就是UndoRedo和程式工作負載的問題。熟悉Oracle的朋友們知道,在DML操作的時候,UndoRedo是非常重要的方面。當我們在UpdateDelete資料的時候,資料塊被修改之前的“前映象”就會儲存在Undo Tablespace裡面。注意:Undo Tablespace是一種特殊的表空間,需要儲存在磁碟上。Undo的存在主要是為了支援資料庫其他會話的“一致讀”操作。只要事務沒有被commit或者rollbackUndo資料就會一直保留在資料庫中,而且不能被“覆蓋”。

Redo記錄了進行DML操作的“後映象”,Redo生成是和我們修改的資料量相關。現實問題要修改多少條記錄,生成的Redo總量是不變的,除非我們嘗試nologging選項。Redo單個日誌成員如果比較小,Oracle應用生成Redo速度比較大。Redo Group切換頻度高,系統中就面臨著大量的日誌切換或者Log Space Buffer相關的等待事件。

如果我們選擇第一種方法,Undo表空間就是一個很大的瓶頸。大量的前映象資料儲存在Undo表空間中不能釋放,繼而不斷的引起Undo檔案膨脹。如果Undo檔案不允許膨脹(autoextend=no),Oracle DML操作會在一定時候報錯。即使允許進行膨脹,也會伴隨大量的資料檔案DBWR寫入動作。這也就是我們在進行大量update的時候,在event等待事件中能看到很多的DBWR寫入。因為,這些寫入中,不一定都是更新你的資料表,裡面很多都是Undo表空間寫入。

同時,長時間的等待操作,觸動OracleOS的負載上限,很多奇怪的事情也可能出現。比如程式僵死、連線被斷開。

這種方式最大的問題在於rollback動作。如果我們在長時間的事務過程中,發生一些異常報錯,通常是由於資料異常,整個資料需要回滾。回滾是Oracle自我保護,維持事務完整性的工具。當一個長期DML update動作發生,中斷的時候,Oracle就會進入自我的rollback階段,直至最後完成。這個過程中,系統是比較執行緩慢的。即使重啟伺服器,rollback過程也會完成。

所以,這種方法在處理大表的時候,一定要慎用!!起碼要評估一下風險。

 

3、方法2PL/SQL匿名塊

 

上面方法1的最大問題在於“一次性瞬間壓力”大。無論是Undo量、還是Rollback量,都是有很大的問題。即使我們的系統能夠支援這樣的操作,如果update過程中存在其他的作業,必然受到影響。

PL/SQL匿名塊的原則在於平穩負載,分批的進行處理。這個過程需要使用bulk collect批次操作,進行遊標操作。

我們首先還原實驗環境。

 

 

SQL> truncate table t_target;

Table truncated

 

SQL> insert /*+append*/into t_target select * from t;

9318016 rows inserted

 

SQL> commit;

Commit complete

 

 

程式碼片段。

 

 

SQL> set timing on;

SQL> declare

  2    type rowid_list is table of urowid index by binary_integer;

  3    rowid_infosrowid_list;

  4    i number;

  5    cursor c_rowids is select rowid from t_target;

6  begin

  7    open c_rowids;

  8 

  9    loop

 10      fetch c_rowidsbulk collect into rowid_infos limit 2000;

 11 

 12      foralli in 1..rowid_infos.count

 13         update t_target set owner=to_char(length(owner)+1)

 14             where rowid=rowid_infos(i);

 15 

 16      commit;

 17      exit when rowid_infos.count<2000;

 18    end loop;

 19    close c_rowids;

20  end;

21  /

 

PL/SQL procedure successfully completed

 

Executed in 977.081 seconds

 

 

我們在977s完成了整個操作。這個方法有幾個特點:首先是批次的獲取bulk collect指定數量更新批次。第二個是使用forall的方法批次更新,減少引擎之間的切換。第三是更新一批之後,commit操作。

這樣的好處在於平穩化undo使用。如果資料量少,這種程式碼方法可能比直接update要慢。但是如果資料量大,特別是海量資料情況下,這種方法是可以支援非常大的資料表更新的。

程式碼中update操作,使用rowid,如果有其他業務方面的主鍵也可以使用替換。

在程式設計實踐中,有時候我們可能不能使用PL/SQL程式碼片段,只能使用SQL語句。這種時候就需要結合業務方面有沒有特點可以使用?這種時候往往也就考驗開發人員對業務特點的理解了。

在使用forall的時候,要注意一批更新的數量。根據一些Oracle文獻透露,內部SQL引擎在update的時候,也是分批進行的,每批大約1000條記錄。經驗告訴我們每批數量在1-5萬比較合適。

這是第二種方法,下面我們介紹一種簡單、可行的手段,比較方便。

 

4、方法3insert append方法

 

UndoRedo的角度看,我們更喜歡insert,特別是生成較少redonologgingappend插入。UpdateDelete操作,都會生成Undo記錄,在我們看來,都是可以想辦法減少的方法。

這種方法的思路是:利用insert,將原來的資料表插入到一個新建立的資料表。在insert過程中,整理column的取值,起到update相同的效果。

下面是實驗過程。我們先建立資料表,注意可以設定nologging屬性

 

 

SQL> create tablet_renamenologging as select * from t_target where 1=0;

Table created

 

Executed in 0.889 seconds

 

 

在這個過程中,我們建立的是一個空表。之後就可以插入資料,這種方法比較麻煩的地方,就是需要在insert指令碼中列出所有的資料列。當然,借用一些工具技巧,這個過程也可以很簡單。

 

 

SQL> insert /*+append*/into t_rename

2  selectto_char(length(owner)) owner,

3  OBJECT_NAME,

4  SUBOBJECT_NAME,

5  OBJECT_ID,

6  DATA_OBJECT_ID,

7  OBJECT_TYPE,

8  CREATED,

9  LAST_DDL_TIME,

10  TIMESTAMP,

11  STATUS,

12  TEMPORARY,

13  GENERATED,

14  SECONDARY,

15  NAMESPACE,

16  EDITION_NAME from t_target;

 

9318016 rows inserted

 

Executed in 300.333 seconds

 

 

使用append操作,可以減少redo log的生成。從結果看,一共執行了300s左右,效率應該是比較好的。

之後,提交事務。將原來的資料表刪除,將新資料表rename成原有資料表名稱。

 

 

SQL> commit;

Commit complete

 

Executed in 0.031 seconds

 

SQL> drop table t_target purge;

Table dropped

 

Executed in 1.467 seconds

 

SQL> rename t_rename to t_target;

Table renamed

 

Executed in 0.499 seconds

 

SQL> select count(*) from t_target;

COUNT(*)

----------

   9318016

 

Executed in 14.336 seconds

 

 

最後,可以將nologging屬性修改回來,將資料表約束新增上。

 

 

SQL> alter table t_target logging;

Table altered

 

Executed in 0.015 seconds

 

 

這種方法的好處在於效率,在資料量維持中高的情況下,這種方法速度是比較吸引人的。但是,這種方式也要消耗更多的儲存空間。在儲存空間允許的情況下,可以用這種方法。

如果資料量更大,達到海量的程度,比如幾十G的資料表,這種方法就值得考量一下了。需要結合硬體環境和執行環境完成。另外,這種方法涉及到資料表的建立等運維工作特性,故不適合在應用程式中使用,適合在運維人員過程中使用。

還有,就是這種方法的實際對備份的影響。由於我們使用了nologging+append選項,生成的redo log數量是不足以進行還原的,所以如果要實現完全恢復的話,資料庫實際上是失去了連續還原的依據。因此,如果真正使用了這個方法在生產環境下,之後需要進行資料庫全備份操作。

如果資料庫版本為11.2以上,我們可以使用Oracle的一個新特性dbms_parallel_execute包,進行資料表並行更新。詳見下面介紹。

 

5、方法3dbms_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提供了三種方法,rowidcolumn_valueSQL,分別按照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_idend_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語句,是一種典型不負責任的行為,需要避免杜絕。




使用11g dbms_parallel_execute執行並行更新(上) 

 

海量資料處理,是很多系統開發人員,有時候甚至是運維人員,經常面對的需求。介面海量資料檔案載入入庫、批次資料更新、階段資料歸檔刪除是我們經常遇到的應用需求。針對不同的實際情況,包括軟硬體、運維環境、SLA視窗期要求,我們需要採用不同的策略和方法進行問題解決。

在筆者之前文章《Oracle中如何更新一張大表記錄》(http://blog.itpub.net/17203031/viewspace-1061065/)中,介紹了以Oracle資料庫端為中心,進行大表資料處理過程中的一些方法和考慮因素。簡單的說,海量資料處理難點不在語句層面,而在如何平衡各種需求因素。比較常見的因素有如下:

 

ü  業務系統正常生產衝擊。大資料操作絕大多數場景是在生產環境。在7*24可用性需求日益強化的今天,業務系統一個SQL執行之後,影響減慢核心操作速度,嚴重甚至系統崩潰,絕對不是我們運維人員希望見到的;

ü  操作視窗期長短。在相同的業務操作量的情況下,平緩化操作負載一定是以增加操作時間作為前提的。增加延長操作時間是否能夠在維護視窗內完成,也是需要考量的問題;

ü  對資料一致性的影響。一些“流言”方法(如nologging),雖然可以減少操作負載,但是潛在會給系統備份連續性帶來災難影響;

 

此外,SQL語句本身最佳化,操作策略也會有一些可以提高的空間。但是,一些問題還是需要單純的大量資料處理。當其他常規手段出盡的時候,在硬體條件允許下,並行、併發操作往往是不錯的選擇。

11gR2中,Oracle為海量資料處理提供了很多方便的支援。工具包dbms_parallel_execute可以支援將海量資料分拆為獨立的chunk任務,並行執行作業。本篇就詳細介紹這個新特性的使用。

 

1、環境準備

 

實驗環境為11.2.0.3

 

SQL> select * from v$version;

BANNER

------------------------------------------

Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - Production

PL/SQL Release 11.2.0.3.0 - Production

CORE 11.2.0.3.0 Production

TNS for Linux: Version 11.2.0.3.0 - Production

NLSRTL Version 11.2.0.3.0 – Production

 

 

構造一張大表。說明:條件所限,筆者環境比較簡單,一些效能方面的優勢比較難體現出來。先建立出一個單獨表空間。

 

 

SQL> create tablespace test datafile size 2G autoextend on

  2  extent management local uniform size 1m

  3  segment space management auto;

Tablespace created

 

SQL> create table t as select * from dba_objects;

Table created

 

SQL> insert into t select * from t;

75586 rows inserted

(一系列的insert操作……

 

SQL> commit;

Commit complete

 

資料表T包括大約2千萬條記錄,佔用空間體積在2G左右。

 

SQL> select count(*) from t;

 

  COUNT(*)

----------

  19350016

 

SQL> select bytes/1024/1024/1024, tablespace_name from dba_segments where owner='SYS' and segment_name='T';

 

BYTES/1024/1024/1024 TABLESPACE_NAME

-------------------- ------------------------------

        2.0986328125 TEST

 

Dbms_parallel_execute並不是傳統的多程式並行操作,本質上是透過作業管理器Schedule來完成系列作業的(在後文中會詳細證明)。所以前提要求job_queue_processes引數設定不能為0

 

 

SQL> show parameter job

 

NAME                                 TYPE        VALUE

-------------------- ------------------------------

job_queue_processes                  integer     1000

 

2dbms_parallel_execute包執行介紹

 

Dbms_parallel_executeOracle 11g推出的一個全新並行操作介面。它的原理為:當Oracle需要處理一個大量資料處理,特別是update操作的時候,可以將其拆分為若干各chunk分塊,以多程式作業(Schedule Job)分塊執行操作。從而降低一次性undo的使用,更進一步的便於斷點續作。

Dbms_parallel_execute包使用要滿足兩個條件:

 

ü  執行程式使用者需要擁有create job系統許可權;

ü  Dbms_parallel_execute程式包執行中需要呼叫dbms_sql包的一些方法,所以也需要該程式包執行許可權;

 

並行包的執行有兩個問題需要呼叫者確定:chunk分割方法和並行作業程式個數。

傳統的單執行緒執行策略中,無論任務多大,都是對應一個Server Process進行處理。如果呼叫了並行,會有對應的協調程式和工作程式存在(v$px_process)。

如果啟用了並行執行,一個關鍵問題在於如何劃分任務,將一個資料表更新操作劃分為多個小資料集合操作。Dbms_parallel_execute包支援三種任務劃分方法。

 

ü  By_rowid方法:依據rowid將運算元據進行劃分;

ü  By_number_col方法:輸入定義一個數字列名稱,依據這個列的取值進行劃分;

ü  By_SQL語句方法:給一個SQL語句,使用者可以幫助定義出每次chunk的起始和終止id取值;

 

在三種方法中,筆者比較推薦rowid方法,理由是條件要求低、操作速度快。如果操作過程中沒有明確的對資料表作業,這種策略是首選。具體比較可以從下面的實驗中看出。

確定了劃分方法,還要確定每個chunk的大小。注意:這個chunk設定大小並不一定是每個chunk運算元據行的數量。針對不同的分割槽型別,有不同的策略。這個在下面實驗中筆者也會給出明確的解析。

並行程式個數表示的是當“一塊”任務被劃分為“一堆”相互獨立的任務集合之後,準備多少個工作程式進行工作。這個是並行包使用的關鍵,類似於並行度,是需要依據實際軟硬體資源負載情況綜合考慮。

長時間作業存在一個問題,就是呼叫使用者希望隨時瞭解執行情況。Oracle提供了兩個資料檢視user_parallel_execute_tasksuser_parallel_execute_chunks,分別檢視Task執行情況和各個chunk執行完成情況。

Oracle官方文件中,給出了呼叫dbms_parallel_execute包的方法流程,本文使用的也就是這個指令碼的變種,特此說明。下面,我們先看第一種by rowid方法。

 

3By Rowid劃分chunk方法

 

Oracle中的rowid是資料實際物理位置的表示。藉助rowid直接定位資料,是目前Oracle獲取資料最快的方法。所以在RBO中,第一執行計劃被確定為rowid訪問方式。

依據Oracle文件提供的PL/SQL匿名塊,修改處我們第一個rowid範圍查詢。

 

declare

  vc_task varchar2(100);

  vc_sql varchar2(1000);

  n_try number;

  n_status number;

begin

  --Define the Task

  vc_task := 'Task 1: By Rowid';  --Task名稱

  dbms_parallel_execute.create_task(task_name => vc_task); --手工定義一個Task任務;

 

  --Define the Spilt

  dbms_parallel_execute.create_chunks_by_rowid(task_name => vc_task,

                                               table_owner => 'SYS',

                                               table_name => 'T',

                                               by_row => true,

                                               chunk_size => 1000); --定義Chunk

                                              

  vc_sql := 'update /*+ ROWID(dda) */t set DATA_OBJECT_ID=object_id+1 where rowid between :start_id and :end_id';

  --Run the task

  dbms_parallel_execute.run_task(task_name => vc_task,

                                 sql_stmt => vc_sql,

                                 language_flag => dbms_sql.native,

                                 parallel_level => 2); --執行任務,確定並行度

 

  --Controller

  n_try := 0;

  n_status := dbms_parallel_execute.task_status(task_name => vc_task);

  while (n_try<2 and n_status != dbms_parallel_execute.FINISHED) loop

     dbms_parallel_execute.resume_task(task_name => vc_task);

     n_status := dbms_parallel_execute.task_status(task_name => vc_task);

  end loop;        

 

  --Deal with Result

  dbms_parallel_execute.drop_task(task_name => vc_task);                                   

end;

/

 

從呼叫過程來看,這個並行操作包括下面幾個步驟:

 

ü  定義Task

ü  確定chunk劃分方法,定義每個chunk的範圍資訊;

ü  執行作業,確定並行作業程式數量;

 

這個呼叫過程和我們常見的並行方式有很大差異,類似於OracleJob Schedule機制。由於執行過程比較長,我們可以有比較從容的檢視並行執行包的情況。

user_parallel_execute_tasks中,看到當前作業的關鍵資訊。注意:chunk_type表示的是採用什麼樣的劃分方法。JOB_PREFIX對應的則是Schedule中的內容。

 

SQL> select task_name, chunk_type, JOB_PREFIX from user_parallel_execute_tasks;

 

TASK_NAME            CHUNK_TYPE   JOB_PREFIX

-------------------- ------------ ------------------------------

Task 1: By Rowid     ROWID_RANGE  TASK$_4

 

user_parallel_execute_chunks中,作業的所有chunk劃分,每個chunk對應的一行資料。其中包括這個chunk的起始和截止rowid。對應的chunk取值對應的就是每個chunk的資料行數

 

SQL> select chunk_id, task_name, status, start_rowid, end_rowid from user_parallel_execute_chunks where rownum<10;

 

  CHUNK_ID TASK_NAME            STATUS               START_ROWID        END_ROWID

---------- -------------------- -------------------- ------------------ ------------------

         1 Task 1: By Rowid     PROCESSED            AAATLKAAHAAAACAAAA AAATLKAAHAAAACxCcP

         2 Task 1: By Rowid     PROCESSED            AAATLKAAHAAAACyAAA AAATLKAAHAAAADjCcP

         3 Task 1: By Rowid     PROCESSED            AAATLKAAHAAAADkAAA AAATLKAAHAAAAD/CcP

         4 Task 1: By Rowid     PROCESSED            AAATLKAAHAAAAEAAAA AAATLKAAHAAAAExCcP

         5 Task 1: By Rowid     PROCESSED            AAATLKAAHAAAAEyAAA AAATLKAAHAAAAFjCcP

         6 Task 1: By Rowid     PROCESSED            AAATLKAAHAAAAFkAAA AAATLKAAHAAAAF/CcP

         7 Task 1: By Rowid     PROCESSED            AAATLKAAHAAAAGAAAA AAATLKAAHAAAAGxCcP

         8 Task 1: By Rowid     PROCESSED            AAATLKAAHAAAAGyAAA AAATLKAAHAAAAHjCcP

         9 Task 1: By Rowid     PROCESSED            AAATLKAAHAAAAHkAAA AAATLKAAHAAAAH/CcP

 

9 rows selected

 

作為user_parallel_execute_chunks,一個很重要的欄位就是status狀態列,用於標註每個chunk的處理情況。我們可以依據這個欄位來判斷任務完成情況。

 

SQL> select status, count(*) from user_parallel_execute_chunks group by status;

 

STATUS                 COUNT(*)

-------------------- ----------

ASSIGNED                      2

UNASSIGNED                 5507

PROCESSED                   938

 

(過一會之後…….

SQL> select status, count(*) from user_parallel_execute_chunks group by status;

 

STATUS                 COUNT(*)

-------------------- ----------

ASSIGNED                      2

UNASSIGNED                 5441

PROCESSED                  1004

 

status欄位,我們可以分析出並行作業工作的原理。每一個chunk記錄在劃分之後,都是設定為unassiged狀態,包括起始和終止的id資訊(rowid或者column_range)。每次處理的chunkassigned狀態,實驗程式中我們設定parallel_level2,所以每次都是2chunkassigned狀態。處理結束之後,設定為processed狀態。

海量資料更新最大的問題在於undo擴充的量,我們檢查一下執行過程中的undo size情況。

 

 

SQL> select sum(bytes)/1024/1024 from dba_undo_extents where status='ACTIVE';

 

SUM(BYTES)/1024/1024

--------------------

 

SQL> select sum(bytes)/1024/1024 from dba_undo_extents where status='ACTIVE';

 

SUM(BYTES)/1024/1024

--------------------

                  16

 

SQL> select sum(bytes)/1024/1024 from dba_undo_extents where status='ACTIVE';

 

SUM(BYTES)/1024/1024

--------------------

                  10

 

每次的資料量都不大,說明每次都是一小塊chunk的操作。也確定使用parallel執行的過程,是分步小塊commit的過程。在job檢視中,我們也可以明確的看出作為作業的資訊。

 

SQL> select owner, job_name, JOB_ACTION, SCHEDULE_TYPE, state, last_start_date from dba_scheduler_jobs where job_name like 'TASK$_4%';

 

OWNER   JOB_NAME  JOB_ACTION                                 SCHEDULE_TYPE STATE           LAST_START_DATE

------- ----------- ------------------------------------------ ------------- --------------- -----------------------------------

SYS     TASK$_4_2  DBMS_PARALLEL_EXECUTE.RUN_INTERNAL_WORKER  IMMEDIATE     RUNNING         10-2 -14 01.48.34.947417 下午 PRC

SYS     TASK$_4_1  DBMS_PARALLEL_EXECUTE.RUN_INTERNAL_WORKER  IMMEDIATE     RUNNING         10-2 -14 01.48.34.730487 下午 PRC

 

注意:傳統的並行程式v$px_process中沒有看到資料資訊,說明並行程式包並不是Oracle傳統的資料庫並行方案。

 

SQL> select * from v$px_process;

 

SERVER_NAME STATUS           PID SPID                            SID    SERIAL#

----------- --------- ---------- ------------------------ ---------- ----------

 

執行結束資訊:

 

25    --Controller

 26    n_try := 0;

 27    n_status := dbms_parallel_execute.task_status(task_name => vc_task);

 28    while (n_try<2 and n_status != dbms_parallel_execute.FINISHED) loop

 29       dbms_parallel_execute.resume_task(task_name => vc_task);

 30       n_status := dbms_parallel_execute.task_status(task_name => vc_task);

 31    end loop;

 32 

 33    --Deal with Result

 34    dbms_parallel_execute.drop_task(task_name => vc_task);

 35  end;

 36  /

 

PL/SQL procedure successfully completed

 

Executed in 944.453 seconds

 

更新2G資料一共使用945s,合計約16分鐘。

從上面的資料檢視和呼叫過程,我們可以得到如下結論:

dbms_parallel_execute執行包而言,透過確定chunk方法和chunk size,可以將一個很大的資料集合劃分為若干各小chunk集合,分步進行操作處理。程式碼中設定的parallel_level,體現在設定Job的個數上。啟動作業任務後,Oracle並不是啟動傳統的並行機制,而是在Job Schedule的基礎上建立parallel_level個數的作業,型別為立即執行。多個作業分別執行各個chunk的小塊工作。使用Job Schedule的一個好處在於可以方便的進行作業resumestart過程。

下面我們討論by number colby SQL兩種執行方法。





 

上篇我們討論了dbms_parallel_execute的工作方法、使用流程和特點。本篇繼續來討論其他兩種劃分Chunk方式。說明:對每種劃分策略執行過程中,筆者都進行了不同的實驗,來說明其工作特點。

 

4By Number Col劃分Chunk方法

 

應該說,使用rowid進行資料表劃分可以帶來很多好處。每個chunk資料獲取過程,本質上就是執行一個範圍Range操作。對於rowid而言,直接透過範圍檢索的效率是相當高的。

Rowid方法對應兩種策略都是依據“資料表列範圍”進行chunk劃分。By Number Col的方法顧名思義,需要我們指定出一個數字型別列名稱。Oracle會依據這個列取值進行劃分。每個chunk實際上都是透過數字型別檢索到的結果集合進行處理。

當然,這個過程必然伴隨著我們對於“地勢”條件的依賴。每次從上千萬條記錄中,FTS的檢索出一個chunk資料顯然是很費力的操作過程。最直接的最佳化手段就是索引和分割槽。注意:如果我們沒有特殊的條件進行chunk劃分輔助,一定要考慮by number col方式是否適合。

 

SQL> create index idx_t_id on t(object_id);

Index created

Executed in 107.282 seconds

 

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true,degree => 2);

PL/SQL procedure successfully completed

Executed in 87.453 seconds

 

修改的指令碼如下:

 

SQL> declare

  2    vc_task varchar2(100);

  3    vc_sql varchar2(1000);

  4    n_try number;

  5    n_status number;

  6  begin

  7    --Define the Task

  8    vc_task := 'Task 2: By Number Col';

  9    dbms_parallel_execute.create_task(task_name => vc_task);

 10 

 11    --Define the Spilt

 12    dbms_parallel_execute.create_chunks_by_number_col(task_name => vc_task,

 13                                                      table_owner => 'SYS',

 14                                                      table_name => 'T',

 15                                                      table_column => 'OBJECT_ID',

 16                                                      chunk_size => 1000); --定義chunk

 17 

 18    vc_sql := 'update /*+ ROWID(dda) */t set DATA_OBJECT_ID=object_id+1 where object_id between :start_id and :end_id';

 19    --Run the task

 20    dbms_parallel_execute.run_task(task_name => vc_task,

 21                                   sql_stmt => vc_sql,

 22                                   language_flag => dbms_sql.native,

 23                                   parallel_level => 1);

 24 

 25    --Controller

 26    n_try := 0;

 27    n_status := dbms_parallel_execute.task_status(task_name => vc_task);

 28    while (n_try<2 and n_status != dbms_parallel_execute.FINISHED) loop

 29       dbms_parallel_execute.resume_task(task_name => vc_task);

 30       n_status := dbms_parallel_execute.task_status(task_name => vc_task);

 31    end loop;

 32 

 33    --Deal with Result

 34    dbms_parallel_execute.drop_task(task_name => vc_task);

 35  end;

 36  /

 

從執行流程上看,上面指令碼和by rowid方式沒有顯著地差異。最大的區別在於定義chunk時呼叫的方法,引數包括指定的資料表、列名和chunk size注意:我們這裡定義了chunk size1000,但是在執行過程中,我們不能保證每個chunk的大小是1000。這個結論我們在後面的闡述實驗中可以證明。

執行指令碼的速度顯著的比by rowid的慢了很多。但是我們也能發現很多技術細節。首先,我們會有一個時期,在chunk檢視中沒有結果返回。

 

SQL> select task_name, chunk_type, status from user_parallel_execute_tasks;

 

TASK_NAME                 CHUNK_TYPE   STATUS

------------------------- ------------ -------------------

Task 2: By Number Col     NUMBER_RANGE CHUNKING

Executed in 0.61 seconds

 

SQL> select status, count(*) from user_parallel_execute_chunks group by status;

 

STATUS                 COUNT(*)

-------------------- ----------

 

在之後,我們才能檢視到chunk處理情況。

 

 

SQL> select task_name, chunk_type, status from user_parallel_execute_tasks;

 

TASK_NAME                 CHUNK_TYPE   STATUS

------------------------- ------------ -------------------

Task 2: By Number Col     NUMBER_RANGE PROCESSING

 

SQL> select status, count(*) from user_parallel_execute_chunks group by status;

 

STATUS                 COUNT(*)

-------------------- ----------

ASSIGNED                      1

UNASSIGNED                 1557

PROCESSED                    13

 

這個現象說明:對dbms_parallel_execute包處理過程來說,包括兩個重要的步驟,Chunk分塊步驟和Chunk處理步驟。無論是哪種分塊方法,Oracle都是首先依據分割原則,將任務拆分開來,規劃在任務檢視裡面。之後再進行分作業JobProcessing處理過程。

by rowid方式中的rowid Range資訊一樣,我們在chunk檢視中也是可以看到數字列範圍的資訊。

 

SQL> select task_name, status, start_id, end_id, job_name from user_parallel_execute_chunks where rownum<5;

 

TASK_NAME                 STATUS                 START_ID     END_ID JOB_NAME

------------------------- -------------------- ---------- ---------- ------------------------------

Task 2: By Number Col     PROCESSED                 25002      26001 TASK$_5_2

Task 2: By Number Col     ASSIGNED                  26002      27001 TASK$_5_1

Task 2: By Number Col     ASSIGNED                  27002      28001 TASK$_5_2

Task 2: By Number Col     UNASSIGNED                28002      29001

 

注意:我們此處看到的chunk範圍是1000,由於資料準備過程,範圍1000絕對不意味著每個chunk的大小是1000。所以,我們也就可以推斷出,呼叫方法中的chunk sizenumber col方式中,是取值範圍的大小。

直觀的想,Oracle選取這樣的策略也是有依據的:Oracle可以直接選取一個最小和最大的資料列值,依次chunk取值範圍進行分割。這樣做可減少對資料檢索的壓力。

在執行過程中,我們跟蹤了執行會話的SQL語句,從shared pool中抽取出執行計劃。

 

SQL> select * from table(dbms_xplan.display_cursor(sql_id=>'f2z147unc1n3q'));

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

SQL_ID  f2z147unc1n3q, child number 0

-------------------------------------

update /*+ ROWID(dda) */t set DATA_OBJECT_ID=object_id+1 where

object_id between :start_id and :end_id

Plan hash value: 538090111

-------------------------------------------------------------------------------

| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |

-------------------------------------------------------------------------------

|   0 | UPDATE STATEMENT   |          |       |       | 74397 (100)|          |

|   1 |  UPDATE            | T        |       |       |            |          |

|*  2 |   FILTER           |          |       |       |            |          |

|*  3 |    INDEX RANGE SCAN| IDX_T_ID | 48375 |   472K|   197   (1)| 00:00:03 |

-------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   2 - filter(:START_ID<=:END_ID)

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

   3 - access("OBJECT_ID">=:START_ID AND "OBJECT_ID"<=:END_ID)

 

 

匿名塊執行完畢。

 

32 

 33    --Deal with Result

 34    dbms_parallel_execute.drop_task(task_name => vc_task);

 35  end;

 36  /

 

PL/SQL procedure successfully completed

 

Executed in 11350.421 seconds

 

 

完成時間大大增加,摺合3個小時左右。這個實驗告訴我們:在三種方法選取如果不合適,效能會大大降低。

下面我們來看最後一種方法by SQL

 

5by SQL方法進行chunk劃分

 

By SQL方法是使用者自己定義SQL語句,獲取columnstart idend id作為劃分chunk的內容。程式碼如下:

 

 

SQL> declare

  2    vc_task varchar2(100);

  3    vc_sql varchar2(1000);

  4    vc_sql_mt varchar2(1000);

  5    n_try number;

  6    n_status number;

  7  begin

  8    --Define the Task

  9    vc_task := 'Task 3: By SQL';

 10    dbms_parallel_execute.create_task(task_name => vc_task);

 11 

 12    --Define the Spilt

 13    vc_sql_mt := 'select distinct object_id, object_id from t';

 14    dbms_parallel_execute.create_chunks_by_SQL(task_name => vc_task,

 15                                               sql_stmt => vc_sql_mt,

 16                                               by_rowid => false);

 17 

 18    vc_sql := 'update /*+ ROWID(dda) */t set DATA_OBJECT_ID=object_id+1 where object_id between :start_id and :end_id';

 19    --Run the task

 20    dbms_parallel_execute.run_task(task_name => vc_task,

 21                                   sql_stmt => vc_sql,

 22                                   language_flag => dbms_sql.native,

 23                                   parallel_level => 2);

 24 

 25    --Controller

 26    n_try := 0;

 27    n_status := dbms_parallel_execute.task_status(task_name => vc_task);

 28    while (n_try<2 and n_status != dbms_parallel_execute.FINISHED) loop

 29       dbms_parallel_execute.resume_task(task_name => vc_task);

 30       n_status := dbms_parallel_execute.task_status(task_name => vc_task);

 31    end loop;

 32 

 33    --Deal with Result

 34    dbms_parallel_execute.drop_task(task_name => vc_task);

 35  end;

 36  /

 

在定義chunk的過程中,我們指定出單獨的SQL語句來確定start idend id。這也就讓我們不需要定義所謂的chunk size了。

執行過程依然進行chunkingprocessing過程。相關檢視資訊如下:

 

--chunking過程

SQL> select task_name, chunk_type, status from user_parallel_execute_tasks;

 

TASK_NAME                 CHUNK_TYPE   STATUS

------------------------- ------------ -------------------

Task 3: By SQL            NUMBER_RANGE CHUNKING

 

--Processing過程

SQL> select task_name, chunk_type, status from user_parallel_execute_tasks;

 

TASK_NAME                 CHUNK_TYPE   STATUS

------------------------- ------------ -------------------

Task 3: By SQL            NUMBER_RANGE PROCESSING

 

SQL> select status, count(*) from user_parallel_execute_chunks group by status;

 

STATUS                 COUNT(*)

-------------------- ----------

ASSIGNED                      2

UNASSIGNED                75559

PROCESSED                    25

 

--執行作業情況

SQL> select saddr, sid, serial#, PROGRAM from v$session where username='SYS' and status='ACTIVE' and osuser='oracle';

 

SADDR           SID    SERIAL# PROGRAM

-------- ---------- ---------- ------------------------------------------------

35ECE400         31        103 oracle@SimpleLinux.localdomain (J000)

35EA8300         45         29 oracle@SimpleLinux.localdomain (J001)

 

chunk範圍資訊中,我們可以印證對於chunk size的理解。

 

 

SQL> select chunk_id, task_name, status, start_id, end_id from user_parallel_execute_chunks where rownum<10;

 

  CHUNK_ID TASK_NAME                 STATUS                 START_ID     END_ID

---------- ------------------------- -------------------- ---------- ----------

     20052 Task 3: By SQL            PROCESSED                 17427      17427

     20053 Task 3: By SQL            PROCESSED                 17439      17439

     20054 Task 3: By SQL            PROCESSED                 17442      17442

     20055 Task 3: By SQL            PROCESSED                 17458      17458

     20056 Task 3: By SQL            PROCESSED                 37321      37321

     20057 Task 3: By SQL            PROCESSED                 37322      37322

     20058 Task 3: By SQL            PROCESSED                 17465      17465

     20059 Task 3: By SQL            PROCESSED                 37323      37323

     20060 Task 3: By SQL            PROCESSED                 17468      17468

 

9 rows selected

 

由於條件的限制,本次執行時間較長。

 

32 

 33    --Deal with Result

 34    dbms_parallel_execute.drop_task(task_name => vc_task);

 35  end;

 36  /

 

PL/SQL procedure successfully completed

 

Executed in 47522.328 seconds

 

總執行時間為13個小時。

 

6、結論

 

從上面的實驗,我們可以瞭解dbms_parallel_execute新功能包的使用和功能特點。比較顯著的就是區別與傳統的並行設定,parallel_execute包的方法是依託於10g以來的job schedule機制。並行、多執行緒轉化為多個後臺作業自主執行完成。

應該說,這樣的策略讓並行變的更加簡單易用。我們將關注點轉移到如何進行chunk劃分和設定多少並行度的問題上。Chunk的劃分影響到的是每次處理的資料量,而並行度取決於實際系統的資源富裕程度。










About Me

...............................................................................................................................

● 本文作者:小麥苗,只專注於資料庫的技術,更注重技術的運用

● 本文在itpub(http://blog.itpub.net/26736162)、部落格園(http://www.cnblogs.com/lhrbest)和個人微信公眾號(xiaomaimiaolhr)上有同步更新

● 本文itpub地址:http://blog.itpub.net/26736162/abstract/1/

● 本文部落格園地址:http://www.cnblogs.com/lhrbest

● 本文pdf版及小麥苗雲盤地址:http://blog.itpub.net/26736162/viewspace-1624453/

● 資料庫筆試面試題庫及解答:http://blog.itpub.net/26736162/viewspace-2134706/

● QQ群:230161599     微信群:私聊

● 聯絡我請加QQ好友(646634621),註明新增緣由

● 於 2017-06-02 09:00 ~ 2017-06-30 22:00 在魔都完成

● 文章內容來源於小麥苗的學習筆記,部分整理自網路,若有侵權或不當之處還請諒解

● 版權所有,歡迎分享本文,轉載請保留出處

...............................................................................................................................

拿起手機使用微信客戶端掃描下邊的左邊圖片來關注小麥苗的微信公眾號:xiaomaimiaolhr,掃描右邊的二維碼加入小麥苗的QQ群,學習最實用的資料庫技術。

11g包dbms_parallel_execute在海量資料處理過程中的應用
DBA筆試面試講解
歡迎與我聯絡

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28628435/viewspace-1984409/,如需轉載,請註明出處,否則將追究法律責任。

相關文章