让语句横着走————对海量数据更新的并行优化

作者: fuyuncat

来源: www.HelloDBA.com

 

环境:

OS linux

CPU 8

Oracle Database 10.2.0.3.0

 

 

  接到开发组的一个调优请求,任务是对一张海量表 CS2_CT_MVMT(近 2亿多记录,表大小 48G)进行数据 update,而更新数据来自于另外一张海量的日志表 CS2_TXN_LOG(同样近 2亿,表大小 42G),数据处理的语句如下:

 

UPDATE CS2_CT_MVMT CT



         SET CT.LAST_MOD_DT = (SELECT TL.MSG_GMT_DT



                                   FROM CS2_TXN_LOG TL



                                 WHERE TL.MSG_ID > 9000000000000000000



                                   AND CT.MSG_ID = TL.MSG_ID



                                   AND TL.MSG_GMT_DT IS NOT NULL)



 WHERE EXISTS (SELECT 1



                FROM CS2_TXN_LOG TL



               WHERE TL.MSG_ID > 9000000000000000000



                 AND CT.MSG_ID = TL.MSG_ID



                 AND TL.MSG_GMT_DT IS NOT NULL);



 

根据开发人员对数据的估算,在此逻辑下, CS2_CT_MVMT将会有近一半数据(近 1亿条)要被更新,相对应的,数据来自于 CS2_TXN_LOG中的近 1一亿条数据。开发人员曾经尝试过运行语句,但是 3天都没执行完。

 

第一步,对语句进行调优

首先,先看下语句的查询计划:

SQL> set line 300



SQL> set pages 50



SQL> explain plan for



  2  UPDATE CS2_CT_MVMT CT



  3     SET CT.LAST_MOD_DT = (SELECT TL.MSG_GMT_DT



  4                             FROM CS2_TXN_LOG TL



  5                            WHERE TL.MSG_ID > 9000000000000000000



  6                              AND CT.MSG_ID = TL.MSG_ID



  7                              AND TL.MSG_GMT_DT IS NOT NULL)



  8   WHERE EXISTS (SELECT 1



  9            FROM CS2_TXN_LOG TL



 10           WHERE TL.MSG_ID > 9000000000000000000



 11             AND CT.MSG_ID = TL.MSG_ID



 12             AND TL.MSG_GMT_DT IS NOT NULL);



 



Explained.



 



SQL> select * from table(dbms_xplan.display());



 



PLAN_TABLE_OUTPUT



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



Plan hash value: 3604468536



 



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



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



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



|   0 | UPDATE STATEMENT              |                |    11M|   432M|       |  3580K  (1)| 11:56:06 |



|   1 |  UPDATE                       | CS2_CT_MVMT    |       |       |       |            |          |



|*  2 |   HASH JOIN SEMI              |                |    11M|   432M|   352M|  3580K  (1)| 11:56:06 |



|*  3 |    TABLE ACCESS FULL          | CS2_CT_MVMT    |    11M|   216M|       |  1690K  (1)| 05:38:12 |



|*  4 |    TABLE ACCESS FULL          | CS2_TXN_LOG    |   214M|  3888M|       |  1552K  (1)| 05:10:25 |



|*  5 |   FILTER                      |                |       |       |       |            |          |



|*  6 |    TABLE ACCESS BY INDEX ROWID| CS2_TXN_LOG    |     1 |    19 |       |     4   (0)| 00:00:01 |



|*  7 |     INDEX UNIQUE SCAN         | CS2_TXN_LOG_PK |     1 |       |       |     3   (0)| 00:00:01 |



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



Predicate Information (identified by operation id):



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



 



   2 - access("CT"."MSG_ID"="TL"."MSG_ID")



   3 - filter("CT"."MSG_ID">9000000000000000000)



   4 - filter("TL"."MSG_ID">9000000000000000000 AND "TL"."MSG_GMT_DT" IS NOT NULL)



   5 - filter(9000000000000000000<:B1)



 



PLAN_TABLE_OUTPUT



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



   6 - filter("TL"."MSG_GMT_DT" IS NOT NULL)



   7 - access("TL"."MSG_ID"=:B1)



       filter("TL"."MSG_ID">9000000000000000000)



 



25 rows selected.



 

从查询计划看,两表之间存在一个 hash join。再看看两表之间的关系:

 

SQL> select c.table_name as child_table, c.constraint_name as FK, p.table_name as parent_table, p.constraint_name as PK



  2  from all_constraints p, all_constraints c



  3  where c.table_name in ('CS2_CT_MVMT','CS2_TXN_LOG')



  4  and c.r_constraint_name = p.constraint_name



  5  and c.constraint_type = 'R'



  6  and p.constraint_type='P';



 



CHILD_TABLE                    FK                             PARENT_TABLE                   PK



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



CS2_CT_MVMT                    CS2_CT_MVMT_FK1                CS2_TXN_LOG                    CS2_TXN_LOG_PK



 



SQL> select table_name, column_name from dba_ind_columns



  2  where index_name = 'CS2_TXN_LOG_PK';



 



TABLE_NAME                     COLUMN_NAME



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



CS2_TXN_LOG                    MSG_ID



 

可见, CS2_CT_MVMT是依赖于 CS2_TXN_LOG的子表,其依赖字段 MSG_ID正是 CS2_TXN_LOG的主键唯一字段。我们可以对这条语句的逻辑描述如下:

CS2_CT_MVMT依据主外键关系从父表 CS2_TXN_LOG中更新相应数据,且只从 CS2_TXN_LOG获取满足 (MSG_ID > 9000000000000000000 AND MSG_GMT_DT IS NOT NULL)。我们可以这个逻辑关系对语句进行调整以减少查询计划中的访问路径:

根据业务分析, CS2_CT_MVMT中将近一半的数据满足更新条件,也就是由 hash join只能过滤一半的数据,而在 SET子句中的子查询已经保证了两表数据的完整性关系已经对父表的数据过滤条件。这样的话,这个 join的代价就太大了,它应该从语句中拿掉:

UPDATE CS2_CT_MVMT CT



         SET CT.LAST_MOD_DT = (SELECT TL.MSG_GMT_DT



                                   FROM CS2_TXN_LOG TL



                                 WHERE TL.MSG_ID > 9000000000000000000



                                   AND CT.MSG_ID = TL.MSG_ID



                                   AND TL.MSG_GMT_DT IS NOT NULL);



 

但这样会产生一个问题:子查询的记录数可能为 1 0 1:1),也就是将近 1半为 NULL(即对应的 CS2_CT_MVMT记录无需更新)。很简单,我们用 NVL函数来处理:

UPDATE CS2_CT_MVMT CT



         SET CT.LAST_MOD_DT = NVL((SELECT TL.MSG_GMT_DT



                                     FROM CS2_TXN_LOG TL



                                   WHERE TL.MSG_ID > 9000000000000000000



                                      AND CT.MSG_ID = TL.MSG_ID



                                      AND TL.MSG_GMT_DT IS NOT NULL), CT.LAST_MOD_DT);



从查询计划中看到, COST将近一半:

 

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



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



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



|   0 | UPDATE STATEMENT              |                |   119M|  2160M|  1699K  (2)| 05:39:57 |



|   1 |  UPDATE                       | CS2_CT_MVMT    |       |       |            |          |



|   2 |   TABLE ACCESS FULL           | CS2_CT_MVMT    |   119M|  2160M|  1699K  (2)| 05:39:57 |



|*  3 |   FILTER                      |                |       |       |            |          |



|*  4 |    TABLE ACCESS BY INDEX ROWID| CS2_TXN_LOG    |     1 |    19 |     4   (0)| 00:00:01 |



|*  5 |     INDEX UNIQUE SCAN         | CS2_TXN_LOG_PK |     1 |       |     3   (0)| 00:00:01 |



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



 



Predicate Information (identified by operation id):



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



 



   3 - filter(9000000000000000000<:B1)



   4 - filter("TL"."MSG_GMT_DT" IS NOT NULL)



   5 - access("TL"."MSG_ID"=:B1)



       filter("TL"."MSG_ID">9000000000000000000)



 

再进一步,由于存在等价关系 CT.MSG_ID = TL.MSG_ID,我们可以将 TL.MSG_ID > 9000000000000000000转换为 CT.MSG_ID > 9000000000000000000,作为对 CS2_CT_MVMT扫描后的 filter,从而大大减少 UPDATE操作,也大大减少子查询的运行次数。

 

UPDATE CS2_CT_MVMT CT



         SET CT.LAST_MOD_DT = NVL((SELECT TL.MSG_GMT_DT



                                     FROM CS2_TXN_LOG TL



                                   WHERE CT.MSG_ID = TL.MSG_ID



                                      AND TL.MSG_GMT_DT IS NOT NULL), CT.LAST_MOD_DT)



WHERE CT.MSG_ID > 9000000000000000000;



 



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



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



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



|   0 | UPDATE STATEMENT             |                |    11M|   216M|  1690K  (1)| 05:38:12 |



|   1 |  UPDATE                      | CS2_CT_MVMT    |       |       |            |          |



|*  2 |   TABLE ACCESS FULL          | CS2_CT_MVMT    |    11M|   216M|  1690K  (1)| 05:38:12 |



|*  3 |   TABLE ACCESS BY INDEX ROWID| CS2_TXN_LOG    |     1 |    19 |     4   (0)| 00:00:01 |



|*  4 |    INDEX UNIQUE SCAN         | CS2_TXN_LOG_PK |     1 |       |     3   (0)| 00:00:01 |



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



 



Predicate Information (identified by operation id):



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



 



   2 - filter("CT"."MSG_ID">9000000000000000000)



   3 - filter("TL"."MSG_GMT_DT" IS NOT NULL)



   4 - access("TL"."MSG_ID"=:B1)



 

最后再看子查询部分:对于每条满足条件的 CS2_CT_MVMT中的记录,都将执行一次子查询,由 CS2_TXN_LOG的主键获取其表数据块中的 MSG_GMT_DT数据。子查询包含了 3步操作:

1. 对主键的 INDEX UNIQUE SCAN,获取表记录的 ROWID

2. ROWID读取表的数据块;

3. 根据条件 MSG_GMT_DT IS NOT NULL进行过滤

 

可以预计,子查询的执行次数也是巨大的,我们可以考虑通过建立复合字段索引来消除第二步操作。而通过与开发人员确认,实际上 CS2_TXN_LOG MSG_GMT_DT NULL的数据极少,也就是 MSG_GMT_DT IS NOT NULL过滤的数据极少, NVL函数已经可以处理这些数据,我们就可以将这个条件拿掉。

 

SQL> CREATE UNIQUE INDEX CS2_PARTY_OWNER.CS2_TXN_LOG_TEST ON CS2_TXN_LOG (MSG_ID, MSG_GMT_DT);



 



Index created.



 



语句及查询计划如下:



 



UPDATE CS2_CT_MVMT CT



         SET CT.LAST_MOD_DT = NVL((SELECT TL.MSG_GMT_DT



                                       FROM CS2_TXN_LOG TL



                                      WHERE CT.MSG_ID = TL.MSG_ID), CT.LAST_MOD_DT)



WHERE CT.MSG_ID > 9000000000000000000;



 



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



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



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



|   0 | UPDATE STATEMENT   |                  |    11M|   216M|  1690K  (1)| 05:38:12 |



|   1 |  UPDATE            | CS2_CT_MVMT      |       |       |            |          |



|*  2 |   TABLE ACCESS FULL| CS2_CT_MVMT      |    11M|   216M|  1690K  (1)| 05:38:12 |



|*  3 |   INDEX RANGE SCAN | CS2_TXN_LOG_TEST |     1 |    19 |     4   (0)| 00:00:01 |



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



 



Predicate Information (identified by operation id):



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



 



   2 - filter("CT"."MSG_ID">9000000000000000000)



   3 - access("TL"."MSG_ID"=:B1)



 

通过逻辑分析和查询计划分析对语句的查询部分优化基本上完成。但是这个结果还远不能满足 1小时的系统 off line时间完成数据更新的目标。

 

由于这是一次表更新操作,因此我们就还需要考虑一些依赖对象对更新操作的影响。

 

第二步,消除依赖对象的性能影响

我们看下被更新表上有一些什么依赖对象会影响到更新性能。

 

SQL> SELECT trigger_name FROM DBA_TRIGGERS WHERE table_name='CS2_CT_MVMT' AND triggering_event like '%UPDATE%';



 



no rows selected



 



SQL> SELECT s.constraint_name, search_condition



  2  FROM DBA_CONSTRAINTS S, DBA_CONS_COLUMNS C



  3  WHERE s.table_name='CS2_CT_MVMT'



  4  AND s.constraint_name=c.constraint_name



  5  and s.table_name=c.table_name



  6  and c.column_name='LAST_MOD_DT';



 



CONSTRAINT_NAME                SEARCH_CONDITION



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



SYS_C0013690                   "LAST_MOD_DT" IS NOT NULL



 



SQL> SELECT index_name FROM DBA_IND_COLUMNS



  2  WHERE TABLE_NAME='CS2_CT_MVMT'



  3  AND column_name='LAST_MOD_DT';



 



INDEX_NAME



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



CS2_CT_MVMT_IDX7



 

在批量更新之前,将这些依赖对象和约束先禁用将有助于提高更新性能。

 

SQL> ALTER TABLE CS2_CT_MVMT MODIFY LAST_MOD_DT NULL;



 



Table altered.



 

提示:我们已经通过逻辑保证了 LAST_MOD_DT不被更新为 NULL,这个约束对整体性能的影响很小,可以不用关闭。

 

SQL> drop index CS2_CT_MVMT_IDX7;



 



Index dropped.



 

Tips:有人可能会想到将表修改为 NOLOGGING,以禁止写 REDO LOG。但事实上, NOLOGGING只对 Direct Write INSERT起作用,对 UPDATE,总是会写 REDO LOG。有兴趣的朋友可以做个简单实验去验证一下。

 

第三步,将语句并行化

这一步起的作用并不是提高整体性能,而是使过程在更短的时间内占用更大的负载来完成任务。

给语句加上并行化提示

 

UPDATE /*+parallel(CT 8)*/ CS2_CT_MVMT CT



         SET CT.LAST_MOD_DT = NVL((SELECT TL.MSG_GMT_DT



                                       FROM CS2_TXN_LOG TL



                                      WHERE CT.MSG_ID = TL.MSG_ID), CT.LAST_MOD_DT)



WHERE CT.MSG_ID > 9000000000000000000;



 

看查询计划:

 

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



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



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



|   0 | UPDATE STATEMENT   |                  |    11M|   216M|  1690K  (1)| 05:38:12 |



|   1 |  UPDATE            | CS2_CT_MVMT      |       |       |            |          |



|*  2 |   TABLE ACCESS FULL| CS2_CT_MVMT      |    11M|   216M|  1690K  (1)| 05:38:12 |



|*  3 |   INDEX RANGE SCAN | CS2_TXN_LOG_TEST |     1 |    19 |     4   (0)| 00:00:01 |



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



 

嗯,没其作用。看看系统的 parallel设置:

 

SQL> show parameter parallel



 



NAME                                 TYPE        VALUE



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



fast_start_parallel_rollback         string      LOW



parallel_adaptive_multi_user         boolean     TRUE



parallel_automatic_tuning            boolean     FALSE



parallel_execution_message_size      integer     2152



parallel_instance_group              string



parallel_max_servers                 integer     0



parallel_min_percent                 integer     0



parallel_min_servers                 integer     0



parallel_server                      boolean     TRUE



parallel_server_instances            integer     3



parallel_threads_per_cpu             integer     2



recovery_parallelism                 integer     0



 

注意到, parallel_max_servers被设置为 0了,因此不会起并行进程。修改设置,

 

SQL> alter system set parallel_max_servers=160 scope=memory;



 



System altered.



 



再次获取查询计划



 



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



| Id  | Operation             | Name             | Rows  | Bytes | Cost (%CPU)| Time     |    TQ  |IN-OUT| PQ Distrib |



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



|   0 | UPDATE STATEMENT      |                  |    11M|   216M|   234K  (1)| 00:46:50 |        |      |            |



|   1 |  UPDATE               | CS2_CT_MVMT      |       |       |            |          |        |      |            |



|   2 |   PX COORDINATOR      |                  |       |       |            |          |        |      |            |



|   3 |    PX SEND QC (RANDOM)| :TQ10000         |    11M|   216M|   234K  (1)| 00:46:50 |  Q1,00 | P->S | QC (RAND)  |



|   4 |     PX BLOCK ITERATOR |                  |    11M|   216M|   234K  (1)| 00:46:50 |  Q1,00 | PCWC |            |



|*  5 |      TABLE ACCESS FULL| CS2_CT_MVMT      |    11M|   216M|   234K  (1)| 00:46:50 |  Q1,00 | PCWP |            |



|*  6 |   INDEX RANGE SCAN    | CS2_TXN_LOG_TEST |     1 |    19 |     4   (0)| 00:00:01 |        |      |            |



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



 

语句已经并行化了。

 

尝试运行语句,看看效果。

 

 



SQL> select s.sid, s.SERIAL#, s.PROGRAM, s.MODULE, sq.SQL_ID, sq.sql_text



  2  from v$session s, v$sqlarea sq



  3  where s.sql_address = sq.address



  4  order by sql_id, program;



 



       SID    SERIAL# PROGRAM                                          MODULE                                           SQL_ID



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



SQL_TEXT



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



      1892       6069 oracle@pmrac01 (P000)                            SQL*Plus                                         51b0kx3g5gbfx



UPDATE /*+ PARALLEL(CT,8) */ CS2_CT_MVMT CT    SET CT.LAST_MOD_DT = NVL((SELECT TL.MSG_GMT_DT                            



FROM CS2_TXN_LOG TL                        



WHERE CT.MSG_ID = TL.MSG_ID),CT.LAST_MOD_DT) WHERE CT.MSG_ID > 9000000000000000000



      1961      23363 oracle@pmrac01 (P001)                            SQL*Plus                                         51b0kx3g5gbfx



UPDATE /*+ PARALLEL(CT,8) */ CS2_CT_MVMT CT    SET CT.LAST_MOD_DT = NVL((SELECT TL.MSG_GMT_DT                            



FROM CS2_TXN_LOG TL                        



WHERE CT.MSG_ID = TL.MSG_ID),CT.LAST_MOD_DT) WHERE CT.MSG_ID > 9000000000000000000



... ...



      1898       7240 oracle@pmrac01 (P007)                            SQL*Plus                                         51b0kx3g5gbfx



UPDATE /*+ PARALLEL(CT,8) */ CS2_CT_MVMT CT    SET CT.LAST_MOD_DT = NVL((SELECT TL.MSG_GMT_DT                            



FROM CS2_TXN_LOG TL                        



WHERE CT.MSG_ID = TL.MSG_ID),CT.LAST_MOD_DT) WHERE CT.MSG_ID > 9000000000000000000



      1889       6026 sqlplus@pmrac01 (TNS V1-V3)                      SQL*Plus                                         51b0kx3g5gbfx



UPDATE /*+ PARALLEL(CT,8) */ CS2_CT_MVMT CT    SET CT.LAST_MOD_DT = NVL((SELECT TL.MSG_GMT_DT                            



FROM CS2_TXN_LOG TL                        



WHERE CT.MSG_ID = TL.MSG_ID),CT.LAST_MOD_DT) WHERE CT.MSG_ID > 9000000000000000000



 

可以注意到,已经有 9个会话在执行该语句( 1个协调进程 (coordinator) 8个并行进程 (slave))。 sql_id 51b0kx3g5gbfx。再观察这些会话产生的统计数据:

 

SQL> select s.sid, s.SERIAL#, n.NAME, ss.value, s.USERNAME,s.LOGON_TIME,s.MODULE



  2  from v$sesstat ss, v$statname n, v$session s, v$px_session px



  3  where n.name in ('physical write bytes', 'physical read bytes', 'undo change vector size','redo size')



  4  and ss.statistic#=n.STATISTIC#



  5  and ss.SID = s.SID



  6  and ss.SID = px.SID



  7  and not exists (select 1 from v$mystat m where s.sid=m.sid)



order by n.name desc,ss.value desc;
 



       SID    SERIAL# NAME                                         VALUE



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



      1889       6026 undo change vector size                  374128088



      1892       6069 undo change vector size                          0



      1893      10815 undo change vector size                          0



      1954       2616 undo change vector size                          0



      1935      11165 undo change vector size                          0



      1945       7426 undo change vector size                          0



      1961      23363 undo change vector size                          0



      1832       6515 undo change vector size                          0



      1898       7240 undo change vector size                          0



      1889       6026 redo size                               1140869748



      1961      23363 redo size                                        0



      1832       6515 redo size                                        0



      1892       6069 redo size                                        0



      1893      10815 redo size                                        0



      1898       7240 redo size                                        0



      1935      11165 redo size                                        0



      1954       2616 redo size                                        0



      1945       7426 redo size                                        0



      1954       2616 physical write bytes                             0



      1945       7426 physical write bytes                             0



      1935      11165 physical write bytes                             0



      1898       7240 physical write bytes                             0



      1893      10815 physical write bytes                             0



      1892       6069 physical write bytes                             0



      1889       6026 physical write bytes                             0



      1832       6515 physical write bytes                             0



      1961      23363 physical write bytes                             0



      1889       6026 physical read bytes                     3674587136



      1832       6515 physical read bytes                     2610708480



      1935      11165 physical read bytes                     2233417728



      1945       7426 physical read bytes                     1317404672



      1892       6069 physical read bytes                     1291378688



      1893      10815 physical read bytes                     1283899392



      1954       2616 physical read bytes                     1242357760



      1898       7240 physical read bytes                     1230888960



      1961      23363 physical read bytes                     1193918464



 



36 rows selected.



 

发现问题了:只有一个进程(即主进程)产生了 Redo Undo log,所有并行进程都没有 redo undo产生,也就是只有一个进程在进行写操作,尽管读已经并行化了。

再看会话的并行属性:

 

SQL> select s.sid, s.SERIAL#, s.PDML_STATUS, s.PDDL_STATUS, s.PQ_STATUS, s.PROGRAM



  2  from v$session s, v$px_session px



  3  where s.sid = px.sid



  4  and not exists (select 1 from v$mystat m where s.sid=m.sid)
 



       SID    SERIAL# PDML_STA PDDL_STA PQ_STATU



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



      1792       3473 DISABLED ENABLED  ENABLED



      1827      12996 DISABLED ENABLED  ENABLED



      1872       4173 DISABLED ENABLED  ENABLED



      1889       6998 DISABLED ENABLED  ENABLED



      1910      16279 DISABLED ENABLED  ENABLED



      1916       3073 DISABLED ENABLED  ENABLED



      1935      12587 DISABLED ENABLED  ENABLED



      1953       3125 DISABLED ENABLED  ENABLED



      1958      19949 DISABLED ENABLED  ENABLED



 



9 rows selected.



 

看到 PDML diabled。说明语句只进行了 parallel query,而没有达到 parallel DML PDML)的目的。为了实现 PDML,还需要在会话中打开 parallel DML的开关

 

SQL> ALTER SESSION FORCE PARALLEL DML;



 



Session altered.



 



再次运行语句,观察统计情况:



 



SQL> select s.sid, s.SERIAL#, n.NAME, ss.value



  2  from v$sesstat ss, v$statname n, v$session s, v$sqlarea sq



  3  where n.name in ('physical write bytes', 'physical read bytes', 'undo change vector size','redo size')



  4  and ss.statistic#=n.STATISTIC#



  5  and s.sql_address = sq.address



  6  and ss.SID = s.SID



  7  and sq.sql_id='51b0kx3g5gbfx'



  8  order by n.name desc,ss.value desc;



 



       SID    SERIAL# NAME                                           VALUE



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



      1779        524 undo change vector size                      10659808



      1806       8301 undo change vector size                      10592844



      1935      11332 undo change vector size                      9842040



... ...



      1838       8657 redo size                                    22625612



      1806       8301 redo size                                    22488968



      1935      11332 redo size                                    20878408



      1892       6131 redo size                                    15077588



... ...



      1935      11332 physical write bytes                                0



      1838       8657 physical write bytes                                0



      1901      28061 physical write bytes                                0



... ...



      1838       8657 physical read bytes                         631848960



      1935      11332 physical read bytes                         625991680



      1872       3007 physical read bytes                         559521792



... ...



 



68 rows selected.



 

会话信息中, PDML已经强制使用

 

SQL> select s.sid, s.SERIAL#, s.PDML_STATUS, s.PDDL_STATUS, s.PQ_STATUS, s.PROGRAM



  2  from v$session s, v$px_session px



  3  where s.sid = px.sid



  4  and not exists (select 1 from v$mystat m where s.sid=m.sid)
 



       SID    SERIAL# PDML_STA PDDL_STA PQ_STATU



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



      1779        524 FORCED   ENABLED  ENABLED



      1783      13993 FORCED   ENABLED  ENABLED



      1792       3477 FORCED   ENABLED  ENABLED



      1806       8481 FORCED   ENABLED  ENABLED



      1812       2874 FORCED   ENABLED  ENABLED



      1819      24796 FORCED   ENABLED  ENABLED



      1838       8756 FORCED   ENABLED  ENABLED



      1842       5929 FORCED   ENABLED  ENABLED



      1872       4177 FORCED   ENABLED  ENABLED



      1889       7002 FORCED   ENABLED  ENABLED



      1894      13805 FORCED   ENABLED  ENABLED



      1910      16366 FORCED   ENABLED  ENABLED



      1916       3077 FORCED   ENABLED  ENABLED



      1927       6182 FORCED   ENABLED  ENABLED



      1935      12591 FORCED   ENABLED  ENABLED



      1958      19955 FORCED   ENABLED  ENABLED



      1959       9259 FORCED   ENABLED  ENABLED



 



17 rows selected.



 

细心的朋友可能会留意到在我的 HINT中指定的 Parallel Degree 8,而在这却出现了 16 slave进程。我们看看在设置 PDML语句的查询计划变化就知道原因了:

 

PLAN_TABLE_OUTPUT



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



Plan hash value: 4032879153



 



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



| Id  | Operation                | Name             | Rows  | Bytes | Cost (%CPU)| Time     |    TQ  |IN-OUT| PQ Distrib |



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



|   0 | UPDATE STATEMENT         |                  |    11M|   216M|   124K  (1)| 00:24:53 |        |      |            |



|   1 |  PX COORDINATOR          |                  |       |       |            |          |        |      |            |



|   2 |   PX SEND QC (RANDOM)    | :TQ10001         |    11M|   216M|   124K  (1)| 00:24:53 |  Q1,01 | P->S | QC (RAND)  |



|   3 |    INDEX MAINTENANCE     | CS2_CT_MVMT      |       |       |            |          |  Q1,01 | PCWP |            |



|   4 |     PX RECEIVE           |                  |    11M|   216M|   124K  (1)| 00:24:53 |  Q1,01 | PCWP |            |



|   5 |      PX SEND RANGE       | :TQ10000         |    11M|   216M|   124K  (1)| 00:24:53 |  Q1,00 | P->P | RANGE      |



|   6 |       UPDATE             | CS2_CT_MVMT      |       |       |            |          |  Q1,00 | PCWP |            |



|   7 |        PX BLOCK ITERATOR |                  |    11M|   216M|   124K  (1)| 00:24:53 |  Q1,00 | PCWC |            |



|*  8 |         TABLE ACCESS FULL| CS2_CT_MVMT      |    11M|   216M|   124K  (1)| 00:24:53 |  Q1,00 | PCWP |            |



|*  9 |        INDEX RANGE SCAN  | CS2_TXN_LOG_TEST |     1 |    19 |     4   (0)| 00:00:01 |        |      |            |



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



 

在查询计划中, slave进程集的数量是 2个( TQ字段, Q1,00 Q1,01),因此,进程总量就变为 2*8=16了。

这样,这条语句就真正实现了 PDML。最终,我们设置该语句的并行度为 32,在生产环境上的执行时间是 25分钟,加上索引重建的时间,总共时间不到 1小时,实现了优化目的。

 

Bug :说实话, Oracle PDML还不是十分成熟,在我们的测试调优过程中,遇到了多个 Bug(参见 Metalink Bug 4896424 Bug 5914711)。其中还有一个 Oracle还在定位中(当 Parallel Degree大于 32时随机出现)。这些 bug 10g中都没有补丁,虽然它们都有规避办法,但是如果直接在 online时使用 PDML总还是让人不放心,好彩我们的程序只是一个运行一次的数据补丁,并不会直接影响生产系统。

 

补充 1 parallel DML的效果在很大程度上还取决于磁盘 IO的并行程度。有人可能会有一个误解,认为 parallel DML只能在分区表上起作用。但事实上,从 9iR2开始, oracle intra-partition技术支持在单分区的 parallel DML update,merge. 9iR1开始支持单分区的 parallel Insert)。

 

补充 2 :在分分区表或者单个分区上并行进程数量受到 Min Transaction Freelists的限制。在 MSSM的段管理方式中,可以参考下表可(并非一定等于该数值,因为不同的数据块结构会有不同 ITL数或其他信息,这些不同都会影响到 Min Transaction Freelists)。在 ASSM中, Oracle通过 bitmap管理 free block,这一限制最大可以达到 65535

Block Size

Min Transaction Freelists

2k        

25

4k        

50

8k        

101

16k       

204

32k       

409

  33 :在我的调试过程中,曾经发生一件令人费解的事——有时从会话中找不到并行会话!最终,通过 的方法找到了原因,具体可参见这片文章:《通过 Parallel Trace分析并行过程 》。

你可能感兴趣的:(优化,vector,filter,table,Access,parallel)