在單一的應用環境或業務相對簡單的系統下, 系統性能問題, 瓶頸所在往往是不言自明, 解決問題的前提--定位問題是比較容易解決的, 但在一個複雜的應用環境下, 各應用系統對系統資源往往是一種共享和競爭的關係, 而且應用系統之間也可能存在著共生或制約的關係, 資源利益的均衡往往是此消彼長, 而這種環境下的應用系統一旦出現資源競爭, 系統的瓶頸往往難以斷定, 甚至會發生不同應用設計人員之間互相推諉責任的扯皮現象。本文僅就此問題對Linux平台下各應用系統對Oracle資料庫的使用情況作一探討。Oracle資料庫的TUNING不是一個可以一言以蔽的主題,本文無意概全,內容僅涉及問題的定位及各應用對資料庫資源的共享與競爭問題。
本文試驗及問題取證的環境:
RedHat6.1 Web server(Apache1.3.9+PHP4.0)+Client/Server(Pro*C)之Server端
RedHat6.2 + Oracle8.1.6.1.0
RedHat7.1 Web server(Apache1.3.20+PHP4.06) + Oracle8.1.7.0.0
為方便問題的討論, 應用系統已做簡化, 競爭方僅包括一個Pro*C的daemon程序作為C/S模式的服務端, 和由Apache+PHP所支持的WEB網站業務。
首先, 最簡單的情況莫過於單個SQL語句的分析, SQL語句的優化也是資料庫優化的一個最直接最立竿見影的因素。SQL語句的性能監控從監控工具來說大致可分為由高級語言提供和由Oracle本身提供, 高級語言以典型的應用C 語言和WEB開發語言PHP為例, C語言中可以用gettimeofday函數來在某一資料庫操作之前和之後分別獲取一個時間值, 將兩個時間值之差做為衡量該資料庫操作的效率, 在PHP中, 也可以用gettimeofday, 操作方法當然與C語言中有所不同。當然, PHP中也有其它一些函數可以達到同樣的時間精度, 關於時間精度的考慮, 不能簡單以大小衡量微秒級的時間數值, 因為時鐘中斷的時間間隔從根本上決定了時間計算所能達到的精度, 此外,操作系統本身對進程的時間片分配, 及進程切換的開銷等因素也在一定程度上影響時間數據的意義。所以, 以下時間的計算最理想的情況是對同一操作在儘可能避免緩存的情況下進行多次的循環操作,取總的時間值加以平均, 從而得到比較接近真實情況的時間值。C語言的例子:
#define TV_START 0 #define TV_END 1 int how_long(int cmd, char *res); struct CMD_TIME{ int times; /* times occured within specified package number */ struct timeval time; /* total time consumed by the cmd */ }; void foo() { int id; how_long(TV_START, NULL); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL WHENEVER NOT FOUND CONTINUE; EXEC SQL select user_id into :id from users where name='slimzhao';2; how_long(TV_END, time_consume); puts(time_consume); } int how_long(int cmd, char *res) /* return value: -1 error, 0 sucess , res: 20 bytes is enough */ { static struct timeval before, after; if(cmd == TV_START) { gettimeofday(&before, NULL); return 0; } else if(cmd == TV_END) { gettimeofday(&after, NULL); if(res) { if(after.tv_usec > before.tv_usec) { sprintf(res, "%ld %ld", after.tv_sec - before.tv_sec, after.tv_usec - before.tv_usec); } else { sprintf(res, "%ld %ld", after.tv_sec - before.tv_sec - 1, 1000000 + after.tv_usec - before.tv_usec); } } return 0; } else { return -1; } } |
下面是一個PHP的例子(為簡化起見, 程序的錯誤檢查被忽略):
include "/how_long.inc"; how_long(TV_START, $timestr); $conn = OCILogon("username", "password", "dblink"); $stmt = OCIParse($conn, "select ID from users where name='slimzhao'"); OCIDefineByName($stmt, ID, $id); OCIExecute($stmt); OCIFetch($stmt); OCIFreeStatement($stmt); OCILogoff($conn); how_long(TV_END, $timestr); echo "用戶ID: $id , 該操作消耗時間:$timestr "; ?> |
其中how_long函數的PHP版本如下:
#耗費時間的字元串, 該函數本身的開銷不計入其中. define("TV_START", 0); define("TV_END", 1); function how_long($operation, &$str) #返回值: 0--成功, -1--傳遞了非法的參數. { global $before_SQL, $after_SQL; if($operation == TV_START) { $before_SQL = gettimeofday(); return 0; } else if($operation == TV_END) { $after_SQL = gettimeofday(); if($before_SQL["usec"] > $after_SQL["usec"]) { $str = ($after_SQL["sec"] - $before_SQL["sec"] - 1)."秒". ($after_SQL["usec"] + 1000*1000 -$before_SQL["usec"])."微秒"; } else { $str = ($after_SQL["sec"] - $before_SQL["sec"])."秒". ($after_SQL["usec"]-$before_SQL["usec"])."微秒"; } } else { return -1; } } ?> |
上面的資料庫操作開銷的計算僅限於對時間消耗的計算,對同時使用同一資料庫的其它應用軟體的影響,對磁碟操作的頻繁程度,資料庫操作所採取的具體策略等等因素,都未考慮在內,高級語言也不可能提供這樣的參考數據。而資料庫本身提供的監測手段彌補了這一不足。最簡單的操作控制台:
sqlplus SQL> set timing on |
將為每次執行的資料庫操作進行計時, 精度為1/100秒, 筆者對該功能的使用中發現其時間的計算也有一定的偏差。而且時間偏差很大,嚴格說來,已不屬於誤差的範圍,該歸錯誤了,下面是一個例子中得到的數據:
[bash$] cat tmp.sql set timing on host date; select count(*) from users; host date; SQL> @tmp.sql Wed Dec 5 00:21:01 CST 2001 COUNT(*) ---------- 1243807 Elapsed: 00:00:06.16 Wed Dec 5 00:21:05 CST 2001 |
從系統的時間差來看, 為4秒左右, 但ORACLE卻報告了6.16秒!
如果說Oracle工具在時間計算上太差強人意的話,在SQL語句的執行方案上可算是對SQL語句如何執行的最權威的詮釋了。解讀這樣的信息就需要對Oracle內部對SQL操作的過程有一定了解。