通過IP定位區域的SQL優化思路(r10筆記第10天)
昨天中午吃飯的時候,突然手機收到一條報警資訊,提示資料庫的負載突然提高了。對於一個高配,穩定,核心的系統來說,出現這麼一個報警會立刻引起關注。 連線到環境之後,發現在問題發生時間段快照中資源消耗較大的SQL情況如下:
可以看到有兩個語句的執行頻率還是比較高,整體佔用了絕大多數的CPU資源。
對應的前兩個SQL語句如下:
SQL_FULLTEXT
----------------------------------------------------------------------------------------------------
SELECT IP_ID,COUNTRY,PROVINCE,CAPITAL
FROM SWD_IP2COUNTY
WHERE STRIPTOINT(:B1 ) BETWEEN IP2NUM_LEFT_LINE AND IP2NUM_RIGHT_LINE
COLUMN_ID COLUMN_NAME DATA_TYPE DATA_LENGTH NULLABLE
---------- ------------------------------ --------------- ----------- ----------
1 IP_ID NUMBER(10,0) 22 N
2 IP_LEFT_LINE VARCHAR2(15) 15 N
3 IP_RIGHT_LINE VARCHAR2(15) 15 N
4 IP2NUM_LEFT_LINE NUMBER(10,0) 22 N
5 IP2NUM_RIGHT_LINE NUMBER(10,0) 22 N
6 COUNTRY VARCHAR2(20) 20 Y
7 PROVINCE VARCHAR2(20) 20 Y
8 CAPITAL VARCHAR2(20) 20 Y
SQL> select STRIPTOINT('124.115.229.74') from dual;
STRIPTOINT('124.115.229.74')
----------------------------
208797012
所以這個SQL語句就類似下面的形式。
SELECT IP_ID,COUNTRY,PROVINCE,CAPITAL FROM tlbb.SWD_IP2COUNTY WHERE 208797012 BETWEEN IP2NUM_LEFT_LINE AND IP2NUM_RIGHT_LINE
這樣理解起來還真是有點費勁,我們繼續介紹一些相關的業務情況,這些也是我根據資料猜出來的,後面和開發的同學聊,和我想的是一樣的。
比如資料是這樣的形式:
IP_LEFT_LINE IP_RIGHT_LINE IP2NUM_LEFT_LINE IP2NUM_RIGHT_LINE COUNTRY PROVINCE CAPITAL
----- --------------- --------------- ---------------- ----------------- ---------- ---------- -------
5.34.184.0 5.34.191.255 86161408 86163455 中國 北京 北京
5.34.192.0 5.34.223.255 86163456 86171647 中國 河北 石家莊
比如IP 5.34.184.0~5.34.191.255 這個區間代表的是北京地區,5.34.192.0 ~ 5.34.223.255 這個區間代表的是河北石家莊。那麼傳入一個IP就開始對映得到一個數值,通過這個範圍區間來找到對應的地區。
這樣一個語句,明白了需求,好像還是有點道理。
來看看awr的SQL報告怎麼說吧。
Snap Id Snap Time Sessions Curs/Sess
--------- ------------------- -------- ---------
Begin Snap: 2397 01-Sep-16 13:00:52 567 1.9
End Snap: 2398 01-Sep-16 13:30:54 566 1.9
Elapsed: 30.03 (mins)
DB Time: 138.02 (mins)
可以看到在短期內,資料庫的負載還是比較高。
而SQL的執行統計資訊如下:
如果想得到更多的詳細的資訊,使用診斷事件也是不錯的選擇,這也是11g的一個功能。
比如對某一條SQL開啟sql_trace,可以使用如下的方式。
開啟:
alter system set events 'sql_trace [sql: 63f970ck8r3kc] level 12';
關閉:
alter system set events 'sql_trace [sql: 63f970ck8r3kc] off';
而如果有大量的會話頻繁呼叫,那還是不建議使用的,會生成大量的trace檔案,目前的情況就是如此,需要謹慎使用。
而我們怎麼去分析這個SQL呢,來做一個10053事件吧。
ALTER SESSION SET EVENTS='10053 trace name context forever, level 1';
SELECT IP_ID,COUNTRY,PROVINCE,CAPITAL FROM test.SWD_IP2COUNTY WHERE 208797012 BETWEEN IP2NUM_LEFT_LINE AND IP2NUM_RIGHT_LINE;
ALTER SESSION SET EVENTS '10053 trace name context off';
這個時候trace的資訊可以看到,內部做了查詢轉換,會把原有的語句轉換為下面的形式:
Final query after transformations:******* UNPARSED QUERY IS *******
SELECT "SWD_IP2COUNTY"."IP_ID" "IP_ID","SWD_IP2COUNTY"."COUNTRY" "COUNTRY","SWD_IP2COUNTY"."PROVINCE" "PROVINCE","SWD_IP2C
OUNTY"."CAPITAL" "CAPITAL" FROM "TEST"."SWD_IP2COUNTY" "SWD_IP2COUNTY" WHERE "SWD_IP2COUNTY"."IP2NUM_LEFT_LINE"<=208797012
AND "SWD_IP2COUNTY"."IP2NUM_RIGHT_LINE">=208797012
對於執行路徑和表資訊的統計資訊如下:
Access path analysis for SWD_IP2COUNTY
***************************************
SINGLE TABLE ACCESS PATH
Single Table Cardinality Estimation for SWD_IP2COUNTY[SWD_IP2COUNTY]
Column (#4):
NewDensity:0.000006, OldDensity:0.000006 BktCnt:254, PopBktCnt:0, PopValCnt:0, NDV:163088
Column (#4): IP2NUM_LEFT_LINE(
AvgLen: 7 NDV: 163088 Nulls: 0 Density: 0.000006 Min: 16910592 Max: 4261412864
Histogram: HtBal #Bkts: 254 UncompBkts: 254 EndPtVals: 255
Column (#5):
NewDensity:0.000006, OldDensity:0.000006 BktCnt:254, PopBktCnt:0, PopValCnt:0, NDV:163246
Column (#5): IP2NUM_RIGHT_LINE(
AvgLen: 7 NDV: 163246 Nulls: 0 Density: 0.000006 Min: 16912383 Max: 4278190079
Histogram: HtBal #Bkts: 254 UncompBkts: 254 EndPtVals: 255
Table: SWD_IP2COUNTY Alias: SWD_IP2COUNTY
Card: Original: 163246.000000 Rounded: 1114 Computed: 1114.40 Non Adjusted: 1114.40
Access Path: TableScan
Cost: 444.56 Resp: 444.56 Degree: 0
Cost_io: 443.00 Cost_cpu: 57439773
Resp_io: 443.00 Resp_cpu: 57439773
Access Path: index (RangeScan)
Index: IND_IP2NUM_LEFT_LINE
resc_io: 15.00 resc_cpu: 713197
ix_sel: 0.006874 ix_sel_with_filters: 0.006874
Cost: 15.02 Resp: 15.02 Degree: 1
Access Path: index (RangeScan)
Index: IND_IP2NUM_RIGHT_LINE
resc_io: 1902.00 resc_cpu: 101091934
ix_sel: 0.993126 ix_sel_with_filters: 0.993126
Cost: 1904.74 Resp: 1904.74 Degree: 1
****** finished trying bitmap/domain indexes ******
Best:: AccessPath: IndexRange
Index: IND_IP2NUM_LEFT_LINE
Cost: 15.02 Degree: 1 Resp: 15.02 Card: 1114.40 Bytes: 0
最大的發現是就是這個語句的邏輯結合自己的分析終於清晰了,但是怎麼優化還沒想好。為什麼效能如此差。
我用查詢轉換後的語句,直接在客戶端執行,檢視執行計劃。
SQL> SELECT "SWD_IP2COUNTY"."IP_ID" "IP_ID","SWD_IP2COUNTY"."COUNTRY" "COUNTRY","SWD_IP2COUNTY"."PROVINCE" "PROVINCE","SWD_IP2COUNTY"."CAPITAL" "CAPITAL" FROM "TEST"."SWD_IP2COUNTY" "SWD_IP2COUNTY" WHERE "SWD_IP2COUNTY"."IP2NUM_LEFT_LINE"<=3030728598
AND "SWD_IP2COUNTY"."IP2NUM_RIGHT_LINE">=3030728598 ;
發現這個時候CBO是分析得出需要走全表掃描的。
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
1530 consistent gets
。。。
1 rows processed
這樣就徹底明白了,這裡走索引掃描的選擇度本身就很低,其實效果和走全表差距也不大,這樣一來也就容易理解上面的統計資訊中大量的buffer gets的原因了。
這個語句如何優化呢,問題還是擺在我們面前,根據和開發同學的溝通,對這個需求有了更多的一些瞭解,而且根據IP來確定範圍,是不會出現多個區域的情況,也就是說,這個結果集要不是1條,要麼是沒有對映到的。
所以語句可以簡單優化一下,只需要新增rownum=1即可。
SELECT "SWD_IP2COUNTY"."IP_ID" "IP_ID","SWD_IP2COUNTY"."COUNTRY" "COUNTRY","SWD_IP2COUNTY"."PROVINCE" "PROVINCE","SWD_IP2COUNTY"."CAPITAL" "CAPITAL" FROM "TEST"."SWD_IP2COUNTY" "SWD_IP2COUNTY" WHERE "SWD_IP2COUNTY"."IP2NUM_LEFT_LINE"<=3030728598
AND "SWD_IP2COUNTY"."IP2NUM_RIGHT_LINE">=3030728598 and rownum=1 ;
這個時候走了索引,而且一致性讀大大降低。
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
。。。
1 rows processed
這個語句在實際環境中的改進效果如何呢,我抓取了對比的資料,發現效能提升了50%。
執行計劃如下:
這種優化方式還是值得借鑑的。而且在這個基礎上可以考慮對索引進行優化,也就是建立符合索引,根據數值的分佈情況(存在》=,《=的數值區間),可以考慮對索引進行排序 比如:(IP2NUM_LEFT_LINE desc , IP2NUM_RIGHT_LINE asc)
這一點上還得感謝兔子的建議,看了下老白總結的案例就是這樣的改進,都是套路,哈哈。
對於效能的比對情況如下,DB time有了很多改進。