一條sql語句的改進探索

jeanron100發表於2015-06-16
昨天同事找我,讓我幫忙看兩個sql問題,第一個問題是一個sql語句執行頻率極高,但是目前的執行速度還是比較慢,希望我看看能不能調優一下。
另外一個問題是一個查詢執行速度比較慢,但是執行頻率不高。
我們還是循序漸進,一個一個來看。
第一個sql語句如下
5knr1ywqugtq8
/* TransactionLog_selectByAccRangeTrx_3 */
SELECT TRANSACTION_LOG.TRANS_DATE,
       TRANSACTION_LOG.TRANS_TYPE,
       TRANSACTION_LOG.PARTITION_ID,
       TRANSACTION_LOG.ACCOUNT_ID,
       TRANSACTION_LOG.TRANSACTION_ID,
       TRANSACTION_LOG.SUB_TRANSACTION_ID,
       TRANSACTION_LOG.SYS_UPDATE_DATE,
       TRANSACTION_LOG.SYS_CREATION_DATE,
       TRANSACTION_LOG.DL_SERVICE_CODE,
       TRANSACTION_LOG.APPLICATION_ID,
       TRANSACTION_LOG.DL_UPDATE_STAMP,
       TRANSACTION_LOG.OPERATOR_ID,
       TRANSACTION_LOG.PERIOD_KEY,
       TRANSACTION_LOG.ENTITY_PERIOD_KEY
  FROM TRANSACTION_LOG
 WHERE TRANSACTION_LOG.ACCOUNT_ID = :1
   AND TRANSACTION_LOG.PARTITION_ID = :2
   AND TRANSACTION_LOG.TRANSACTION_ID >= :3
   AND TRANSACTION_LOG.TRANSACTION_ID <= :4
   AND TRANSACTION_LOG.TRANS_TYPE IN
       (SELECT /*+ cardinality(1)*/
        DISTINCT column_value as transType
          FROM table (SELECT CAST(:5 AS ar1_ Varchar2Array_tp) FROM DUAL))
執行計劃如下,從執行計劃來看,看起來執行計劃還是不錯的。索引也使用到了,而且消耗也不高。
Plan hash value: 88692238
----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name                    | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |                         |       |       |    93 (100)|          |       |       |
|   1 |  NESTED LOOPS                          |                         |       |       |            |          |       |       |
|   2 |   NESTED LOOPS                         |                         |     1 |    81 |    93   (2)| 00:00:02 |       |       |
|   3 |    VIEW                                | VW_NSO_1                |     1 |    12 |    19   (0)| 00:00:01 |       |       |
|   4 |     HASH UNIQUE                        |                         |     1 |     2 |            |          |       |       |
|*  5 |      FILTER                            |                         |       |       |            |          |       |       |
|   6 |       COLLECTION ITERATOR PICKLER FETCH|                         |  8168 | 16336 |    19   (0)| 00:00:01 |       |       |
|   7 |        FAST DUAL                       |                         |     1 |       |     2   (0)| 00:00:01 |       |       |
|   8 |    PARTITION RANGE MULTI-COLUMN        |                         |     7 |       |    73   (0)| 00:00:01 |KEY(MC)|KEY(MC)|
|*  9 |     INDEX RANGE SCAN                   | TRANSACTION_LOG_2IX     |     7 |       |    73   (0)| 00:00:01 |KEY(MC)|KEY(MC)|
|* 10 |   TABLE ACCESS BY LOCAL INDEX ROWID    | TRANSACTION_LOG         |     1 |    69 |    73   (0)| 00:00:01 |     1 |     1 |
----------------------------------------------------------------------------------------------------------------------------------
索引情況如下:
INDEX_NAME                     INDEX_TYPE UNIQUENES PAR COLUMN_LIST                    TABLE_TYPE STATUS   NUM_ROWS LAST_ANAL G            
------------------------------ ---------- --------- --- ------------------------------ ---------- ------ ---------- --------- -            

TRANSACTION_LOG_1IX            NORMAL     NONUNIQUE YES TRANSACTION_ID,TRANS_TYPE      TABLE      N/A     270774323 01-APR-15 N            
TRANSACTION_LOG_2IX            NORMAL     NONUNIQUE YES ACCOUNT_ID,TRANS_TYPE          TABLE      N/A     270776953 01-APR-15 N            
TRANSACTION_LOG_PK             NORMAL     UNIQUE    YES SUB_TRANSACTION_ID,PARTITION_ID,PERIOD_KEY TABLE      N/A     270772197 01-APR-15 N
可以看到索引還是用了TRANSACTION_LOG_2IX,即使用了欄位ACCOUNT_ID,TRANS_TYPE來進行資料過濾。
從sql語句的執行和資料分佈來看,還是合理的,沒有什麼問題。根據現在的執行情況,每次執行大約需要2秒以上。期望還是能夠達到1秒以內甚至更低。
我們來看看索引TRANSACTION_LOG_1IX和TRANSACTION_LOG_2IX,其實會發現無論是使用第一個還是第二個,資料的分佈還是基本平均的。
從叢集因子也能看出。
TABLE_NAME           INDEX_NAME         CLUSTERING_FACTOR     BLOCKS   NUM_ROWS
----------------- -- -------------------- ----------------- ---------- ----------
TRANSACTION_LOG    TRANSACTION_LOG_1IX      183188039    2912269  270768896
TRANSACTION_LOG    TRANSACTION_LOG_2IX      237869001    2912269  270768896
TRANSACTION_LOG    TRANSACTION_LOG_PK       186148006    2912269  270768896
不過多多少少還是有些差別的。不過差別還是不大,我們還是用sql profile來看看能夠給點什麼建議。
很快生成了報告,可以看出,改進其實不大。大約只有不到20%的改進。
                           Original Plan  With SQL Profile  % Improved
                           -------------  ----------------  ----------
  Completion Status:            COMPLETE          COMPLETE
  Elapsed Time (s):             .000389            .00033      15.16 %
  CPU Time (s):                 .000399           .000299      25.06 %
  User I/O Time (s):                  0                 0
  Buffer Gets:                        0                 0
  Physical Read Requests:             0                 0
  Physical Write Requests:            0                 0
  Physical Read Bytes:                0                 0
  Physical Write Bytes:               0                 0
  Rows Processed:                     0                 0
  Fetches:                            0                 0
  Executions:                         1                 1
而且從改進的執行計劃來看,其實主要的差別就在於索引掃描了。索引掃描從原來的range scan建議改為skip scan.
2- Using SQL Profile
--------------------
Plan hash value: 1415584969
---------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name                    | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                         |     1 |    81 |    93   (2)| 00:00:02 |       |       |
|   1 |  NESTED LOOPS                         |                         |     1 |    81 |    93   (2)| 00:00:02 |       |       |
|   2 |   VIEW                                | VW_NSO_1                |  8168 | 98016 |    19   (0)| 00:00:01 |       |       |
|   3 |    HASH UNIQUE                        |                         |     1 | 16336 |            |          |       |       |
|*  4 |     FILTER                            |                         |       |       |            |          |       |       |
|   5 |      COLLECTION ITERATOR PICKLER FETCH|                         |  8168 | 16336 |    19   (0)| 00:00:01 |       |       |
|   6 |       FAST DUAL                       |                         |     1 |       |     2   (0)| 00:00:01 |       |       |
|   7 |   PARTITION RANGE MULTI-COLUMN        |                         |     1 |    69 |    73   (0)| 00:00:01 |KEY(MC)|KEY(MC)|
|*  8 |    TABLE ACCESS BY LOCAL INDEX ROWID  | AR1_TRANSACTION_LOG     |     1 |    69 |    73   (0)| 00:00:01 |KEY(MC)|KEY(MC)|
|*  9 |     INDEX SKIP SCAN                   | AR1_TRANSACTION_LOG_1IX |     1 |       |    73   (0)| 00:00:01 |KEY(MC)|KEY(MC)|
---------------------------------------------------------------------------------------------------------------------------------
這個時候,還是很有顧慮的,因為在生產中已經完全能禁用skip scan了,根據產品線的建議是需要禁用的,因為在實際使用中還是碰到了不少的問題,skip scan在有些場景中反而不如range scan,會有full index scan的效果。
所以即使在Hint中知名需要skip scan在生產中也是很可能會走range scan. 在資料庫引數中,禁用skip scan的部分如下:
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
_optimizer_skip_scan_enabled         boolean     FALSE
不過對此,也不是沒有辦法,可以使用opt_param來啟用,使用hint的例項如下:
/*+opt_param('_optimizer_skip_scan_enabled', 'true')*/
不過雖然可行,但是不一定我們需要確實值得這麼去做。我們還是需要評估一下。
首先skip scan在一些場景中已經碰到了一些相關的問題,不能直接根據執行計劃的情況進行調優。執行計劃可以作為參考,但不是全部。
二來目前的執行情況來看,就算啟用skip scan,收效也不大,提高20%左右儘管在1秒以上,但是還是存在問題。
三來從這個語句來看,這個查詢其實還是一個很簡單的語句,沒有關聯表,使用了索引,查詢條件也不復雜,調優的空間確實有限。就算退一步來看看使用另外一個索引,其實改進空間也是不大的。
這個可以簡單的測試證明,這個查詢沒有使用transaction_id相關的索引列,其實很大一部分就是因為>=, <=的原因。
我們可以模擬測試一下,把<=改為=改為>=   得到的執行計劃就會是下面的樣子
----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name                    | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |                         |     1 |    81 |    93   (2)| 00:00:02 |       |       |
|   1 |  NESTED LOOPS                          |                         |       |       |            |          |       |       |
|   2 |   NESTED LOOPS                         |                         |     1 |    81 |    93   (2)| 00:00:02 |       |       |
|   3 |    VIEW                                | VW_NSO_1                |     1 |    12 |    19   (0)| 00:00:01 |       |       |
|   4 |     HASH UNIQUE                        |                         |     1 |     2 |            |          |       |       |
|*  5 |      FILTER                            |                         |       |       |            |          |       |       |
|   6 |       COLLECTION ITERATOR PICKLER FETCH|                         |  8168 | 16336 |    19   (0)| 00:00:01 |       |       |
|   7 |        FAST DUAL                       |                         |     1 |       |     2   (0)| 00:00:01 |       |       |
|   8 |    PARTITION RANGE MULTI-COLUMN        |                         |     7 |       |    73   (0)| 00:00:01 |KEY(MC)|KEY(MC)|
|*  9 |     INDEX RANGE SCAN                   | TRANSACTION_LOG_2IX     |     7 |       |    73   (0)| 00:00:01 |KEY(MC)|KEY(MC)|
|* 10 |   TABLE ACCESS BY LOCAL INDEX ROWID    | TRANSACTION_LOG         |     1 |    69 |    73   (0)| 00:00:01 |     1 |     1 |
----------------------------------------------------------------------------------------------------------------------------------
改進空間還是很有限的。
對此我的建議還是能夠從多個方面來考慮。
首先就是索引,目前存在3個索引,其實TRANSACTION_LOG_1IX和TRANSACTION_LOG_2IX都是非唯一性索引,如果能夠使用TRANSACTION_LOG_PK其實還是有很大的改進空間。和開發同事的交流來看,他們認為改動難度較大,需要改動的程式碼量很大,可能需要的時間也較長。但是也不失為一種方式。所以這個方案也是可行但是能不能落地還是一個問號。畢竟在雙方的一個權衡中也需要很多的協調。
第二個改進就是和開發的交流和自己抓取的一些資訊,發現transaction_id的取值範圍還是很大,這也是資料過濾較慢的一個原因。可以從執行計劃的謂詞資訊中看出來一些。首先是根據account_id來進行定位,但是資料過濾是根據transaction_id來做的。做這個時候
   9 - access("TRANSACTION_LOG"."ACCOUNT_ID"=:1 AND "TRANSACTION_LOG"."TRANS_TYPE"="TRANSTYPE")
  10 - filter(("TRANSACTION_LOG"."TRANSACTION_ID">=:3 AND "TRANSACTION_LOG"."TRANSACTION_ID"<=:4 AND
              "TRANSACTION_LOG"."PARTITION_ID"=:2))
所以經過討論一種可行的效果就是能夠不盡可能的縮短transaction_id的範圍。使得transaction_id的範圍儘量小一些,資料過濾就會更加高效。這個從前端來說還是比較容易來實現的。
經過驗證發現速度還是比較理想的,調整了範圍之後,速度都在毫秒。

Elapsed: 00:00:00.70



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

相關文章