天天看點

資料庫

大多數的mysql伺服器都開啟了查詢緩存。這是提高性最有效的方法之一,而且這是被mysql的資料庫引擎處理的。當有很多相同的查詢被執行了多次的時候,這些查詢結果會被放到一個緩存中,這樣,後續的相同的查詢就不用操作表而直接通路緩存結果了。

這裡最主要的問題是,對于程式員來說,這個事情是很容易被忽略的。因為,我們某些查詢語句會讓mysql不使用緩存。請看下面的示例:

上面兩條sql語句的差别就是 curdate() ,mysql的查詢緩存對這個函數不起作用。是以,像 now() 和 rand() 或是其它的諸如此類的sql函數都不會開啟查詢緩存,因為這些函數的傳回是會不定的易變的。是以,你所需要的就是用一個變量來代替mysql的函數,進而開啟緩存。

使用 explain 關鍵字可以讓你知道mysql是如何處理你的sql語句的。這可以幫你分析你的查詢語句或是表結構的性能瓶頸。

explain 的查詢結果還會告訴你你的索引主鍵被如何利用的,你的資料表是如何被搜尋和排序的……等等,等等。

挑一個你的select語句(推薦挑選那個最複雜的,有多表聯接的),把關鍵字explain加到前面。你可以使用phpmyadmin來做這個事。然後,你會看到一張表格。下面的這個示例中,我們忘記加上了group_id索引,并且有表聯接:

資料庫

當我們為 group_id 字段加上索引後:

資料庫

我們可以看到,前一個結果顯示搜尋了 7883 行,而後一個隻是搜尋了兩個表的 9 和 16 行。檢視rows列可以讓我們找到潛在的性能問題。

當你查詢表的有些時候,你已經知道結果隻會有一條結果,但因為你可能需要去fetch遊标,或是你也許會去檢查傳回的記錄數。

在這種情況下,加上 limit 1 可以增加性能。這樣一樣,mysql資料庫引擎會在找到一條資料後停止搜尋,而不是繼續往後查少下一條符合記錄的資料。

下面的示例,隻是為了找一下是否有“中國”的使用者,很明顯,後面的會比前面的更有效率。(請注意,第一條中是select *,第二條是select 1)

如果你的應用程式有很多 join 查詢,你應該确認兩個表中join的字段是被建過索引的。這樣,mysql内部會啟動為你優化join的sql語句的機制。

而且,這些被用來join的字段,應該是相同的類型的。例如:如果你要把 decimal 字段和一個 int 字段join在一起,mysql就無法使用它們的索引。對于那些string類型,還需要有相同的字元集才行。(兩個表的字元集有可能不一樣)

想打亂傳回的資料行?随機挑一個資料?真不知道誰發明了這種用法,但很多新手很喜歡這樣用。但你确不了解這樣做有多麼可怕的性能問題。

如果你真的想把傳回的資料行打亂了,你有n種方法可以達到這個目的。這樣使用隻讓你的資料庫的性能呈指數級的下降。這裡的問題是:mysql會不得不去執行rand()函數(很耗cpu時間),而且這是為了每一行記錄去記行,然後再對其排序。就算是你用了limit 1也無濟于事(因為要排序)

下面的示例是随機挑一條記錄

從資料庫裡讀出越多的資料,那麼查詢就會變得越慢。并且,如果你的資料庫伺服器和web伺服器是兩台獨立的伺服器的話,這還會增加網絡傳輸的負載。

是以,你應該養成一個需要什麼就取什麼的好的習慣。

我們應該為資料庫裡的每張表都設定一個id做為其主鍵,而且最好的是一個int型的(推薦使用unsigned),并設定上自動增加的auto_increment标志。

就算是你 users 表有一個主鍵叫 “email”的字段,你也别讓它成為主鍵。使用 varchar 類型來當主鍵會使用得性能下降。另外,在你的程式中,你應該使用表的id來構造你的資料結構。

而且,在mysql資料引擎下,還有一些操作需要使用主鍵,在這些情況下,主鍵的性能和設定變得非常重要,比如,叢集,分區……

在這裡,隻有一個情況是例外,那就是“關聯表”的“外鍵”,也就是說,這個表的主鍵,通過若幹個别的表的主鍵構成。我們把這個情況叫做“外鍵”。比如:有一個“學生表”有學生的id,有一個“課程表”有課程id,那麼,“成績表”就是“關聯表”了,其關聯了學生表和課程表,在成績表中,學生id和課程id叫“外鍵”其共同組成主鍵。

enum 類型是非常快和緊湊的。在實際上,其儲存的是 tinyint,但其外表上顯示為字元串。這樣一來,用這個字段來做一些選項清單變得相當的完美。

如果你有一個字段,比如“性别”,“國家”,“民族”,“狀态”或“部門”,你知道這些字段的取值是有限而且固定的,那麼,你應該使用 enum 而不是 varchar。

mysql也有一個“建議”(見第十條)告訴你怎麼去重新組織你的表結構。當你有一個 varchar 字段時,這個建議會告訴你把其改成 enum 類型。使用 procedure analyse() 你可以得到相關的建議。

procedure analyse() 會讓 mysql 幫你去分析你的字段和其實際的資料,并會給你一些有用的建議。隻有表中有實際的資料,這些建議才會變得有用,因為要做一些大的決定是需要有資料作為基礎的。

例如,如果你建立了一個 int 字段作為你的主鍵,然而并沒有太多的資料,那麼,procedure analyse()會建議你把這個字段的類型改成 mediumint 。或是你使用了一個 varchar 字段,因為資料不多,你可能會得到一個讓你把它改成 enum 的建議。這些建議,都是可能因為資料不夠多,是以決策做得就不夠準。

在phpmyadmin裡,你可以在檢視表時,點選 “propose table structure” 來檢視這些建議

資料庫

一定要注意,這些隻是建議,隻有當你的表裡的資料越來越多時,這些建議才會變得準确。一定要記住,你才是最終做決定的人。

除非你有一個很特别的原因去使用 null 值,你應該總是讓你的字段保持 not null。這看起來好像有點争議,請往下看。

首先,問問你自己“empty”和“null”有多大的差別(如果是int,那就是0和null)?如果你覺得它們之間沒有什麼差別,那麼你就不要使用null。(你知道嗎?在 oracle 裡,null 和 empty 的字元串是一樣的!)

不要以為 null 不需要空間,其需要額外的空間,并且,在你進行比較的時候,你的程式會更複雜。 當然,這裡并不是說你就不能使用null了,現實情況是很複雜的,依然會有些情況下,你需要使用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.”

prepared statements很像存儲過程,是一種運作在背景的sql語句集合,我們可以從使用 prepared statements 獲得很多好處,無論是性能問題還是安全問題。

prepared statements 可以檢查一些你綁定好的變量,這樣可以保護你的程式不會受到“sql注入式”攻擊。當然,你也可以手動地檢查你的這些變量,然而,手動的檢查容易出問題,而且很經常會被程式員忘了。當我們使用一些framework或是orm的時候,這樣的問題會好一些。

在性能方面,當一個相同的查詢被使用多次的時候,這會為你帶來可觀的性能優勢。你可以給這些prepared statements定義一些參數,而mysql隻會解析一次。

雖然最新版本的mysql在傳輸prepared statements是使用二進制形勢,是以這會使得網絡傳輸非常有效率。

當然,也有一些情況下,我們需要避免使用prepared statements,因為其不支援查詢緩存。但據說版本5.1後支援了。

在php中要使用prepared statements,你可以檢視其使用手冊:mysqli 擴充 或是使用資料庫抽象層,如: pdo.

正常的情況下,當你在當你在你的腳本中執行一個sql語句的時候,你的程式會停在那裡直到沒這個sql語句傳回,然後你的程式再往下繼續執行。你可以使用無緩沖查詢來改變這個行為。

關于這個事情,在php的文檔中有一個非常不錯的說明: mysql_unbuffered_query() 函數:

“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.”

上面那句話翻譯過來是說,mysql_unbuffered_query() 發送一個sql語句到mysql而并不像mysql_query()一樣去自動fethch和緩存結果。這會相當節約很多可觀的記憶體,尤其是那些會産生大量結果的查詢語句,并且,你不需要等到所有的結果都傳回,隻需要第一行資料傳回的時候,你就可以開始馬上開始工作于查詢結果了。

然而,這會有一些限制。因為你要麼把所有行都讀走,或是你要在進行下一次的查詢前調用mysql_free_result() 清除結果。而且, mysql_num_rows() 或 mysql_data_seek() 将無法使用。是以,是否使用無緩沖的查詢你需要仔細考慮。

很多程式員都會建立一個 varchar(15) 字段來存放字元串形式的ip而不是×××的ip。如果你用×××來存放,隻需要4個位元組,并且你可以有定長的字段。而且,這會為你帶來查詢上的優勢,尤其是當你需要使用這樣的where條件:ip between ip1 and ip2。

我們必需要使用unsigned int,因為 ip位址會使用整個32位的無符号×××。

而你的查詢,你可以使用 inet_aton() 來把一個字元串ip轉成一個×××,并使用 inet_ntoa() 把一個×××轉成一個字元串ip。在php中,也有這樣的函數 ip2long() 和 long2ip()。

如果表中的所有字段都是“固定長度”的,整個表會被認為是 “static” 或 “fixed-length”。 例如,表中沒有如下類型的字段: varchar,text,blob。隻要你包括了其中一個這些字段,那麼這個表就不是“固定長度靜态表”了,這樣,mysql 引擎會用另一種方法來處理。

固定長度的表會提高性能,因為mysql搜尋得會更快一些,因為這些固定的長度是很容易計算下一個資料的偏移量的,是以讀取的自然也會很快。而如果字段不是定長的,那麼,每一次要找下一條的話,需要程式找到主鍵。

并且,固定長度的表也更容易被緩存和重建。不過,唯一的副作用是,固定長度的字段會浪費一些空間,因為定長的字段無論你用不用,他都是要配置設定那麼多的空間。

使用“垂直分割”技術(見下一條),你可以分割你的表成為兩個一個是定長的,一個則是不定長的。

“垂直分割”是一種把資料庫中的表按列變成幾張表的方法,這樣可以降低表的複雜度和字段的數目,進而達到優化的目的。(以前,在銀行做過項目,見過一張表有100多個字段,很恐怖)

示例一:在users表中有一個字段是家庭位址,這個字段是可選字段,相比起,而且你在資料庫操作的時候除了個人資訊外,你并不需要經常讀取或是改寫這個字段。那麼,為什麼不把他放到另外一張表中呢? 這樣會讓你的表有更好的性能,大家想想是不是,大量的時候,我對于使用者表來說,隻有使用者id,使用者名,密碼,使用者角色等會被經常使用。小一點的表總是會有好的性能。

示例二: 你有一個叫 “last_login” 的字段,它會在每次使用者登入時被更新。但是,每次更新時會導緻該表的查詢緩存被清空。是以,你可以把這個字段放到另一個表中,這樣就不會影響你對使用者id,使用者名,使用者角色的不停地讀取了,因為查詢緩存會幫你增加很多性能。

另外,你需要注意的是,這些被分出去的字段所形成的表,你不會經常性地去join他們,不然的話,這樣的性能會比不分割時還要差,而且,會是極數級的下降。

如果你需要在一個線上的網站上去執行一個大的 delete 或 insert 查詢,你需要非常小心,要避免你的操作讓你的整個網站停止相應。因為這兩個操作是會鎖表的,表一鎖住了,别的操作都進不來了。

apache 會有很多的子程序或線程。是以,其工作起來相當有效率,而我們的伺服器也不希望有太多的子程序,線程和資料庫連結,這是極大的占伺服器資源的事情,尤其是記憶體。

如果你把你的表鎖上一段時間,比如30秒鐘,那麼對于一個有很高通路量的站點來說,這30秒所積累的通路程序/線程,資料庫連結,打開的檔案數,可能不僅僅會讓你泊web服務crash,還可能會讓你的整台伺服器馬上掛了。

是以,如果你有一個大的處理,你定你一定把其拆分,使用 limit 條件是一個好的方法。下面是一個示例:

對于大多數的資料庫引擎來說,硬碟操作可能是最重大的瓶頸。是以,把你的資料變得緊湊會對這種情況非常有幫助,因為這減少了對硬碟的通路。

參看 mysql 的文檔 storage requirements 檢視所有的資料類型。

如果一個表隻會有幾列罷了(比如說字典表,配置表),那麼,我們就沒有理由使用 int 來做主鍵,使用 mediumint, smallint 或是更小的 tinyint 會更經濟一些。如果你不需要記錄時間,使用 date 要比 datetime 好得多。

當然,你也需要留夠足夠的擴充空間,不然,你日後來幹這個事,你會死的很難看,參看slashdot的例子(2009年11月06日),一個簡單的alter table語句花了3個多小時,因為裡面有一千六百萬條資料。

在 mysql 中有兩個存儲引擎 myisam 和 innodb,每個引擎都有利有弊。酷殼以前文章《mysql: innodb 還是 myisam?》讨論和這個事情。

myisam 适合于一些需要大量查詢的應用,但其對于有大量寫操作并不是很好。甚至你隻是需要update一個字段,整個表都會被鎖起來,而别的程序,就算是讀程序都無法操作直到讀操作完成。另外,myisam 對于 select count(*) 這類的計算是超快無比的。

innodb 的趨勢會是一個非常複雜的存儲引擎,對于一些小的應用,它會比 myisam 還慢。他是它支援“行鎖” ,于是在寫操作比較多的時候,會更優秀。并且,他還支援更多的進階應用,比如:事務。

下面是mysql的手冊

target=”_blank”myisam storage engine

innodb storage engine

使用 orm (object relational mapper),你能夠獲得可靠的性能增漲。一個orm可以做的所有事情,也能被手動的編寫出來。但是,這需要一個進階專家。

orm 的最重要的是“lazy loading”,也就是說,隻有在需要的去取值的時候才會去真正的去做。但你也需要小心這種機制的副作用,因為這很有可能會因為要去建立很多很多小的查詢反而會降低性能。

orm 還可以把你的sql語句打包成一個事務,這會比單獨執行他們快得多得多。

目前,個人最喜歡的php的orm是:doctrine。

“永久連結”的目的是用來減少重新建立mysql連結的次數。當一個連結被建立了,它會永遠處在連接配接的狀态,就算是資料庫操作已經結束了。而且,自從我們的apache開始重用它的子程序後——也就是說,下一次的http請求會重用apache的子程序,并重用相同的 mysql 連結。

22、分庫分表

很明顯,一個主表(也就是很重要的表,例如使用者表)無限制的增長勢必嚴重影響性能,分庫與分表是一個很不錯的解決途徑,也就是性能優化途徑,現在的案例是我們有一個1000多萬條記錄的使用者表members,查詢起來非常之慢,同僚的做法是将其散列到100個表中,分别從members0到members99,然後根據mid分發記錄到這些表中,牛逼的代碼大概是這樣子:

23、不停機修改mysql表結構

同樣還是members表,前期設計的表結構不盡合理,随着資料庫不斷運作,其備援資料也是增長巨大,同僚使用了下面的方法來處理:

先建立一個臨時表:

然後修改members_tmp的表結構為新結構,接着使用上面那個for循環來導出資料,因為1000萬的資料一次性導出是不對的,mid是主鍵,一個區間一個區間的導,基本是一次導出5萬條吧,這裡略去了

接着重命名将新表替換上去:

就是這樣,基本可以做到無損失,無需停機更新表結構,但實際上rename期間表是被鎖死的,是以選擇線上少的時候操作是一個技巧。經過這個操作,使得原先8g多的表,一下子變成了2g多

另外還講到了mysql中float字段類型的時候出現的詭異現象,就是在pma中看到的數字根本不能作為條件來查詢.感謝zj同學的新鮮分享。

24、mysql性能優化的參數

公司網站通路量越來越大,mysql自然成為瓶頸,是以最近我一直在研究 mysql 的優化,第一步自然想到的是 mysql 系統參數的優化,作為一個通路量很大的網站(日20萬人次以上)的資料庫系統,不可能指望 mysql 預設的系統參數能夠讓 mysql運作得非常順暢。 

通過在網絡上查找資料和自己的嘗試,我認為以下系統參數是比較關鍵的: 

(1)、back_log:

要求 mysql 能有的連接配接數量。當主要mysql線程在一個很短時間内得到非常多的連接配接請求,這就起作用,然後主線程花些時間(盡管很短)檢查連接配接并且啟動一個新線程。

back_log值指出在mysql暫時停止回答新請求之前的短時間内多少個請求可以被存在堆棧中。隻有如果期望在一個短時間内有很多連接配接,你需要增加它,換句話說,這值對到來的tcp/ip連接配接的偵聽隊列的大小。你的作業系統在這個隊列大小上有它自己的限制。 試圖設定back_log高于你的作業系統的限制将是無效的。 

當你觀察你的主機程序清單,發現大量 264084 | unauthenticated user | xxx.xxx.xxx.xxx | null | connect | null | login | null 的待連接配接程序時,就要加大 back_log 的值了。預設數值是50,我把它改為500。 

(2)、interactive_timeout:

伺服器在關閉它前在一個互動連接配接上等待行動的秒數。一個互動的客戶被定義為對 mysql_real_connect()使用 client_interactive 選項的客戶。 預設數值是28800,我把它改為7200。 

(3)、key_buffer_size:

索引塊是緩沖的并且被所有的線程共享。key_buffer_size是用于索引塊的緩沖區大小,增加它可得到更好處理的索引(對所有讀和多重寫),到你能負擔得起那樣多。如果你使它太大,系統将開始換頁并且真的變慢了。預設數值是8388600(8m),我的mysql主機有2gb記憶體,是以我把它改為402649088(400mb)。 

(4)、max_connections:

允許的同時客戶的數量。增加該值增加 mysqld 要求的檔案描述符的數量。這個數字應該增加,否則,你将經常看到 too many connections 錯誤。 預設數值是100,我把它改為1024 。 

(5)、record_buffer:

每個進行一個順序掃描的線程為其掃描的每張表配置設定這個大小的一個緩沖區。如果你做很多順序掃描,你可能想要增加該值。預設數值是131072(128k),我把它改為16773120 (16m) 

(6)、sort_buffer:

每個需要進行排序的線程配置設定該大小的一個緩沖區。增加這值加速order by或group by操作。預設數值是2097144(2m),我把它改為 16777208 (16m)。 

(7)、table_cache:

為所有線程打開表的數量。增加該值能增加mysql要求的檔案描述符的數量。mysql對每個唯一打開的表需要2個檔案描述符。預設數值是64,我把它改為512。 

(8)、thread_cache_size: 

可以複用的儲存在中的線程的數量。如果有,新的線程從緩存中取得,當斷開連接配接的時候如果有空間,客戶的線置在緩存中。如果有很多新的線程,為了提高性能可以這個變量值。通過比較 connections 和 threads_created 狀态的變量,可以看到這個變量的作用。我把它設定為 80。 

(10)、wait_timeout:

伺服器在關閉它之前在一個連接配接上等待行動的秒數。 預設數值是28800,我把它改為7200。 

注:參數的調整可以通過修改 /etc/my.cnf 檔案并重新開機 mysql 實作。這是一個比較謹慎的工作,上面的結果也僅僅是我的一些看法,你可以根據你自己主機的硬體情況(特别是記憶體大小)進一步修改。

資料庫

asp?id=482" width=1 border=0>

聲明:iteye文章版權屬于作者,受法律保護。沒有作者書面許可不得轉載。