如果需要從 MySQL 服務(wù)端獲得很高的性能象踊,最佳的方式就是花時間研究 MySQL 優(yōu)化和執(zhí)行查詢的機制锉矢。一旦理解了這些,大部分的查詢優(yōu)化是有據(jù)可循的询吴,從而使得整個查詢優(yōu)化的過程更有邏輯性掠河。下圖展示了 MySQL 執(zhí)行查詢的過程:
- 客戶端將 SQL 語句發(fā)送到服務(wù)端。
- 服務(wù)端檢查查詢緩存猛计。如果緩存中已有數(shù)據(jù)唠摹,則直接返回緩存結(jié)果;否則有滑,將 SQL 語句傳遞給下一環(huán)節(jié)跃闹。
- 服務(wù)端解析、預(yù)處理和優(yōu)化 SQL 語句后,傳遞到查詢優(yōu)化器中形成查詢計劃望艺。
- 查詢執(zhí)行引擎通過調(diào)用存儲引擎接口執(zhí)行查詢計劃苛秕。
- 服務(wù)端將查詢結(jié)果返回給客戶端。
上述的幾個步驟都有其復雜性找默,接下來幾篇文章將詳細講述各個環(huán)節(jié)艇劫。查詢優(yōu)化過程尤其復雜,并且理解這一環(huán)節(jié)很重要惩激。
MySQL 客戶端/服務(wù)端協(xié)議
雖然并不需要了解 MySQL 客戶端/服務(wù)端協(xié)議的內(nèi)部細節(jié)店煞,但需要從高應(yīng)用層面理解其是如何工作的。這個協(xié)議是半雙工的风钻,這意味著 MySQL 服務(wù)端不同同時發(fā)送和接收消息顷蟀,以及不可以將消息拆成多條短消息發(fā)送。這種機制一方面使得 MySQL 的通信簡單快速骡技,另一方面也增加了一些限制鸣个。例如,這意味著無法進行流控布朦,一旦一方發(fā)送了消息囤萤,另一方在響應(yīng)前必須接收整個消息。這就好像來回打乒乓球一樣是趴,同一時間只有一方有球,只有接到了球才能把它打回去唆途。
客戶端通過單個數(shù)據(jù)包將查詢語句發(fā)送給服務(wù)端,因此在存在大的查詢語句時配置 max_allowed_packet 很重要窘哈。一旦客戶端發(fā)送查詢語句后吹榴,它就只能等待返回結(jié)果。
相反滚婉,服務(wù)端的響應(yīng)通常是由多個數(shù)據(jù)包組成的图筹。一旦服務(wù)端響應(yīng)后,客戶端必須獲取整個結(jié)果集让腹≡妒#客戶端沒法簡單地獲取幾行然后告訴服務(wù)端不要再發(fā)送剩余的數(shù)據(jù)。如果客戶端僅僅需要返回數(shù)據(jù)前面的幾行骇窍,只能是等待服務(wù)端全部數(shù)據(jù)返回后再從中丟棄不需要的數(shù)據(jù)瓜晤,或者是粗暴地斷開連接。不管哪種方式都不是好的選擇腹纳,因此合適的 LIMIT子句就顯得十分重要痢掠。
大部分的 MySQL連接庫支持獲取整個結(jié)果集并在內(nèi)存中緩存起來驱犹,或者是獲取需要的數(shù)據(jù)行。默認的行為通常是獲取整個結(jié)果集然后在內(nèi)存緩存足画。知道這一點很重要雄驹,因為 MySQL 服務(wù)端在所有請求的數(shù)據(jù)行沒返回前,不會釋放這次查詢的鎖和資源淹辞。大部分客戶端庫會讓你感覺數(shù)據(jù)是從服務(wù)端獲取的医舆,實際上這些數(shù)據(jù)可能僅僅是從緩存中讀取的。這在大部分時間是沒問題的象缀,但對于耗時很久或占據(jù)很多內(nèi)存的大數(shù)據(jù)量查詢來說就不合適了蔬将。如果指定了不緩存查詢結(jié)果,那么占用的內(nèi)存會更小央星,并且可以更快地處理結(jié)果霞怀。缺點是這種方式會在查詢時引起
服務(wù)端的鎖和資源占用。
以 PHP 為例莉给,以下是PHP常用的查詢代碼:
<?php
$link = mysql_connect('localhost', 'user', 'password');
$result = mysql_query('SELECT * FROM huge_table', $link);
while ($row = mysql_fetch_array($result)) {
//處理數(shù)據(jù)結(jié)果
}
?>
這個代碼看起來好像是只獲取了需要的數(shù)據(jù)行里烦。然而,這個查詢通過 mysql_query 的調(diào)用后實際上將全部結(jié)果放到了內(nèi)存中禁谦。而 while 循環(huán)實際上是對內(nèi)存中的數(shù)據(jù)進行循環(huán)迭代。相反废封,如果使用 mysql_unbuffered_query 替代 mysql_query 的話州泊,那就不會緩存結(jié)果。
<?php
$link = mysql_connect('localhost', 'user', 'password');
$result = mysql_unbuffered_query('SELECT * FROM huge_table', $link);
while ($row = mysql_fetch_array($result)) {
//處理數(shù)據(jù)結(jié)果
}
?>
不同的編程語言處理緩存覆蓋的方式不同漂洋。例如遥皂,Perl 的 DBD::mysql 驅(qū)動需要通過 mysql_use_result 屬性指定 C 語音客戶端庫(默認是 mysql_buffer_result),示例如下:
#!/usr/bin/perl
use DBI;
my $dbn = DBI->connect('DBI:mysql:;host=localhost', 'user', 'password');
my $sth = $dbn->prepare('SELECT * FROM huge_table', {mysql_use_result => 1});
$sth->execute();
while (my $row = $sth->fetchrow_array()) {
#處理數(shù)據(jù)結(jié)果
}
注意到 prepare 指定了使用結(jié)果而不是緩存結(jié)果刽漂。也可以通過在連接的時候指定演训,這會使得每次查詢都不緩存。
my $dbn = DBI->connect('DBI:mysql:;mysql_use_result=1;host=localhost', 'user', 'password');