持續近7個小時的索引掃描的查詢優化分析

jeanron100發表於2015-05-22
昨天客戶的DBA反映有一個資料抽取的任務持續了很長時間最後超時退出了,讓我看看有什麼地方可以調優一下。
找到了對應的日誌,發現在一個大表抽取的時候,抽取持續了將近7個小時,最後超時退出了。對於這個問題,有以下幾個方面需要考慮一下。
1)為什麼這個問題之前沒有發現過
2)是否是由某些變化導致了這個問題
3)這個問題的調優方向
這個資料抽取的服務之前一直沒有問題,抽取速度都是比較快的,結果這次竟然持續了7個小時還沒有抽取完。首先抓取到了對應的日誌,把相關的sql語句也抓取到了。
同時從系統負載的角度進行分析,檢視資料庫層,系統級是否發生了某些變化導致了這個問題,結果抓取了詳細的awr報告,同時結合系統命令分析檢視系統負載,都沒有發現任何的異常,而且這些天來一直沒有任何資料庫層面的引數變更。
所以問題的關注點還是到了sql語句上。
檢視sql語句的執行計劃,也沒有發現異常,可以很明顯看到走的是索引掃描。
語句是類似下面的樣子,使用了閃回查詢,查詢條件只有一個customer_id
select * FROM "RATED_EVENT" AS OF TIMESTAMP (TIMESTAMP' 2015-05-21 07:33:23.000000000') "RATED_EVENT" WHERE "CUSTOMER_ID"=:1 

因為資料抽取為了保證資料一致性,所以使用閃回查詢的功能,所以這個問題為了方便排查,可以進一步把sql語句改寫為:
select count(*) FROM "RATED_EVENT" "RATED_EVENT" WHERE "CUSTOMER_ID"=11727713 
執行計劃如下:
Plan hash value: 3695503463
----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                          | Name            | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |                 |   432 |   369K|   320   (0)| 00:00:04 |       |       |
|   1 |  PARTITION RANGE ALL               |                 |   432 |   369K|   320   (0)| 00:00:04 |     1 |   961 |
|   2 |   TABLE ACCESS BY LOCAL INDEX ROWID| RATED_EVENT     |   432 |   369K|   320   (0)| 00:00:04 |     1 |   961 |
|*  3 |    INDEX RANGE SCAN                | RATED_EVENT_1UQ |   432 |       |   289   (0)| 00:00:04 |     1 |   961 |
----------------------------------------------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
   1 - SEL$1
   2 - SEL$1 / RATED_EVENT@SEL$1
   3 - SEL$1 / RATED_EVENT@SEL$1
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("CUSTOMER_ID"=11727713)

這樣一來,問題就顯得更加奇怪了。走了索引掃描,條件也很簡單,怎麼就查詢了那麼長的時間呢。
這條語句有一個亮點就是看看pstart,pstop的部分,顯示為1和961,即表示這個分割槽表在查詢中掃描的分割槽為1~961個,這個規模還是相當大的。
但這個還不是最終的問題原因。
這個時候需要結合一下業務來進行診斷。
customer對應有多個subscriber,一般的三戶模型中都是一個customer可能就對應一個subscriber,當然一個customer也可以對應多個subscriber,比如一些大客戶就是如此。
我們來看看這個場景裡的customer和subscriber的對應比例。
SQL> select count(*)from subscriber where customer_id=11727713;
  COUNT(*)
----------
      6168
裡面有6000多個subscriber,在近10億條記錄中進行這麼大範圍的資料掃描,而且掃描的分割槽是1~961個,難度可想而知。
這個資料抽取的部分程式碼都是靈活配置的,怎麼能儘快的提升效率呢。
自己嘗試了幾個方法,一個是使用exp/expdp匯出資料,結合使用query條件,這個時候算是脫離了原有的資料抽取工具,因為這個場景裡抽取邏輯相對簡單,所以不妨一試。
exp xxxxx/xxxxx@xxxxfile=test.dmp tables=rated_event query=\' where  customer_id= 11727713 \'   grants=n indexes=n statistics=none buffer=9102000
但是根據自己的測試發現,效果並不理想。

如果要直接修改抽取的配置規則,相對也是比較困難的。如果能夠提升抽取速度,同時能從抽取業務上做一些優化但不改變原有的業務就是最好的方法了。
明白了這點,自己就開始結合業務來進行分析,因為整個分割槽表是按照一個類似賬期的欄位來分割槽的,一個customer只對應一個賬期,customer下的subscriber都是同一個賬期,明白了這點。
語句就可以相應的修改成下面的形式。
select count(*) FROM "RATED_EVENT" "RATED_EVENT" WHERE "CUSTOMER_ID"=10566068  and cycle_code in (select cycle_code from customer where customer_id=10566068)
這個時候執行計劃也有了一些變化。
Plan hash value: 1017421008
-------------------------------------------------------------------------------------------------------------
| Id  | Operation                 | Name            | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |                 |     1 |     9 |    73   (0)| 00:00:01 |       |       |
|   1 |  SORT AGGREGATE           |                 |     1 |     9 |            |          |       |       |
|   2 |   PARTITION RANGE ITERATOR|                 |   303 |  2727 |    73   (0)| 00:00:01 |   241 |   481 |
|*  3 |    INDEX RANGE SCAN       | RATED_EVENT_1UQ |   303 |  2727 |    73   (0)| 00:00:01 |   241 |   481 |
-------------------------------------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
   1 - SEL$1
   3 - SEL$1 / RATED_EVENT@SEL$1
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("CUSTOMER_ID"=10566068 AND "CYCLE_CODE"=2)
       filter("CYCLE_CODE"=2)
可以看到這個時候掃描的分割槽少了很多,從241~481,相比原來只掃描了四分之一的分割槽。效能的提升還是很高的。
這個時候簡單對比一下,結合了分割槽欄位,掃描的速度也快了不少。用了大概4分鐘就能夠有結果了。
SQL> select count(*)from rated_event where customer_id=11727713 and cycle_code=2
    /
  COUNT(*)
----------
  11757084
Elapsed: 00:04:16.40

而對於select count(*)的操作在之前卻要花費將近15分鐘。
SQL>  select count(*) FROM "RATED_EVENT" "RATED_EVENT" WHERE "CUSTOMER_ID"=11727713 ;
  COUNT(*)
----------
  11751975
Elapsed: 00:15:05.54

這個時候如果觀察足夠細緻,會發現兩個查詢的資料條數還是有一些出入,這是因為某些客戶做了修改賬期的操作,在這個資料抽取中只關注當前賬期的操作,所以可以暫時放過。
由此可見,看似簡單的語句走了索引掃描,看起來合理,但是問題突然發生的時候還得結合具體的場景來分析,不能把問題孤立起來看,在明白了問題的瓶頸之後,如果單純從資料庫層面所做的工作有限時,可以考慮從業務上進行進一步的優化,輔助資料庫優化的方向。這個時候DBA的效能調優工作就不單單是一個資料層面的工作了,可能結合業務場景更有針對性,調優的方向也更明確。

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

相關文章