oracle sql語句跟蹤及性能分析工具實現


在網上找了一大圈,沒找着合適的工具來跟蹤oracle一段時間的sql。

我們的場景是打算自動化跑遍所有場景(rft)+fiddler跟蹤請求+后端跟蹤sql,根據結果去分析慢的請求和sql,本來awr報告也能實現,但是每次都用awr比較麻煩,想的是能夠簡單點,直接定位到執行慢的sql或者耗cpu的sql。

這個工具抓取的sql不是實時的,是某段時間的sql,原理也很簡單,比awr的sql要簡單的多,試了一把,跟awr給出的數據是一致的。

最終效果:准備操作時開啟跟蹤,結束操作后關閉跟蹤,然后工具會提供這段時間的sql及相關指標,包括磁盤讀次數、cpu、sql執行時間、解析次數、執行次數等,根據需要可以把跟蹤結果保存到數據庫或者導出到excel。

視圖介紹

用到的視圖:v$sqlarea

這個視圖存放了近期執行過的sql,如果想要達到awr的效果,可以再結合dba_hist_sqlstat等相關視圖進行查詢。

本次用到的相關字段:

SQL_FULLTEXT:完整的sql語句,類型為lob字段,要獲取內容可以使用DBMS_LOB.SUBSTR(Sql_Fulltext,2000,1)進行讀取,獲取sql前2000個字符;

PARSING_SCHEMA_NAME:執行sql的用戶;

MODULE:執行sql的應用程序,如IIS為w3wp.exe,plsql為PL/SQL Developer;

PARSE_CALLS:解析次數,包括軟解析和硬解析;

EXECUTIONS:sql執行次數;

BUFFER_GETS:讀內存次數;

DISK_READS:讀磁盤次數(可以結合讀內存次數計算命中率);

CPU_TIME:cpu時間,微秒;

ELAPSED_TIME:sql執行時間,微秒;

LAST_ACTIVE_TIME:sql上次的活動時間。

由於v$sqlarea存放的數據都為累加值,所以要計算本次的數據的話,需要減去之前的舊數據。

實現原理

開始執行操作前,備份v$sqlarea,存放到oldtable;

獲取當前系統時間,存放到sysdate;

結束操作后,備份v$sqlarea,存放到newtable。至此,准備工作就做完了,接下來就是整合數據了。

 

根據newtable中的last_active_time>sysdate獲取本次執行過的sql,然后相關的指標數據都是newtable減去oldtable(兩張表的關聯字段為hash_value和address,sql_id應該也可以)。

要注意newtable中新出現的sql,所以用left join並用nvl轉換一下空值。如果關心其他指標,可以根據實際情況添加字段。

如果要避免其他因素的干擾,可以只顯示符合要求的sql,比如PARSING_SCHEMA_NAME=登陸用戶或者MODULE=w3wp.exe等。

核心sql:

sql = "SELECT * FROM (SELECT n.parsing_schema_name AS SCHEMA,n.module AS MODULE, DBMS_LOB.SUBSTR(n.Sql_Fulltext,2000,1) AS SQL_TEXT,n.parse_calls-nvl(o.parse_calls,0) AS PARSE_CALLS,n.buffer_gets-nvl(o.buffer_gets,0) AS BUFFER_GETS,n.disk_reads-nvl(o.disk_reads,0) AS DISK_READS,n.executions-nvl(o.executions,0) AS EXECUTIONS,round((n.cpu_time-nvl(o.cpu_time,0))/1000000,2) AS CPU_TIME,round((n.cpu_time-nvl(o.cpu_time,0))/((n.executions-nvl(o.executions,0))*1000000),2) AS CPU_TIME_PER_EXE,round((n.elapsed_time-nvl(o.elapsed_time,0))/((n.executions-nvl(o.executions,0))*1000000),2) AS ELAPSED_TIME_PER_EXE " +
"FROM "+NewTable+" n " +
"LEFT JOIN "+OldTable+" o ON o.hash_value = n.hash_value AND o.address = n.address " +
"WHERE n.last_active_time > to_date('" + Sysdate + "','yyyy/MM/dd hh24:mi:ss') " +
"AND(n.executions - nvl(o.executions,0)) > 0 " +
"ORDER BY ELAPSED_TIME_PER_EXE DESC) WHERE ROWNUM<=20";

最終效果(c#版):

 2017.11更新

后續修改了下,去掉了導出excel的功能,改為保存到數據庫了。

然后把sqlserver的也寫了下,跟sqlserver profiler比對了下,大致差不多,但是總是有個1s左右的出入,不知道啥原因,也是醉了。

可能是方法不對吧。。。

附上sqlserver的核心sql:

sql = "select top 20 ST.text as SQL_TEXT,cast((n.total_worker_time-isnull(o.total_worker_time,0))*1.0/(n.execution_count-isnull(o.execution_count,0)) as decimal(10,2)) as PER_CPU_TIME,cast((n.total_physical_reads-isnull(o.total_physical_reads,0))*1.0/(n.execution_count-isnull(o.execution_count,0)) as decimal(10,2)) as PHYSICAL_READS,cast((n.total_logical_reads-isnull(o.total_logical_reads,0))*1.0/(n.execution_count-isnull(o.execution_count,0)) as decimal(10,2)) as LOGICAL_READS,cast((n.total_logical_writes-isnull(o.total_logical_writes,0))*1.0/(n.execution_count-ISNULL(o.execution_count,0)) as decimal(10,2)) as LOGICAL_WRITES,n.execution_count-isnull(o.execution_count,0) as EXECUTIONS,cast(n.last_elapsed_time*1.0/1000000 as decimal(10,2)) as LAST_EXEC_TIME,cast((n.total_elapsed_time-isnull(o.total_elapsed_time,0))*1.0/((n.execution_count-isnull(o.execution_count,0))*1000000) as decimal(10,2)) as PER_EXEC_TIME from " + SqlNewTable+" n "+
"left join "+SqlOldTable+" o on o.plan_handle = n.plan_handle "+
"CROSS APPLY "+
"sys.dm_exec_sql_text(n.sql_handle) ST "+
"where n.last_execution_time > '"+SqlSysdate+"' and n.creation_time < '"+SqlSysdate+"' "+
"and n.execution_count - o.execution_count > 0 "+
"union "+
"select ST.text as SQL_TEXT,cast(n.total_worker_time * 1.0 / n.execution_count as decimal(10, 2)) as PER_CPU_TIME,cast(n.total_physical_reads * 1.0 / n.execution_count as decimal(10, 2)) as PHYSICAL_READS,cast(n.total_logical_reads * 1.0 / n.execution_count as decimal(10, 2)) as LOGICAL_READS,cast(n.total_logical_writes * 1.0 / n.execution_count as decimal(10, 2)) as LOGICAL_WRITES,n.execution_count as EXECUTIONS,cast(n.last_elapsed_time * 1.0 / 1000000 as decimal(10, 2)) as LAST_EXEC_TIME,cast(n.total_elapsed_time * 1.0 / (n.execution_count * 1000000) as decimal(10, 2)) as PER_EXEC_TIME from " + SqlNewTable+" n "+
"CROSS APPLY "+
"sys.dm_exec_sql_text(n.sql_handle) ST "+
"where n.creation_time > '"+SqlSysdate+"' "+
"order by PER_EXEC_TIME desc";

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM