MySQL性能調優總結的19個知識點!

前言

Mysql性能優化就算經過合理安排資源,調整系統參數使MYSQL運行更快,更節省資源。MYSQL性能優化包括查詢速度優化,更新速度優化,mysql服務器優化等等。此處,介紹如下幾個優化。包含,性能優化的介紹,查詢優化,數據庫結構優化,mysql服務器優化。mysql

Mysql優化,一方面是找出系統的瓶頸,提升mysql數據庫總體的性能,另一個方面須要合理的結構設計和參數調整,以提升用戶操做響應的速度。同時還要儘量節省系統資源,以便系統能夠提供更大負荷的服務。mysql數據庫優化是多方面的,原則是減小系統的瓶頸,減小資源的佔用,增長系統反應的速度。小編這裏總結了一份詳細MySQL的思惟導圖以及MySQL筆記500多頁資料集錦 關注公衆號:麒麟改bug。程序員

一、爲查詢優化你的查詢

大多數的MySQL服務器都開啓了查詢緩存。這是提升性最有效的方法之一,並且這是被MySQL的數據庫引擎處理的。當有不少相同的查詢被執行了屢次的時候,這些查詢結果會被放到一個緩存中,這樣,後續的相同的查詢就不用操做表而直接訪問緩存結果了。面試

這裏最主要的問題是,對於程序員來講,這個事情是很容易被忽略的。由於,咱們某些查詢語句會讓MySQL不使用緩存。請看下面的示例:spring

// 查詢緩存不開啓$r = mysql_query("SELECT username FROM user WHERE   signup_date >= CURDATE()");// 開啓查詢緩存$today = date("Y-m-d");$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");
複製代碼

上面兩條SQL語句的差異就是 CURDATE() ,MySQL的查詢緩存對這個函數不起做用。因此,像 NOW() 和 RAND() 或是其它的諸如此類的SQL函數都不會開啓查詢緩存,由於這些函數的返回是會不定的易變的。因此,你所須要的就是用一個變量來代替MySQL的函數,從而開啓緩存。sql

二、EXPLAIN 你的SELECT查詢

使用EXPLAIN關鍵字可讓你知道MySQL是如何處理你的SQL語句的。數據庫

有表關聯的查詢,以下列:編程

select username, group_namefrom users ujoins groups g on (u.group_id = g.id)
複製代碼

發現查詢緩慢,而後在group_id字段上增長索引,則會加快查詢緩存

三、當只要一行數據時使用LIMIT 1

當你查詢表的有些時候,你已經知道結果只會有一條結果,單由於你可能須要去fetch遊標,或是你也許會去檢查返回的記錄數。
在這種狀況下,加上LIMIT 1 能夠增長性能。這樣同樣, MySQL數據庫引擎會在找到一條數據後中止搜索,而不是繼續日後查找下一條符合記錄的數據。
下面的示例,只是爲了找一下是否有「中國」的用戶,很明顯,後面的會比前面的更有效率。(請注意,第一條中是Select *,第二條是Select 1)性能優化

// 沒有效率的:$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) {// ...}
複製代碼

四、爲搜索字段建索引

索引並不必定就是給主鍵或是惟一的字段。若是在你的表中,有某個字段你總要會常常用來作搜索,那麼,請爲其創建索引吧。服務器

五、在Join表的時候使用至關類型的列,並將其索引

若是你的應用程序有不少JOIN查詢,你應該確認兩個表中Join的字段是被建過索引的。這樣,MySQL內部會啓動爲你優化Join的SQL語句的機制。
並且,這些被用來Join的字段,應該是相同的類型的。例如:若是你要把DECIMAL字段和一個INT字段JOIN在一塊兒,MYSQL就沒法使用他們的索引。對於那些STRING類型,還須要有相同的字符集才行(兩個表的字符集有可能不同)

六、千萬不要ORDER BY RAND()

七、避免SELECT *

從數據庫裏讀出越多的數據,那麼查詢就會變得越慢。而且,若是你的數據庫服務器和WEB服務器是兩臺獨立的服務器的話,這還會增長網絡傳輸的負載。

因此,你應該養成一個須要什麼就取什麼的好的習慣。

// 不推薦$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']}";
複製代碼

八、永遠爲兩張表設置一個ID

咱們應該爲數據庫裏的每張表都設置一個ID做爲其主鍵,而最好的是一個INT型(推薦使用UNSIGNED),並設置上自動增加的AUTO INCREMENT標誌。就算是你 users 表有一個主鍵叫 「email」的字段,你也別讓它成爲主鍵。使用 VARCHAR 類型來當主鍵會使用得性能降低。另外,在你的程序中,你應該使用表的ID來構造你的數據結構。
複製代碼

並且,在MySQL數據引擎下,還有一些操做須要使用主鍵,在這些狀況下,主鍵的性能和設置變得很是重要,好比,集羣,分區……

九、使用 ENUM 而不是 VARCHAR ?

ENUM 類型是很是快和緊湊的。在實際上,其保存的是 TINYINT,但其外表上顯示爲字符串。這樣一來,用這個字段來作一些選項列表變得至關的完美。

若是你有一個字段,好比「性別」,「國家」,「民族」,「狀態」或「部門」,你知道這些字段的取值是有限並且固定的,那麼,你應該使用 ENUM 而不是 VARCHAR。

十、從 PROCEDURE ANALYSE() 取得建議 ?

PROCEDURE ANALYSE() 會讓 MySQL 幫你去分析你的字段和其實際的數據,並會給你一些有用的建議。只有表中有實際的數據,這些建議纔會變得有用,由於要作一些大的決定是須要有數據做爲基礎的。

例如,若是你建立了一個 INT 字段做爲你的主鍵,然而並無太多的數據,那麼,PROCEDURE ANALYSE()會建議你把這個字段的類型改爲 MEDIUMINT 。或是你使用了一個 VARCHAR 字段,由於數據很少,你可能會獲得一個讓你把它改爲 ENUM 的建議。這些建議,都是可能由於數據不夠多,因此決策作得就不夠準。

十一、儘量的使用 NOT NULL

除非你有一個很特別的緣由去使用 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.」

十二、把IP地址存成 UNSIGNED INT

不少程序員都會建立一個 VARCHAR(15) 字段來存放字符串形式的IP而不是整形的IP。若是你用整形來存放,只須要4個字節,而且你能夠有定長的字段。並且,這會爲你帶來查詢上的優點,尤爲是當你須要使用這樣的WHERE條件:IP between ip1 and ip2。

咱們必須要使用UNSIGNED INT,由於 IP地址會使用整個32位的無符號整形

1三、固定長度的表會更快

若是表中的全部字段都是「固定長度」的,整個表會被認爲是 「static」 或 「fixed-length」。 例如,表中沒有以下類型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一個這些字段,那麼這個表就不是「固定長度靜態表」了,這樣,MySQL 引擎會用另外一種方法來處理。

固定長度的表會提升性能,由於MySQL搜尋得會更快一些,由於這些固定的長度是很容易計算下一個數據的偏移量的,因此讀取的天然也會很快。而若是字段不是定長的,那麼,每一次要找下一條的話,須要程序找到主鍵。

而且,固定長度的表也更容易被緩存和重建。不過,惟一的反作用是,固定長度的字段會浪費一些空間,由於定長的字段不管你用不用,他都是要分配那麼多的空間。

1四、垂直分割

「垂直分割」是一種把數據庫中的表按列變成幾張表的方法,這樣能夠下降表的複雜度和字段的數目,從而達到優化的目的。(之前,在銀行作過項目,見過一張表有100多個字段,很恐怖)

示例一:在Users表中有一個字段是家庭地址,這個字段是可選字段,相比起,並且你在數據庫操做的時候除了我的信息外,你並不須要常常讀取或是改寫這個字段。那麼,爲何不把他放到另一張表中呢? 這樣會讓你的表有更好的性能,你們想一想是否是,大量的時候,我對於用戶表來講,只有用戶ID,用戶名,口令,用戶角色等會被常用。小一點的表老是會有好的性能。

示例二: 你有一個叫 「last_login」 的字段,它會在每次用戶登陸時被更新。可是,每次更新時會致使該表的查詢緩存被清空。因此,你能夠把這個字段放到另外一個表中,這樣就不會影響你對用戶ID,用戶名,用戶角色的不停地讀取了,由於查詢緩存會幫你增長不少性能。

另外,你須要注意的是,這些被分出去的字段所造成的表,你不會常常性地去Join他們,否則的話,這樣的性能會比不分割時還要差,並且,會是極數級的降低。

1五、拆分大的 DELETE 或 INSERT 語句

若是你須要在一個在線的網站上去執行一個大的 DELETE 或 INSERT 查詢,你須要很是當心,要避免你的操做讓你的整個網站中止相應。由於這兩個操做是會鎖表的,表一鎖住了,別的操做都進不來了。

Apache 會有不少的子進程或線程。因此,其工做起來至關有效率,而咱們的服務器也不但願有太多的子進程,線程和數據庫連接,這是極大的佔服務器資源的事情,尤爲是內存。

若是你把你的表鎖上一段時間,好比30秒鐘,那麼對於一個有很高訪問量的站點來講,這30秒所積累的訪問進程/線程,數據庫連接,打開的文件數,可能不只僅會讓你泊WEB服務Crash,還可能會讓你的整臺服務器立刻掛了。

因此,若是你有一個大的處理,你定你必定把其拆分,使用 LIMIT 條件是一個好的方法。下面是一個示例:

while (1) {//每次只作1000條mysql_query("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");if (mysql_affected_rows() == 0) {    // 沒得可刪了,退出!    break;}// 每次都要休息一下子usleep(50000);}
複製代碼

1六、 越小的列會越快

對於大多數的數據庫引擎來講,硬盤操做多是最重大的瓶頸。因此,把你的數據變得緊湊會對這種狀況很是有幫助,由於這減小了對硬盤的訪問。

參看 MySQL 的文檔 Storage Requirements 查看全部的數據類型。

若是一個表只會有幾列罷了(好比說字典表,配置表),那麼,咱們就沒有理由使用 INT 來作主鍵,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 會更經濟一些。若是你不須要記錄時間,使用 DATE 要比 DATETIME 好得多。

固然,你也須要留夠足夠的擴展空間,否則,你往後來幹這個事,你會死的很難看,參看Slashdot的例子(2009年11月06日),一個簡單的ALTER TABLE語句花了3個多小時,由於裏面有一千六百萬條數據。

1七、選擇一個正確的存儲引擎

在 MySQL 中有兩個存儲引擎 MyISAM 和 InnoDB,每一個引擎都有利有弊。酷殼之前文章《MySQL: InnoDB 仍是 MyISAM?》討論和這個事情。

MyISAM 適合於一些須要大量查詢的應用,但其對於有大量寫操做並非很好。甚至你只是須要update一個字段,整個表都會被鎖起來,而別的進程,就算是讀進程都沒法操做直到讀操做完成。另外,MyISAM 對於 SELECT COUNT(*) 這類的計算是超快無比的。

InnoDB 的趨勢會是一個很是複雜的存儲引擎,對於一些小的應用,它會比 MyISAM 還慢。他是它支持「行鎖」 ,因而在寫操做比較多的時候,會更優秀。而且,他還支持更多的高級應用,好比:事務。

1八、當心「永久連接」

「永久連接」的目的是用來減小從新建立MySQL連接的次數。當一個連接被建立了,它會永遠處在鏈接的狀態,就算是數據庫操做已經結束了。並且,自從咱們的Apache開始重用它的子進程後——也就是說,下一次的HTTP請求會重用Apache的子進程,並重用相同的 MySQL 連接。

PHP手冊:mysql_pconnect()
在理論上來講,這聽起來很是的不錯。可是從我的經驗(也是大多數人的)上來講,這個功能製造出來的麻煩事更多。由於,你只有有限的連接數,內存問題,文件句柄數,等等。

並且,Apache 運行在極端並行的環境中,會建立不少不少的了進程。這就是爲何這種「永久連接」的機制工做地很差的緣由。在你決定要使用「永久連接」以前,你須要好好地考慮一下你的整個系統的架構。

參考

1九、當查詢較慢的時候,可用Join來改寫一下該查詢來進行優化

mysql> select sql_no_cache * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151) ; Empty set (18.87 sec)    mysql> select sql_no_cache a.* from guang_deal_outs a inner join guang_deals b on a.deal_id = b.id where b.id = 100017151;    Empty set (0.01 sec)緣由mysql> desc select sql_no_cache * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151) ;+----+--------------------+-----------------+-------+---------------+---------+---------+-------+----------+-------------+| id | select_type        | table           | type  | possible_keys | key     | key_len | ref   | rows     | Extra       |+----+--------------------+-----------------+-------+---------------+---------  +---------+-------+----------+-------------+|  1 | PRIMARY            | guang_deal_outs | ALL   | NULL          | NULL    |     NULL    | NULL  | 18633779 | Using where ||  2 | DEPENDENT SUBQUERY | guang_deals     | const | PRIMARY       | PRIMARY |     4       | const |        1 | Using index |+----+--------------------+-----------------+-------+---------------+---------  +---------+-------+----------+-------------+2 rows in set (0.04 sec)mysql> desc select sql_no_cache a.* from guang_deal_outs a inner join guang_deals b on a.deal_id = b.id where b.id = 100017151;+----+-------------+-------+-------+----------------------  +----------------------+---------+-------+------+-------------+| id | select_type | table | type  | possible_keys        | key                     | key_len | ref   | rows | Extra       |+----+-------------+-------+-------+----------------------  +----------------------+---------+-------+------+-------------+|  1 | SIMPLE      | b     | const | PRIMARY              | PRIMARY                 | 4       | const |    1 | Using index ||  1 | SIMPLE      | a     | ref   | idx_guang_dlout_dlid |     idx_guang_dlout_dlid | 4       | const |    1 |             |+----+-------------+-------+-------+----------------------    +----------------------+---------+-------+------+-------------+   2 rows in set (0.05 sec)
複製代碼

其實在 guang_deal_outs 在deal_id 上也是有索引的。
其實我想把子查詢設置爲

select * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151);
複製代碼

變成下面的樣子

select * from guang_deal_outs where deal_id in (100017151);
複製代碼

但不幸的是,實際狀況正好相反。MySQL試圖讓它和外面的表產生聯繫來「幫助」優化查詢,它認爲下面的exists形式更有效率

select * from guang_deal_outs where exists (select * from guang_deals where id = 100017151 and id = guang_deal_outs.deal_id);
複製代碼

這種in子查詢的形式,在外部表(好比上面的guang_deals)數據量比較大的時候效率是不好的(若是對於較小的表,不會形成顯著地影響)

文章到這裏就結束了!

小編這裏總結了【免費領取 MySQL筆記500多頁資料集錦+1000道互聯網大廠Java工程師面試題、spring、mybatis、jvm,Zookeeper,spring】 關注公衆號:麒麟改bug ,編程的世界永遠向全部熱愛編程的人開放,這是一個自由,平等,共享的世界,我始終是這樣堅信的。

相關文章
相關標籤/搜索