天天看點

執行計劃的偏差導緻的性能問題

在生産環境中有一條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