天天看点

执行计划的偏差导致的性能问题

在生产环境中有一条sql语句,查看执行计划来看,效果还是可以接受的。

sql语句类似下面的样子,可以看到里面还使用了比较纠结的外连接。从执行计划来说,默认是走nested loop join,数据的查取中会走索引,从oracle的分析来说这样的效果要好一些。

  SELECT /*+ leading(cyc_cust,cyc_pay) */

   CUST.CUSTOMER_ID,

   CUST.WEIGHT,

   CYC_CUST.STATUS,

   CYC_CUST.DB_STATUS,

   CYC_CUST.UNDO_REQ_TYPE,

   CYC_PAY.BA_NO AS BA_NO,

   CYC_PAY.STATUS AS PAY_STATUS,

   CYC_PAY.DB_STATUS AS PAY_DB_STATUS,

   CYC_PAY.UNDO_REQ_TYPE AS PAY_UNDO_REQ_TYPE,

   CYC_CUST.CYCLE_SEQ_RUN

    FROM CUSTOMER        CUST,

         CYCLE_CUSTOMERS CYC_CUST,

         CYC_PAYER_POP   CYC_PAY

   WHERE CYC_CUST.PERIOD_KEY = 57

     AND CYC_CUST.CUSTOMER_KEY = 87

     AND CYC_CUST.CYCLE_SEQ_NO =3325

     AND CUST.CUSTOMER_ID = CYC_CUST.CUSTOMER_NO

     AND CYC_PAY.PERIOD_KEY(+) = CYC_CUST.PERIOD_KEY

     AND CYC_PAY.CUSTOMER_KEY(+) = CYC_CUST.CUSTOMER_KEY

     AND CYC_PAY.CUSTOMER_NO(+) = CYC_CUST.CUSTOMER_NO

     AND CYC_PAY.CYCLE_SEQ_NO(+) = CYC_CUST.

   CYCLE_SEQ_NO

     AND (CYC_PAY.UNDO_REQ_TYPE 'N' OR CYC_CUST.UNDO_REQ_TYPE 'N')

   ORDER BY CUSTOMER_ID;

预计时间在1分半左右,在生产环境中单个执行的时候差不多控制在20~30秒左右。这样看来实际效果比期望的要好。

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

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

|   0 | SELECT STATEMENT                       |                        |  2152 |   138K|  8254   (1)| 00:01:40 |       |       |

|   1 |  NESTED LOOPS                          |                        |       |       |            |          |       |       |

|   2 |   NESTED LOOPS                         |                        |  2152 |   138K|  8254   (1)| 00:01:40 |       |       |

|*  3 |    FILTER                              |                        |       |       |            |          |       |       |

|   4 |     NESTED LOOPS OUTER                 |                        |  2152 |   119K|  8039   (1)| 00:01:37 |       |       |

|   5 |      PARTITION RANGE SINGLE            |                        |  4304 |   109K|  6317   (1)| 00:01:16 |   KEY |   KEY |

|   6 |       TABLE ACCESS BY LOCAL INDEX ROWID| CYCLE_CUSTOMERS        |  4304 |   109K|  6317   (1)| 00:01:16 |   KEY |   KEY |

|*  7 |        INDEX FULL SCAN                 | CYCLE_CUSTOMERS_PK     |    24 |       |  5944   (1)| 00:01:12 |   KEY |   KEY |

|   8 |      PARTITION RANGE SINGLE            |                        |     1 |    31 |     1   (0)| 00:00:01 |   KEY |   KEY |

|*  9 |       TABLE ACCESS BY LOCAL INDEX ROWID| CYC_PAYER_POP          |     1 |    31 |     1   (0)| 00:00:01 |   KEY |   KEY |

|* 10 |        INDEX RANGE SCAN                | CYC_PAYER_POP_1IX      |     3 |       |     1   (0)| 00:00:01 |   KEY |   KEY |

|* 11 |    INDEX UNIQUE SCAN                   | CUSTOMER_PK            |     1 |       |     1   (0)| 00:00:01 |       |       |

|  12 |   TABLE ACCESS BY INDEX ROWID          | CUSTOMER               |     1 |     9 |     1   (0)| 00:00:01 |       |       |

但是在生产环境中执行的时候,是通过一个程序来触发的,会并行的执行多个这样的查询,结果性能是越来越糟糕,最后本来执行20秒的语句现在又变成了1分半了。

在尝试加入4个并行之后,效果没有任何的改观。

在排查这个问题的时候有两个方面的考虑,也是在调优中可能比较纠结的问题。

一个是nested loop join和hash join的取舍

这个问题按照理论来说是比较好判断的,如果大表,小表关联,是建议采用nested loop join的。

如果大表,大表关联,是建议采用hash join的。

但是在一些sql的执行过程中,可能根据执行计划走了索引,结果按照预期,过滤出的数据应该很少,在解析的时候就走了nested loop join,但是实际中可能数据分布很不均匀,有时候过滤出的数据多,有时候过滤出的数据少。

这样一来,如果过滤出的数据量大的时候,走了nested loop join 结果就会发送大量的io请求,然后不停的去走关联,瓶颈都消耗在io等待上了。

第二个问题是关于索引和全表扫描

这个问题,可能开发人员的普遍认识就是全表扫描要比索引要慢,大多数情况是,但是dba碰到的更多情况是希望走全表扫描,因为走索引的代价其实更大。如果表很小的情况下,走索引和全表扫描来说,全表扫描要更好一些,

如果表很大的情况下,但是通过索引过滤出的数据也比较多,那么也就建议还是走全表吧。

现在生产环境中碰到的这个sql问题就有如上的两种情况。

这几张表都是上千万的大表,有的表中的数据分布确实不透均匀,结果就在处理某一批数据的时候,性能还可以接受,但是在处理另外一批数据的时候性能抖动就很明显。

如果同时运行多个查询的情况下。可能等待时间都在io上了。

在查看awr报告中的一些指标之后,我尝试修改表的访问路径,把原本的两个表关联由nested loop join变为hash join。

然后两个表都坚持走全表扫描,当然了考虑到了执行的效率,加了4个并行。这样来说就算比较笼统的平衡了数据不均匀的问题。

修改hint为/*+   use_hash(CYC_CUST,CYC_PAY) parallel(CYC_CUST 4)  */ 

从执行计划来看是有些得不偿失的,cpu消耗要高很多。但是在实际执行的时候效果却很好。平均执行时间为10~15秒,执行的很稳定。在做了多个session的并发查询的情况下,速度还是很稳定。

Plan hash value: 4044834379

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

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

|   0 | SELECT STATEMENT                   |                     |   567 | 37422 | 45236   (1)| 00:09:03 |       |       |        |      |            |

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

|   2 |   PX SEND QC (ORDER)               | :TQ10003            |   567 | 37422 | 45236   (1)| 00:09:03 |       |       |  Q1,03 | P->S | QC (ORDER) |

|   3 |    SORT ORDER BY                   |                     |   567 | 37422 | 45236   (1)| 00:09:03 |       |       |  Q1,03 | PCWP |            |

|   4 |     PX RECEIVE                     |                     |       |       |            |          |       |       |  Q1,03 | PCWP |            |

|   5 |      PX SEND RANGE                 | :TQ10002            |       |       |            |          |       |       |  Q1,02 | P->P | RANGE      |

|   6 |       NESTED LOOPS                 |                     |       |       |            |          |       |       |  Q1,02 | PCWP |            |

|   7 |        NESTED LOOPS                |                     |   567 | 37422 | 45235   (1)| 00:09:03 |       |       |  Q1,02 | PCWP |            |

|*  8 |         FILTER                     |                     |       |       |            |          |       |       |  Q1,02 | PCWC |            |

|*  9 |          HASH JOIN OUTER           |                     |   567 | 32319 | 45219   (1)| 00:09:03 |       |       |  Q1,02 | PCWP |            |

|  10 |           PX RECEIVE               |                     |  1134 | 29484 |  8563   (1)| 00:01:43 |       |       |  Q1,02 | PCWP |            |

|  11 |            PX SEND HASH            | :TQ10001            |  1134 | 29484 |  8563   (1)| 00:01:43 |       |       |  Q1,01 | P->P | HASH       |

|  12 |             PX BLOCK ITERATOR      |                     |  1134 | 29484 |  8563   (1)| 00:01:43 |   171 |   171 |  Q1,01 | PCWC |            |

|* 13 |              TABLE ACCESS FULL     | CYCLE_CUSTOMERS     |  1134 | 29484 |  8563   (1)| 00:01:43 |   171 |   171 |  Q1,01 | PCWP |            |

|  14 |           BUFFER SORT              |                     |       |       |            |          |       |       |  Q1,02 | PCWC |            |

|  15 |            PX RECEIVE              |                     |  1355 | 42005 | 36656   (1)| 00:07:20 |       |       |  Q1,02 | PCWP |            |

|  16 |             PX SEND HASH           | :TQ10000            |  1355 | 42005 | 36656   (1)| 00:07:20 |       |       |        | S->P | HASH       |

|  17 |              PARTITION RANGE SINGLE|                     |  1355 | 42005 | 36656   (1)| 00:07:20 |   171 |   171 |        |      |            |

|* 18 |               TABLE ACCESS FULL    | CYC_PAYER_POP       |  1355 | 42005 | 36656   (1)| 00:07:20 |   171 |   171 |        |      |            |

|* 19 |         INDEX UNIQUE SCAN          | CUSTOMER_PK         |     1 |       |     1   (0)| 00:00:01 |       |       |  Q1,02 | PCWP |            |

|  20 |        TABLE ACCESS BY INDEX ROWID | CUSTOMER            |     1 |     9 |     1   (0)| 00:00:01 |       |       |  Q1,02 | PCWP |            |

所以执行计划只能作为大家调优的一个参考而已,dba需要对于数据需要有更加深入的了解。自己做到心中有数。

最后分享一下两种方案的执行效果。

采用nested loop join+索引扫描

new1.log:Elapsed: 00:00:56.30

new1.log:Elapsed: 00:00:23.51

new1.log:Elapsed: 00:00:01.46

new1.log:Elapsed: 00:00:01.29

new1.log:Elapsed: 00:00:01.27

new1.log:Elapsed: 00:00:01.28

new1.log:Elapsed: 00:00:01.26

new2.log:Elapsed: 00:00:42.79

new2.log:Elapsed: 00:00:23.51

new2.log:Elapsed: 00:00:01.29

new2.log:Elapsed: 00:00:01.28

new2.log:Elapsed: 00:00:01.27

采用hash join+全表扫描

new1.log:Elapsed: 00:00:14.95

new1.log:Elapsed: 00:00:13.70

new1.log:Elapsed: 00:00:12.81

new1.log:Elapsed: 00:00:13.89

new1.log:Elapsed: 00:00:14.56

new1.log:Elapsed: 00:00:11.61

new1.log:Elapsed: 00:00:11.45

new1.log:Elapsed: 00:00:10.84

new1.log:Elapsed: 00:00:11.31

new2.log:Elapsed: 00:00:12.13

new2.log:Elapsed: 00:00:11.85

new2.log:Elapsed: 00:00:14.44

new2.log:Elapsed: 00:00:15.60

new2.log:Elapsed: 00:00:12.55

new2.log:Elapsed: 00:00:11.09

new2.log:Elapsed: 00:00:11.11

new2.log:Elapsed: 00:00:11.04

new2.log:Elapsed: 00:00:11.14