在單一的應(yīng)用環(huán)境或業(yè)務(wù)相對簡單的系統(tǒng)下,系統(tǒng)性能問題,瓶頸所在往往是不言自明,解決問題的前提——定位問題是比較容易解決的,但在一個(gè)復(fù)雜的應(yīng)用環(huán)境下,各應(yīng)用系統(tǒng)對系統(tǒng)資源往往是一種共享和競爭的關(guān)系,而且應(yīng)用系統(tǒng)之間也可能存在著共生或制約的關(guān)系,資源利益的均衡往往是此消彼長,而這種環(huán)境下的應(yīng)用系統(tǒng)一旦出現(xiàn)資源競爭,系統(tǒng)的瓶頸往往難以斷定,甚至?xí)l(fā)生不同應(yīng)用設(shè)計(jì)人員之間互相推諉責(zé)任的扯皮現(xiàn)象,本文僅就此問題對Linux平臺下各應(yīng)用系統(tǒng)對ORACLE數(shù)據(jù)庫的使用情況作一探討,ORACLE數(shù)據(jù)庫的TUNING不是一個(gè)可以一言以蔽的主題,本文無意概全,內(nèi)容僅涉及問題的定位及各應(yīng)用對數(shù)據(jù)庫資源的共享與競爭問題。
本文試驗(yàn)及問題取證的環(huán)境:
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
為方便問題的討論,應(yīng)用系統(tǒng)已做簡化,競爭方僅包括一個(gè)Pro*C的daemon程序作為C/S模式的服務(wù)端,和由Apache+PHP所支持的WEB網(wǎng)站業(yè)務(wù)。
1. 單個(gè)SQL語句的處理
首先,最簡單的情況莫過于單個(gè)SQL語句的分析,SQL語句的優(yōu)化也是數(shù)據(jù)庫優(yōu)化的一個(gè)最直接最立竿見影的因素。SQL語句的性能監(jiān)控從監(jiān)控工具來說大致可分為由高級語言提供和由ORACLE本身提供,高級語言以典型的應(yīng)用C 語言和WEB開發(fā)語言PHP為例,C語言中可以用gettimeofday函數(shù)來在某一數(shù)據(jù)庫操作之前和之后分別獲取一個(gè)時(shí)間值,將兩個(gè)時(shí)間值之差做為衡量該數(shù)據(jù)庫操作的效率,在PHP中,也可以用gettimeofday, 操作方法當(dāng)然與C語言中有所不同. 當(dāng)然, PHP中也有其它一些函數(shù)可以達(dá)到同樣的時(shí)間精度, 關(guān)于時(shí)間精度的考慮, 不能簡單以大小衡量微秒級的時(shí)間數(shù)值, 因?yàn)闀r(shí)鐘中斷的時(shí)間間隔從根本上決定了時(shí)間計(jì)算所能達(dá)到的精度, 此外, 操作系統(tǒng)本身對進(jìn)程的時(shí)間片分配, 及進(jìn)程切換的開銷等因素也在一定程度上影響時(shí)間數(shù)據(jù)的意義. 所以, 以下時(shí)間的計(jì)算最理想的情況是對同一操作在盡可能避免緩存的情況下進(jìn)行多次的循環(huán)操作, 取總的時(shí)間值加以平均, 從而得到比較接近真實(shí)情況的時(shí)間值。
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;
}
}
==========================================================
下面是一個(gè)PHP的例子(為簡化起見, 程序的錯(cuò)誤檢查被忽略)
==========================================================
<?
include "<path_to_file>/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 , 該操作消耗時(shí)間:$timestr<br>";
?>
其中how_long函數(shù)的PHP版本如下:
<?
#作者: slimzhao@21cn.com
#當(dāng)前維護(hù)人: slimzhao@21cn.com
#創(chuàng)建日期: 2001.12.04 00:18:00
#目的, 在一個(gè)操作之前或之后調(diào)用該函數(shù)的不同版本, 將得到一個(gè)記載了該操作
#耗費(fèi)時(shí)間的字符串, 該函數(shù)本身的開銷不計(jì)入其中.
define("TV_START", 0);
define("TV_END", 1);
function how_long($operation, &$str)
#返回值: 0--成功, -1--傳遞了非法的參數(shù).
{
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;
}
}
?>
==========================================================
上面的數(shù)據(jù)庫操作開銷的計(jì)算僅限于對時(shí)間消耗的計(jì)算, 對同時(shí)使用同一數(shù)據(jù)庫的其它應(yīng)用軟件的影響, 對磁盤操作的頻繁程度, 數(shù)據(jù)庫操作所采取的具體策略等等因素, 都未考慮在內(nèi), 高級語言也不可能提供這樣的參考數(shù)據(jù). 而數(shù)據(jù)庫本身提供的監(jiān)測手段彌補(bǔ)了這一不足. 最簡單的操作控制臺:sqlplus
SQL> set timing on
將為每次執(zhí)行的數(shù)據(jù)庫操作進(jìn)行計(jì)時(shí), 精度為1/100秒, 筆者對該功能的使用中發(fā)現(xiàn)其時(shí)間的計(jì)算也有一定的偏差. 而且時(shí)間偏差很大, 嚴(yán)格說來, 已不屬于誤差的范圍, 該歸錯(cuò)誤了, 下面是一個(gè)例子中得到的數(shù)據(jù):
[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
從系統(tǒng)的時(shí)間差來看, 為4秒左右, 但ORACLE卻報(bào)告了6.16秒!
如果說ORACLE工具在時(shí)間計(jì)算上太差強(qiáng)人意的話, 在SQL語句的執(zhí)行方案上可算是對SQL語句如何執(zhí)行的最權(quán)威的詮釋了. 解讀這樣的信息需要對ORACLE內(nèi)部對SQL 操作的過程有一定了解, 下面是該功能的一樣典型示例:
SQL> set autotrace on
SQL> select count(*) from users;
COUNT(*)
----------
1243807
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1)
1 0 SORT (AGGREGATE)
2 1 INDEX (FAST FULL SCAN) OF 'USER_BASEINFO$NAME' (UNIQUE)
(Cost=4 Card=1244840)
Statistics
----------------------------------------------------------
0 recursive calls
4 db block gets
3032 consistent gets
3033 physical reads
0 redo size
370 bytes sent via SQL*Net to client
424 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
Execution Plan下的信息顯示ORACLE制定了一個(gè)什么樣的計(jì)劃來完成SQL操作的,SQL語言是一種4GL語言, 其特點(diǎn)是告訴系統(tǒng)做什么, 而不提供如何做的信息. 當(dāng)然, 最終的具體工作總得有人做的, 只是由數(shù)據(jù)庫自動制定而不是程序員人為指定一個(gè)具體的操作步驟, 制作這個(gè)步驟當(dāng)然要有所依據(jù), ORACLE有兩個(gè)基本原則來決定如何優(yōu)化: cost-based(基于開銷的優(yōu)化)和rule-based(基于規(guī)則的優(yōu)化). 基于開銷的優(yōu)化的工作方式依賴于數(shù)據(jù)庫對SQL語句所操作的數(shù)據(jù)對象(可簡單認(rèn)為就是表)的數(shù)據(jù)特征的統(tǒng)計(jì)特性進(jìn)行收集和分析. 收集分析的工作由DBA來定期執(zhí)行 , 時(shí)間間隔依數(shù)據(jù)變化頻率而定, 以保持統(tǒng)計(jì)數(shù)據(jù)一定的準(zhǔn)確性, 具體操作請參照 analyze 語句. Oracle準(zhǔn)備在將來的版本中取消對基于開銷的優(yōu)化方案的支持, 因?yàn)檫@種方案需要大量的數(shù)據(jù)收集與分析工作, 且總會有一定的誤差, 這造成最終的執(zhí)行方案往往不是最優(yōu)的.
基于規(guī)則的優(yōu)化則是依據(jù)一些數(shù)據(jù)操作效率的規(guī)則進(jìn)行選擇, 優(yōu)化的核心在于效率, 時(shí)間上盡可能短, 空間上盡可能少進(jìn)行IO 操作. 兩種優(yōu)化方案都絕非十全十美, ORACLE雖將其稱為優(yōu)化方案, 筆者的觀察結(jié)果表明, ORACLE制定出一個(gè)不是最優(yōu)或錯(cuò)誤的執(zhí)行方案也是完全可能的. 以上為例, Oracle的優(yōu)化策略是Choose, 所謂Choose就是cost-based或rule-based , 讓ORACLE自 關(guān)鍵詞標(biāo)簽:性能,監(jiān)控,環(huán)境,應(yīng)用,