8G內(nèi)存下MySQL的優(yōu)化
按照下面的設(shè)置試試看:
key_buffer = 3840M
max_allowed_packet = 16M
table_cache = 1024
sort_buffer_size = 32M
read_buffer_size = 32M
read_rnd_buffer_size = 32M
myisam_sort_buffer_size = 256M
thread_cache_size = 32
query_cache_size = 256M
# Try number of CPU's*2 for thread_concurrency
thread_concurrency = 8
其中key_buffer_size 上限是4G,不能再多了
但是實(shí)際的為了使MySql的性能最優(yōu)化,內(nèi)存的分配是需要進(jìn)行調(diào)試的
mysql大內(nèi)存高性能優(yōu)化
服務(wù)器硬件對(duì)MySQL性能的影響
1、磁盤尋道能力(磁盤I/O),以目前高轉(zhuǎn)速SCSI硬盤(7200轉(zhuǎn)/秒)為例,這種硬盤理論上每秒尋道7200次,這是物理特性決定的,沒(méi)有辦法改變。 MySQL每秒鐘都在進(jìn)行大量、復(fù)雜的查詢操作,對(duì)磁盤的讀寫量可想而知。所以,通常認(rèn)為磁盤I/O是制約MySQL性能的最大因素之一,對(duì)于日均訪問(wèn)量 在100萬(wàn)PV以上的Discuz!論壇,由于磁盤I/O的制約,MySQL的性能會(huì)非常低下!解決這一制約因素可以考慮以下幾種解決方案:? 使用RAID-0+1磁盤陣列,注意不要嘗試使用RAID-5,MySQL在RAID-5磁盤陣列上的效率不會(huì)像你期待的那樣快。
2、CPU 對(duì)于MySQL應(yīng)用,推薦使用S.M.P.架構(gòu)的多路對(duì)稱CPU,例如:可以使用兩顆Intel Xeon 3.6GHz的CPU,現(xiàn)在我較推薦用4U的服務(wù)器來(lái)專門做 數(shù)據(jù)庫(kù) 服務(wù)器,不僅僅是針對(duì)于mysql。
3、物理內(nèi)存對(duì)于一臺(tái)使用MySQL的Database Server來(lái)說(shuō),服務(wù)器內(nèi)存建議不要小于2GB,推薦使用4GB以上的物理內(nèi)存,不過(guò)內(nèi)存對(duì)于現(xiàn)在的服務(wù)器而言可以說(shuō)是一個(gè)可以忽略的問(wèn)題,工作中遇到了高端服務(wù)器基本上內(nèi)存都超過(guò)了32G。
MySQL自身因素
當(dāng)解決了上述服務(wù)器硬件制約因素后,讓我們看看MySQL自身的優(yōu)化是如何操作的。對(duì)MySQL自身的優(yōu)化主要是對(duì)其配置文件 my.cnf中的各項(xiàng)參數(shù)進(jìn)行優(yōu)化調(diào)整。下面我們介紹一些對(duì)性能影響較大的參數(shù)。由于my.cnf文件的優(yōu)化設(shè)置是與服務(wù)器硬件配置息息相關(guān)的,因而我們指定一個(gè)假想的服務(wù)器硬件環(huán)境:
CPU:2顆Intel Xeon 2.4GHz?
內(nèi)存:4GB DDR?
硬盤:SCSI 73GB(很常見(jiàn)的2U服務(wù)器)。
下面,我們根據(jù)以上硬件配置結(jié)合一份已經(jīng)優(yōu)化好的my.cnf進(jìn)行說(shuō)明:
以下只列出my.cnf文件中[mysqld]段落中的內(nèi)容,其他段落內(nèi)容對(duì)MySQL運(yùn)行性能影響甚微,因而姑
且忽略。
[mysqld]
port = 3306
serverid = 1
socket = /tmp/mysql.sock
skip-locking
#避免MySQL的外部鎖定,減少出錯(cuò)幾率增強(qiáng)穩(wěn)定性。
skip-name-resolve
#禁止MySQL對(duì)外部連接進(jìn)行DNS解析,使用這一選項(xiàng)可以消除MySQL進(jìn)行DNS解析的時(shí)間。但需要注意,如果開(kāi)啟該選項(xiàng),則所有遠(yuǎn)程主機(jī)連接授權(quán)都要使用IP地址方式,否則MySQL將無(wú)法正常處理連接請(qǐng)求!
back_log = 384
#back_log 參數(shù)的值指出在MySQL暫時(shí)停止響應(yīng)新請(qǐng)求之前的短時(shí)間內(nèi)多少個(gè)請(qǐng)求可以被存在堆棧中。如果系統(tǒng)在一個(gè)短時(shí)間內(nèi)有很多連接,則需要增大該參數(shù)的值,該參數(shù)值指定到來(lái)的TCP/IP連接的偵聽(tīng)隊(duì)列的大小。不同的操作系統(tǒng)在這個(gè)隊(duì)列大小上有它自 己的限制。 試圖設(shè)定back_log高于你的操作系統(tǒng)的限制將是無(wú)效的。默認(rèn)值為50。對(duì)于Linux系統(tǒng)推薦設(shè)置為小于512的整數(shù)。
key_buffer_size = 256M
#key_buffer_size指定用于索引的緩沖區(qū)大小,增加它可得到更好的索引處理性能。對(duì)于內(nèi)存在4GB左右的服務(wù)器該參數(shù)可設(shè)置為256M或384M。注意:該參數(shù)值設(shè)置的過(guò)大反而會(huì)是服務(wù)器整體效率降低!
max_allowed_packet = 4M
thread_stack = 256K
table_cache = 128K
sort_buffer_size = 6M
#查詢排序時(shí)所能使用的緩沖區(qū)大小。注意:該參數(shù)對(duì)應(yīng)的分配內(nèi)存是每連接獨(dú)占,如果有100個(gè)連接,那么實(shí)際分配的總共排序緩沖區(qū)大小為100 × 6 = 600MB。所以,對(duì)于內(nèi)存在4GB左右的服務(wù)器推薦設(shè)置為6-8M。
read_buffer_size = 4M
#讀查詢操作所能使用的緩沖區(qū)大小。和sort_buffer_size一樣,該參數(shù)對(duì)應(yīng)的分配內(nèi)存也是每連接獨(dú)享。
join_buffer_size = 8M
#聯(lián)合查詢操作所能使用的緩沖區(qū)大小,和sort_buffer_size一樣,該參數(shù)對(duì)應(yīng)的分配內(nèi)存也是每連接獨(dú)享。
myisam_sort_buffer_size = 64M
table_cache = 512
thread_cache_size = 64
query_cache_size = 64M
# 指定MySQL查詢緩沖區(qū)的大小。可以通過(guò)在MySQL控制臺(tái)觀察,如果Qcache_lowmem_prunes的值非常大,則表明經(jīng)常出現(xiàn)緩沖不夠的 情況;如果Qcache_hits的值非常大,則表明查詢緩沖使用非常頻繁,如果該值較小反而會(huì)影響效率,那么可以考慮不用查詢緩 沖;Qcache_free_blocks,如果該值非常大,則表明緩沖區(qū)中碎片很多。
tmp_table_size = 256M
max_connections = 768
#指定MySQL允許的最大連接進(jìn)程數(shù)。如果在訪問(wèn)論壇時(shí)經(jīng)常出現(xiàn)Too Many Connections的錯(cuò)誤提 示,則需要增大該參數(shù)值。
max_connect_errors = 10000000
wait_timeout = 10
#指定一個(gè)請(qǐng)求的最大連接時(shí)間,對(duì)于4GB左右內(nèi)存的服務(wù)器可以設(shè)置為5-10。
thread_concurrency = 8
#該參數(shù)取值為服務(wù)器邏輯CPU數(shù)量*2,在本例中,服務(wù)器有2顆物理CPU,而每顆物理CPU又支持H.T超線程,所以實(shí)際取值為4*2=8
skip-networking
#開(kāi)啟該選項(xiàng)可以徹底關(guān)閉MySQL的TCP/IP連接方式,如果WEB服務(wù)器是以遠(yuǎn)程連接的方式訪問(wèn)MySQL數(shù)據(jù)庫(kù)服務(wù)器則不要開(kāi)啟該選項(xiàng)!否則將無(wú)法正常連接!
table_cache=1024
#物理內(nèi)存越大,設(shè)置就越大.默認(rèn)為2402,調(diào)到512-1024最佳
innodb_additional_mem_pool_size=4M
#默認(rèn)為2M
innodb_flush_log_at_trx_commit=1
#設(shè)置為0就是等到innodb_log_buffer_size列隊(duì)滿后再統(tǒng)一儲(chǔ)存,默認(rèn)為1
innodb_log_buffer_size=2M
#默認(rèn)為1M
innodb_thread_concurrency=8
#你的服務(wù)器CPU有幾個(gè)就設(shè)置為幾,建議用默認(rèn)一般為8
key_buffer_size=256M
#默認(rèn)為218,調(diào)到128最佳
tmp_table_size=64M
#默認(rèn)為16M,調(diào)到64-256最掛
read_buffer_size=4M
#默認(rèn)為64K
read_rnd_buffer_size=16M
#默認(rèn)為256K
sort_buffer_size=32M
#默認(rèn)為256K
thread_cache_size=120
#默認(rèn)為60
query_cache_size=32M
值得注意的是:很多情況需要具體情況具體分析。
如果Key_reads太大,則應(yīng)該把my.cnf中Key_buffer_size變大,保持Key_reads/Key_read_ request s至少1/100以上,越小越好。
提升性能的建議
1.如果opened_tables太大,應(yīng)該把my.cnf中的table_cache變大
2.如果Key_reads太大,則應(yīng)該把my.cnf中key_buffer_size變大.可以用Key_reads/Key_read_requests計(jì)算出cache失敗率
3.如果Handler_read_rnd太大,則你寫的SQL語(yǔ)句里很多查詢都是要掃描整個(gè)表,而沒(méi)有發(fā)揮鍵的作用
4.如果Threads_created太大,就要增加my.cnf中thread_cache_size的值.可以用Threads_created/Connections計(jì)算cache命中率
5.如果Created_tmp_disk_tables太大,就要增加my.cnf中tmp_table_size的值,用基于內(nèi)存的臨時(shí)表代替基于磁盤的
除了機(jī)器優(yōu)化我們的sql語(yǔ)句也可以優(yōu)化
1. 為查詢緩存優(yōu)化你的查詢
大多數(shù)的MySQL服務(wù)器都開(kāi)啟了查詢緩存。這是提高性最有效的方法之一,而且這是被MySQL的數(shù)據(jù)庫(kù)引擎處理的。當(dāng)有很多相同的查詢被執(zhí)行了多次的時(shí)候,這些查詢結(jié)果會(huì)被放到一個(gè)緩存中,這樣,后續(xù)的相同的查詢就不用操作表而直接訪問(wèn)緩存結(jié)果了。
這里最主要的問(wèn)題是,對(duì)于程序員來(lái)說(shuō),這個(gè)事情是很容易被忽略的。因?yàn)椋覀兡承┎樵冋Z(yǔ)句會(huì)讓MySQL不使用緩存。請(qǐng)看下面的示例:?
// 查詢緩存不開(kāi)啟?
$r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");
// 開(kāi)啟查詢緩存?
$today = date("Y-m-d");?
$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");
上面兩條SQL語(yǔ)句的差別就是 CURDATE() ,MySQL的查詢緩存對(duì)這個(gè)函數(shù)不起作用。所以,像 NOW() 和 RAND() 或是其它的諸如此類的SQL函數(shù)都不會(huì)開(kāi)啟查詢緩存,因?yàn)檫@些函數(shù)的返回是會(huì)不定的易變的。所以,你所需要的就是用一個(gè)變量來(lái)代替MySQL的函數(shù),從而開(kāi)啟緩存。
2. EXPLAIN 你的 SELECT 查詢
使用 EXPLAIN 關(guān)鍵字可以讓你知道MySQL是如何處理你的SQL語(yǔ)句的。這可以幫你分析你的查詢語(yǔ)句或是表結(jié)構(gòu)的性能瓶頸。
EXPLAIN 的查詢結(jié)果還會(huì)告訴你你的索引主鍵被如何利用的,你的數(shù)據(jù)表是如何被搜索和排序的……等等,等等。
挑一個(gè)你的SELECT語(yǔ)句(推薦挑選那個(gè)最復(fù)雜的,有多表聯(lián)接的),把關(guān)鍵字EXPLAIN加到前面。你可以使用 phpmyadmin 來(lái)做這個(gè)事。然后,你會(huì)看到一張表格。下面的這個(gè)示例中,我們忘記加上了group_id索引,并且有表聯(lián)接:
當(dāng)我們?yōu)?group_id 字段加上索引后:
我們可以看到,前一個(gè)結(jié)果顯示搜索了 7883 行,而后一個(gè)只是搜索了兩個(gè)表的 9 和 16 行。查看rows列可以讓我們找到潛在的性能問(wèn)題。
3. 當(dāng)只要一行數(shù)據(jù)時(shí)使用 LIMIT 1
當(dāng)你查詢表的有些時(shí)候,你已經(jīng)知道結(jié)果只會(huì)有一條結(jié)果,但因?yàn)槟憧赡苄枰etch游標(biāo),或是你也許會(huì)去檢查返回的記錄數(shù)。
在這種情況下,加上 LIMIT 1 可以增加性能。這樣一樣,MySQL數(shù)據(jù)庫(kù)引擎會(huì)在找到一條數(shù)據(jù)后停止搜索,而不是繼續(xù)往后查少下一條符合記錄的數(shù)據(jù)。
下面的示例,只是為了找一下是否有“中國(guó)”的用戶,很明顯,后面的會(huì)比前面的更有效率。(請(qǐng)注意,第一條中是Select *,第二條是Select 1)?
// 沒(méi)有效率的:?
$r = mysql_query("SELECT * FROM user WHERE country = 'China'");?
if (mysql_num_rows($r) > 0) {?
// ...?
}
// 有效率的:?
$r = mysql_query("SELECT 1 FROM user WHERE country = 'China' LIMIT 1");?
if (mysql_num_rows($r) > 0) {?
// ...?
}
4. 為搜索字段建索引
索引并不一定就是給主鍵或是唯一的字段。如果在你的表中,有某個(gè)字段你總要會(huì)經(jīng)常用來(lái)做搜索,那么,請(qǐng)為其建立索引吧。
從上圖你可以看到那個(gè)搜索字串 “l(fā)ast_name LIKE ‘a(chǎn)%'”,一個(gè)是建了索引,一個(gè)是沒(méi)有索引,性能差了4倍左右。
另外,你應(yīng)該也需要知道什么樣的搜索是不能使用正常的索引的。例如,當(dāng)你需要在一篇大的文章中搜索一個(gè)詞時(shí),如: “WHERE post_content LIKE ‘%apple%'”,索引可能是沒(méi)有意義的。你可能需要使用MySQL全文索引 或是自己做一個(gè)索引(比如說(shuō):搜索關(guān)鍵詞或是Tag什么的)
5. 在Join表的時(shí)候使用相當(dāng)類型的例,并將其索引
如果你的應(yīng)用程序有很多 JOIN 查詢,你應(yīng)該確認(rèn)兩個(gè)表中Join的字段是被建過(guò)索引的。這樣,MySQL內(nèi)部會(huì)啟動(dòng)為你優(yōu)化Join的SQL語(yǔ)句的機(jī)制。
而且,這些被用來(lái)Join的字段,應(yīng)該是相同的類型的。例如:如果你要把 DECIMAL 字段和一個(gè) INT 字段Join在一起,MySQL就無(wú)法使用它們的索引。對(duì)于那些STRING類型,還需要有相同的字符集才行。(兩個(gè)表的字符集有可能不一樣)?
// 在state中查找company?
$r = mysql_query("SELECT company_name FROM users?
LEFT JOIN companies ON (users.state = companies.state)?
WHERE users.id = $user_id");
// 兩個(gè) state 字段應(yīng)該是被建過(guò)索引的,而且應(yīng)該是相當(dāng)?shù)念愋停嗤淖址?
6. 千萬(wàn)不要 ORDER BY RAND()
想打亂返回的數(shù)據(jù)行?隨機(jī)挑一個(gè)數(shù)據(jù)?真不知道誰(shuí)發(fā)明了這種用法,但很多新手很喜歡這樣用。但你確不了解這樣做有多么可怕的性能問(wèn)題。
如果你真的想把返回的數(shù)據(jù)行打亂了,你有N種方法可以達(dá)到這個(gè)目的。這樣使用只讓你的數(shù)據(jù)庫(kù)的性能呈指數(shù)級(jí)的下降。這里的問(wèn)題是:MySQL會(huì)不得不去執(zhí)行RAND()函數(shù)(很耗CPU時(shí)間),而且這是為了每一行記錄去記行,然后再對(duì)其排序。就算是你用了Limit 1也無(wú)濟(jì)于事(因?yàn)橐判颍?
下面的示例是隨機(jī)挑一條記錄?
// 千萬(wàn)不要這樣做:?
$r = mysql_query("SELECT username FROM user ORDER BY RAND() LIMIT 1");
// 這要會(huì)更好:?
$r = mysql_query("SELECT count(*) FROM user");?
$d = mysql_fetch_row($r);?
$rand = mt_rand(0,$d[0] - 1);
$r = mysql_query("SELECT username FROM user LIMIT $rand, 1");
7. 避免 SELECT *
從數(shù)據(jù)庫(kù)里讀出越多的數(shù)據(jù),那么查詢就會(huì)變得越慢。并且,如果你的數(shù)據(jù)庫(kù)服務(wù)器和WEB服務(wù)器是兩臺(tái)獨(dú)立的服務(wù)器的話,這還會(huì)增加網(wǎng)絡(luò)傳輸?shù)呢?fù)載。
所以,你應(yīng)該養(yǎng)成一個(gè)需要什么就取什么的好的習(xí)慣。?
// 不推薦?
$r = mysql_query("SELECT * FROM user WHERE user_id = 1");?
$d = mysql_fetch_assoc($r);?
echo "Welcome {$d['username']}";
// 推薦?
$r = mysql_query("SELECT username FROM user WHERE user_id = 1");?
$d = mysql_fetch_assoc($r);?
echo "Welcome {$d['username']}";
8. 永遠(yuǎn)為每張表設(shè)置一個(gè)ID
我們應(yīng)該為數(shù)據(jù)庫(kù)里的每張表都設(shè)置一個(gè)ID做為其主鍵,而且最好的是一個(gè)INT型的(推薦使用UNSIGNED),并設(shè)置上自動(dòng)增加的 AUTO_INCREMENT標(biāo)志。
就算是你 users 表有一個(gè)主鍵叫 “email”的字段,你也別讓它成為主鍵。使用 VARCHAR 類型來(lái)當(dāng)主鍵會(huì)使用得性能下降。另外,在你的程序中,你應(yīng)該使用表的ID來(lái)構(gòu)造你的數(shù)據(jù)結(jié)構(gòu)。
而且,在MySQL數(shù)據(jù)引擎下,還有一些操作需要使用主鍵,在這些情況下,主鍵的性能和設(shè)置變得非常重要,比如,集群,分區(qū)……
在這里,只有一個(gè)情況是例外,那就是“關(guān)聯(lián)表”的“外鍵”,也就是說(shuō),這個(gè)表的主鍵,通過(guò)若干個(gè)別的表的主鍵構(gòu)成。我們把這個(gè)情況叫做“外鍵”。比如:有一個(gè)“學(xué)生表”有學(xué)生的ID,有一個(gè)“課程表”有課程ID,那么,“成績(jī)表”就是“關(guān)聯(lián)表”了,其關(guān)聯(lián)了學(xué)生表和課程表,在成績(jī)表中,學(xué)生ID和課程ID叫“外鍵”其共同組成主鍵。?
9. 使用 ENUM 而不是 VARCHAR
ENUM 類型是非常快和緊湊的。在實(shí)際上,其保存的是 TINYINT,但其外表上顯示為字符串。這樣一來(lái),用這個(gè)字段來(lái)做一些選項(xiàng)列表變得相當(dāng)?shù)耐昝馈?
如果你有一個(gè)字段,比如“性別”,“國(guó)家”,“民族”,“狀態(tài)”或“部門”,你知道這些字段的取值是有限而且固定的,那么,你應(yīng)該使用 ENUM 而不是 VARCHAR。
MySQL也有一個(gè)“建議”(見(jiàn)第十條)告訴你怎么去重新組織你的表結(jié)構(gòu)。當(dāng)你有一個(gè) VARCHAR 字段時(shí),這個(gè)建議會(huì)告訴你把其改成 ENUM 類型。使用 PROCEDURE ANALYSE() 你可以得到相關(guān)的建議。?
10. 從 PROCEDURE ANALYSE() 取得建議
PROCEDURE ANALYSE() 會(huì)讓 MySQL 幫你去分析你的字段和其實(shí)際的數(shù)據(jù),并會(huì)給你一些有用的建議。只有表中有實(shí)際的數(shù)據(jù),這些建議才會(huì)變得有用,因?yàn)橐鲆恍┐蟮臎Q定是需要有數(shù)據(jù)作為基礎(chǔ)的。
例如,如果你創(chuàng)建了一個(gè) INT 字段作為你的主鍵,然而并沒(méi)有太多的數(shù)據(jù),那么,PROCEDURE ANALYSE()會(huì)建議你把這個(gè)字段的類型改成 MEDIUMINT 。或是你使用了一個(gè) VARCHAR 字段,因?yàn)閿?shù)據(jù)不多,你可能會(huì)得到一個(gè)讓你把它改成 ENUM 的建議。這些建議,都是可能因?yàn)閿?shù)據(jù)不夠多,所以決策做得就不夠準(zhǔn)。
在phpmyadmin里,你可以在查看表時(shí),點(diǎn)擊 “Propose table structure” 來(lái)查看這些建議
一定要注意,這些只是建議,只有當(dāng)你的表里的數(shù)據(jù)越來(lái)越多時(shí),這些建議才會(huì)變得準(zhǔn)確。一定要記住,你才是最終做決定的人。?
11. 盡可能的使用 NOT NULL
除非你有一個(gè)很特別的原因去使用 NULL 值,你應(yīng)該總是讓你的字段保持 NOT NULL。這看起來(lái)好像有點(diǎn)爭(zhēng)議,請(qǐng)往下看。
首先,問(wèn)問(wèn)你自己“Empty”和“NULL”有多大的區(qū)別(如果是INT,那就是0和NULL)?如果你覺(jué)得它們之間沒(méi)有什么區(qū)別,那么你就不要使用NULL。(你知道嗎?在 Oracle 里,NULL 和 Empty 的字符串是一樣的!)
不要以為 NULL 不需要空間,其需要額外的空間,并且,在你進(jìn)行比較的時(shí)候,你的程序會(huì)更復(fù)雜。 當(dāng)然,這里并不是說(shuō)你就不能使用NULL了,現(xiàn)實(shí)情況是很復(fù)雜的,依然會(huì)有些情況下,你需要使用NULL值。
下面摘自MySQL自己的文檔:
“NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.”
12. Prepared Statements
Prepared Statements很像存儲(chǔ)過(guò)程,是一種運(yùn)行在后臺(tái)的SQL語(yǔ)句集合,我們可以從使用 prepared statements 獲得很多好處,無(wú)論是性能問(wèn)題還是安全問(wèn)題。
Prepared Statements 可以檢查一些你綁定好的變量,這樣可以保護(hù)你的程序不會(huì)受到“SQL注入式”攻擊。當(dāng)然,你也可以手動(dòng)地檢查你的這些變量,然而,手動(dòng)的檢查容易出問(wèn)題,而且很經(jīng)常會(huì)被程序員忘了。當(dāng)我們使用一些framework或是ORM的時(shí)候,這樣的問(wèn)題會(huì)好一些。
在性能方面,當(dāng)一個(gè)相同的查詢被使用多次的時(shí)候,這會(huì)為你帶來(lái)可觀的性能優(yōu)勢(shì)。你可以給這些Prepared Statements定義一些參數(shù),而MySQL只會(huì)解析一次。
雖然最新版本的MySQL在傳輸Prepared Statements是使用二進(jìn)制形勢(shì),所以這會(huì)使得網(wǎng)絡(luò)傳輸非常有效率。
當(dāng)然,也有一些情況下,我們需要避免使用Prepared Statements,因?yàn)槠洳恢С植樵兙彺妗5珦?jù)說(shuō)版本5.1后支持了。
在PHP中要使用prepared statements,你可以查看其使用手冊(cè):mysqli 擴(kuò)展 或是使用數(shù)據(jù)庫(kù)抽象層,如: PDO.?
// 創(chuàng)建 prepared statement?
if ($stmt = $mysqli->prepare("SELECT username FROM user WHERE state=?")) {
// 綁定參數(shù)?
$stmt->bind_param("s", $state);
// 執(zhí)行?
$stmt->execute();
// 綁定結(jié)果?
$stmt->bind_result($username);
// 移動(dòng)游標(biāo)?
$stmt->fetch();
printf("%s is from %sn", $username, $state);
$stmt->close();?
}
13. 無(wú)緩沖的查詢
正常的情況下,當(dāng)你在當(dāng)你在你的腳本中執(zhí)行一個(gè)SQL語(yǔ)句的時(shí)候,你的程序會(huì)停在那里直到?jīng)]這個(gè)SQL語(yǔ)句返回,然后你的程序再往下繼續(xù)執(zhí)行。你可以使用無(wú)緩沖查詢來(lái)改變這個(gè)行為。
關(guān)于這個(gè)事情,在PHP的文檔中有一個(gè)非常不錯(cuò)的說(shuō)明: mysql_unbuffered_query() 函數(shù):
“mysql_unbuffered_query() sends the SQL query query to MySQL without automatically fetching and buffering the result rows as mysql_query() does. This saves a considerable amount of memory with SQL queries that produce large result sets, and you can start working on the result set immediately after the first row has been retrieved as you don't have to wait until the complete SQL query has been performed.”
上面那句話翻譯過(guò)來(lái)是說(shuō),mysql_unbuffered_query() 發(fā)送一個(gè)SQL語(yǔ)句到MySQL而并不像mysql_query()一樣去自動(dòng)fethch和緩存結(jié)果。這會(huì)相當(dāng)節(jié)約很多可觀的內(nèi)存,尤其是那些會(huì)產(chǎn)生大量結(jié)果的查詢語(yǔ)句,并且,你不需要等到所有的結(jié)果都返回,只需要第一行數(shù)據(jù)返回的時(shí)候,你就可以開(kāi)始馬上開(kāi)始工作于查詢結(jié)果了。
然而,這會(huì)有一些限制。因?yàn)槟阋窗阉行卸甲x走,或是你要在進(jìn)行下一次的查詢前調(diào)用 mysql_free_result() 清除結(jié)果。而且, mysql_num_rows() 或 mysql_data_seek() 將無(wú)法使用。所以,是否使用無(wú)緩沖的查詢你需要仔細(xì)考慮。?
14. 把IP地址存成 UNSIGNED INT
很多程序員都會(huì)創(chuàng)建一個(gè) VARCHAR(15) 字段來(lái)存放字符串形式的IP而不是整形的IP。如果你用整形來(lái)存放,只需要4個(gè)字節(jié),并且你可以有定長(zhǎng)的字段。而且,這會(huì)為你帶來(lái)查詢上的優(yōu)勢(shì),尤其是當(dāng)你需要使用這樣的WHERE條件:IP between ip1 and ip2。
我們必需要使用UNSIGNED INT,因?yàn)?IP地址會(huì)使用整個(gè)32位的無(wú)符號(hào)整形。
而你的查詢,你可以使用 INET_ATON() 來(lái)把一個(gè)字符串IP轉(zhuǎn)成一個(gè)整形,并使用 INET_NTOA() 把一個(gè)整形轉(zhuǎn)成一個(gè)字符串IP。在PHP中,也有這樣的函數(shù) ip2long() 和 long2ip()。?
1 $r = "UPDATE users SET ip = INET_ATON('{$_SERVER['REMOTE_ADDR']}') WHERE user_id = $user_id";?
15. 固定長(zhǎng)度的表會(huì)更快
如果表中的所有字段都是“固定長(zhǎng)度”的,整個(gè)表會(huì)被認(rèn)為是 “static” 或 “fixed-length”。 例如,表中沒(méi)有如下類型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一個(gè)這些字段,那么這個(gè)表就不是“固定長(zhǎng)度靜態(tài)表”了,這樣,MySQL 引擎會(huì)用另一種方法來(lái)處理。
固定長(zhǎng)度的表會(huì)提高性能,因?yàn)镸ySQL搜尋得會(huì)更快一些,因?yàn)檫@些固定的長(zhǎng)度是很容易計(jì)算下一個(gè)數(shù)據(jù)的偏移量的,所以讀取的自然也會(huì)很快。而如果字段不是定長(zhǎng)的,那么,每一次要找下一條的話,需要程序找到主鍵。
并且,固定長(zhǎng)度的表也更容易被緩存和重建。不過(guò),唯一的副作用是,固定長(zhǎng)度的字段會(huì)浪費(fèi)一些空間,因?yàn)槎ㄩL(zhǎng)的字段無(wú)論你用不用,他都是要分配那么多的空間。
使用“垂直分割”技術(shù)(見(jiàn)下一條),你可以分割你的表成為兩個(gè)一個(gè)是定長(zhǎng)的,一個(gè)則是不定長(zhǎng)的。?
16. 垂直分割
“垂直分割”是一種把數(shù)據(jù)庫(kù)中的表按列變成幾張表的方法,這樣可以降低表的復(fù)雜度和字段的數(shù)目,從而達(dá)到優(yōu)化的目的。(以前,在銀行做過(guò)項(xiàng)目,見(jiàn)過(guò)一張表有100多個(gè)字段,很恐怖)
示例一:在Users表中有一個(gè)字段是家庭地址,這個(gè)字段是可選字段,相比起,而且你在數(shù)據(jù)庫(kù)操作的時(shí)候除了個(gè)人信息外,你并不需要經(jīng)常讀取或是改寫這個(gè)字段。那么,為什么不把他放到另外一張表中呢? 這樣會(huì)讓你的表有更好的性能,大家想想是不是,大量的時(shí)候,我對(duì)于用戶表來(lái)說(shuō),只有用戶ID,用戶名,口令,用戶角色等會(huì)被經(jīng)常使用。小一點(diǎn)的表總是會(huì)有好的性能。
示例二: 你有一個(gè)叫 “l(fā)ast_login” 的字段,它會(huì)在每次用戶登錄時(shí)被更新。但是,每次更新時(shí)會(huì)導(dǎo)致該表的查詢緩存被清空。所以,你可以把這個(gè)字段放到另一個(gè)表中,這樣就不會(huì)影響你對(duì)用戶 ID,用戶名,用戶角色的不停地讀取了,因?yàn)椴樵兙彺鏁?huì)幫你增加很多性能。
另外,你需要注意的是,這些被分出去的字段所形成的表,你不會(huì)經(jīng)常性地去Join他們,不然的話,這樣的性能會(huì)比不分割時(shí)還要差,而且,會(huì)是極數(shù)級(jí)的下降。?
17. 拆分大的 DELETE 或 INSERT 語(yǔ)句
如果你需要在一個(gè)在線的網(wǎng)站上去執(zhí)行一個(gè)大的 DELETE 或 INSERT 查詢,你需要非常小心,要避免你的操作讓你的整個(gè)網(wǎng)站停止相應(yīng)。因?yàn)檫@兩個(gè)操作是會(huì)鎖表的,表一鎖住了,別的操作都進(jìn)不來(lái)了。
Apache 會(huì)有很多的子進(jìn)程或線程。所以,其工作起來(lái)相當(dāng)有效率,而我們的服務(wù)器也不希望有太多的子進(jìn)程,線程和數(shù)據(jù)庫(kù)鏈接,這是極大的占服務(wù)器資源的事情,尤其是內(nèi)存。
如果你把你的表鎖上一段時(shí)間,比如30秒鐘,那么對(duì)于一個(gè)有很高訪問(wèn)量的站點(diǎn)來(lái)說(shuō),這30秒所積累的訪問(wèn)進(jìn)程/線程,數(shù)據(jù)庫(kù)鏈接,打開(kāi)的文件數(shù),可能不僅僅會(huì)讓你泊WEB服務(wù)Crash,還可能會(huì)讓你的整臺(tái)服務(wù)器馬上?熗恕?/p>
所以,如果你有一個(gè)大的處理,你定你一定把其拆分,使用 LIMIT 條件是一個(gè)好的方法。下面是一個(gè)示例:
while (1) {?
//每次只做1000條?
mysql_query("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");?
if (mysql_affected_rows() == 0) {?
// 沒(méi)得可刪了,退出!?
break;?
}?
// 每次都要休息一會(huì)兒?
usleep(50000);?
}
18. 越小的列會(huì)越快
對(duì)于大多數(shù)的數(shù)據(jù)庫(kù)引擎來(lái)說(shuō),硬盤操作可能是最重大的瓶頸。所以,把你的數(shù)據(jù)變得緊湊會(huì)對(duì)這種情況非常有幫助,因?yàn)檫@減少了對(duì)硬盤的訪問(wèn)。
參看 MySQL 的文檔 Storage Requirements 查看所有的數(shù)據(jù)類型。
如果一個(gè)表只會(huì)有幾列罷了(比如說(shuō)字典表,配置表),那么,我們就沒(méi)有理由使用 INT 來(lái)做主鍵,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 會(huì)更經(jīng)濟(jì)一些。如果你不需要記錄時(shí)間,使用 DATE 要比 DATETIME 好得多。
當(dāng)然,你也需要留夠足夠的擴(kuò)展空間,不然,你日后來(lái)干這個(gè)事,你會(huì)死的很難看,參看Slashdot的例子(2009年11月06 日),一個(gè)簡(jiǎn)單的ALTER TABLE語(yǔ)句花了3個(gè)多小時(shí),因?yàn)槔锩嬗幸磺Я偃f(wàn)條數(shù)據(jù)。?
19. 選擇正確的存儲(chǔ)引擎
在 MySQL 中有兩個(gè)存儲(chǔ)引擎 MyISAM 和 InnoDB,每個(gè)引擎都有利有弊。酷殼以前文章《MySQL: InnoDB 還是 MyISAM?》討論和這個(gè)事情。
MyISAM 適合于一些需要大量查詢的應(yīng)用,但其對(duì)于有大量寫操作并不是很好。甚至你只是需要update一個(gè)字段,整個(gè)表都會(huì)被鎖起來(lái),而別的進(jìn)程,就算是讀進(jìn)程都無(wú)法操作直到讀操作完成。另外,MyISAM 對(duì)于 SELECT COUNT(*) 這類的計(jì)算是超快無(wú)比的。
InnoDB 的趨勢(shì)會(huì)是一個(gè)非常復(fù)雜的存儲(chǔ)引擎,對(duì)于一些小的應(yīng)用,它會(huì)比 MyISAM 還慢。他是它支持“行鎖” ,于是在寫操作比較多的時(shí)候,會(huì)更優(yōu)秀。并且,他還支持更多的 高級(jí)應(yīng)用 ,比如:事務(wù)。
下面是MySQL的手冊(cè)
* target=”_blank”MyISAM Storage Engine?
* InnoDB Storage Engine
20. 使用一個(gè)對(duì)象關(guān)系映射器(Object Relational Mapper)
使用 ORM (Object Relational Mapper),你能夠獲得可靠的性能增漲。一個(gè)ORM可以做的所有事情,也能被手動(dòng)的編寫出來(lái)。但是,這需要一個(gè)高級(jí)專家。
ORM 的最重要的是“Lazy Loading”,也就是說(shuō),只有在需要的去取值的時(shí)候才會(huì)去真正的去做。但你也需要小心這種機(jī)制的副作用,因?yàn)檫@很有可能會(huì)因?yàn)橐?chuàng)建很多很多小的查詢反而會(huì)降低性能。
ORM 還可以把你的SQL語(yǔ)句打包成一個(gè)事務(wù),這會(huì)比單獨(dú)執(zhí)行他們快得多得多。
目前,個(gè)人最喜歡的PHP的ORM是:Doctrine。?
21. 小心“永久鏈接”
“永久鏈接”的目的是用來(lái)減少重新創(chuàng)建MySQL鏈接的次數(shù)。當(dāng)一個(gè)鏈接被創(chuàng)建了,它會(huì)永遠(yuǎn)處在連接的狀態(tài),就算是數(shù)據(jù)庫(kù)操作已經(jīng)結(jié)束了。而且,自從我們的Apache開(kāi)始重用它的子進(jìn)程后——也就是說(shuō),下一次的HTTP請(qǐng)求會(huì)重用Apache的子進(jìn)程,并重用相同的 MySQL 鏈接。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
