利用線性擬合模型預測SQL性能隱患

#利用線性擬合模型發現測試環境性能隱患
##一個經典的性能問題
投產後數週,生產環境出現了系統響應緩慢、數據庫負載衝高、大量數據庫連接不釋放的生產事件,問題原因是由於一張表的數據量在投產後持續增長,且有聯機語句使用了全表掃描訪問了這張表,導致執行時間變長,數據庫鎖等待增加,資源被持續消耗,引發數據庫性能問題。這類問題在過去的幾年中一直困擾着我們,令我們不勝其煩。當然,也有很多朋友支了很多招,比如恢復全量的生產數據,或者杜絕所有聯機全表掃描,但是這些辦法都不太奏效。首先,測試環境資源有限,尤其是沒有那麼多存儲;其次,聯機全表掃描並不違反開發規範,而且針對數據量很小的全表掃描是非常合理的,因此很難說服開發去做修改。所以這個問題最終還得靠測試自己來解決。

##解決方法
**“性能指標的持續增長在未來必然會引發測試問題。”**應該是性能測試領域的定理,我們根據這條定理,定製了一個趨勢分析模型,用來找出那些存在持續增長趨勢的指標。原理非常簡單:

  1. 利用自主開發的數據庫監控系統,記錄測試環境發生的所有全表掃描語句
  2. 對全表掃描的對象統計記錄數,每天記錄一次
  3. 利用python的一次線性擬合模型,對全表掃描對象的測試和生產環境記錄數進行線性擬合
  4. 根據擬合結果,對增長趨勢>0,擬合度>80%的記錄進行統計
  5. 根據記錄中的sqlid深入分析

同理,我們也將語句執行時間、語句cost、操作系統CPU、內存使用率,網絡連接數等指標也納入了該模型分析。這樣就能夠在日常的功能測試中,發現那些存在性能隱患,但是有沒有達到報警閾值的性能問題。

##代碼

# encoding=gbk

#根據APP_SQL_COST表中記錄,統計COST和執行時間持續增長的語句
import math
import cx_Oracle
import collections

#一次線性擬合模型
def linefit(x , y):
   N = float(len(x))
   sx,sy,sxx,syy,sxy=0,0,0,0,0
   for i in range(0,int(N)):
       sx  += x[i]
       sy  += y[i]
       sxx += x[i]*x[i]
       syy += y[i]*y[i]
       sxy += x[i]*y[i]
   a = (sy*sx/N -sxy)/( sx*sx/N -sxx)
   b = (sy - a*sx)/N
   if a==0:
       r=0
   else:
       r = abs(sy*sx/N-sxy)/math.sqrt((sxx-sx*sx/N)*(syy-sy*sy/N))
   return a,b,r

def func_table_full(appname,addr,dbsid):
   str0="SELECT OBJECT_OWNER, OBJECT_NAME FROM APP_SQL_PLAN_STAT WHERE APPNAME='"+APPNAME+"' AND ADDR='"+ADDR+"' AND DBSID='"+DBSID+"' GROUP BY OBJECT_OWNER, OBJECT_NAME"
   cursor0 = conn.cursor()
   cursor0.execute(str0)
   res0 = cursor0.fetchall()
   rowcount0 = len(res0)
   x=0
   result=[]
   while x<rowcount0:
       table_owner=res0[x][0]
       table_name=res0[x][1]
       str1="SELECT TABLE_NAME,rank() over(partition by TABLE_NAME order by EXEC_SEQ,CHECK_TIME,STAT_DATE) rk,nvl(rows_count,0) FROM APP_TABLE_ROWS WHERE  APPNAME='"+APPNAME+"' AND ADDR='"+ADDR+"' AND DBSID='"+DBSID+"' AND TABLE_NAME='"+table_name+"' AND OWNER='"+table_owner+"'"
       cursor1 = conn.cursor()
       cursor1.execute(str1)
       res1 = cursor1.fetchall()
       rowcount1 = len(res1)
       RK=[]
       ROWS=[]
       if rowcount1>=3:
           y = 0
           while y<rowcount1:
               RK.append(res1[y][1])
               ROWS.append(res1[y][2])
               y=y+1
           try:
               a,b,r=linefit(RK,ROWS)
           except:
               print APPNAME,ADDR,DBSID,ROWS
           if a>0 and r>0.8 :
               str2="INSERT INTO APP_GROWTH_MODEL VALUES('"+APPNAME+"','"+ADDR+"','"+DBSID+"','ROWS','"+table_owner+"."+table_name+"','"+str(max(ROWS))+"','"+str(a)+"','"+str(r)+"','"+str(len(RK))+"','')"
               cursor2 = conn.cursor()
               cursor2.execute(str2)
               cursor2.execute("commit")
       str2 = "SELECT TABLE_NAME,rank() over(partition by TABLE_NAME order by to_date(COLLECT_DATE,'YYYY-MM-DD')) rk,nvl(NUM_ROWS,0) FROM MCTEST.DB_SC_ROW_NUM WHERE   UPPER(DBSID)=UPPER('"+DBSID+"') AND TABLE_NAME='"+table_name+"' AND OWNER='"+table_owner+"'"
       cursor1 = conn.cursor()
       cursor1.execute(str2)
       res1 = cursor1.fetchall()
       rowcount1 = len(res1)
       RK=[]
       ROWS=[]
       if rowcount1>=3:
           y = 0
           while y<rowcount1:
               RK.append(res1[y][1])
               ROWS.append(res1[y][2])
               y=y+1
           try:
               a,b,r=linefit(RK,ROWS)
           except:
               print APPNAME,ADDR,DBSID,ROWS
           if a>0 and r>0.8 :
               str2="INSERT INTO APP_GROWTH_MODEL VALUES('"+APPNAME+"','"+ADDR+"','"+DBSID+"','SCROWS','"+table_owner+"."+table_name+"','"+str(max(ROWS))+"','"+str(a)+"','"+str(r)+"','"+str(len(RK))+"','')"
               #print str2
               cursor2 = conn.cursor()
               cursor2.execute(str2)
               cursor2.execute("commit")
       x=x+1



if __name__ == '__main__':
   distinct_count=3
   conn = cx_Oracle.connect('test/[email protected]/test')
   strdel="DELETE FROM APP_GROWTH_MODEL"
   cursordel=conn.cursor()
   cursordel.execute(strdel)
   cursordel.execute("commit")
   str2="SELECT APPNAME,ADDR,DBSID FROM APP_SQL_COST GROUP BY APPNAME,ADDR,DBSID"
   cursor2 = conn.cursor()
   cursor2.execute(str2)
   res2 = cursor2.fetchall()
   rowcount2 = len(res2)
   z=0
   while z<rowcount2:
       APPNAME=res2[z][0]
       ADDR=res2[z][1]
       DBSID=res2[z][2]
       table_full=func_table_full(APPNAME,ADDR,DBSID)
       str0="SELECT DISTINCT SQL_ID FROM APP_SQL_COST WHERE APPNAME='"+APPNAME+"' AND ADDR='"+ADDR+"' AND DBSID='"+DBSID+"'"
       cursor0 = conn.cursor()
       cursor0.execute(str0)
       res0 = cursor0.fetchall()
       rowcount0 = len(res0)
       x=0
       while x<rowcount0:
           SQL_ID=str(res0[x][0])
           str1="SELECT  SQL_ID,rank() over(partition by SQL_ID order by STAT_DATE,SNAP_ID,PLAN_HASH_VALUE) rk,nvl(cost,0),nvl(elapsed_time,0) FROM APP_SQL_COST WHERE APPNAME='"+APPNAME+"' AND ADDR='"+ADDR+"' AND DBSID='"+DBSID+"' AND SQL_ID='"+SQL_ID+"'"
           cursor1 = conn.cursor()
           cursor1.execute(str1)
           res1 = cursor1.fetchall()
           rowcount1 = len(res1)
           RK=[]
           COST=[]
           ELAPSED_TIME=[]
           if rowcount1>=3:
               y = 0
               while y<rowcount1:
                   #print res1[y][1],res1[y][2], res1[y][3]
                   RK.append(res1[y][1])
                   COST.append(res1[y][2])
                   ELAPSED_TIME.append(res1[y][3])
                   y=y+1
               try:
                   a,b,r=linefit(RK,COST)
               except:
                   print APPNAME,ADDR,DBSID,COST
               distinct_cost=len(set(COST))
               if a>0 and r>0.8 and distinct_cost>=distinct_count and max(COST)>1000:
                   str_ins="INSERT INTO APP_GROWTH_MODEL VALUES('"+APPNAME+"','"+ADDR+"','"+DBSID+"','COST','"+SQL_ID+"','"+str(max(COST))+"','"+str(a)+"','"+str(r)+"','"+str(len(RK))+"','')"
                   cursor_ins = conn.cursor()
                   cursor_ins.execute(str_ins)
                   cursor_ins.execute("commit")
               try:
                   a,b,r=linefit(RK,ELAPSED_TIME)
               except:
                   print APPNAME,ADDR,DBSID,ELAPSED_TIME
               distinct_time=len(set(ELAPSED_TIME))
               if a>0 and r>0.8 and distinct_time>=distinct_count  and max(ELAPSED_TIME)>60:
                   str_ins="INSERT INTO APP_GROWTH_MODEL VALUES('"+APPNAME+"','"+ADDR+"','"+DBSID+"','ELAPSED_TIME','"+SQL_ID+"','"+str(max(ELAPSED_TIME))+"','"+str(a)+"','"+str(r)+"','"+str(len(RK))+"','')"
                   cursor_ins = conn.cursor()
                   cursor_ins.execute(str_ins)
                   cursor_ins.execute("commit")
           x=x+1
       z=z+1
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章