糟糕的SQL查詢語句可對整個應用程序的運行產生嚴重的影響,其不只消耗掉更多的數據庫時間,且它將對其餘應用組件產生影響。mysql
如同其它學科,優化查詢性能很大程度上決定於開發者的直覺。幸運的是,像MySQL這樣的數據庫自帶有一些協助工具。本文簡要討論諸多工具之三種:使用索引,使用EXPLAIN分析查詢以及調整MySQL的內部配置。sql
1、使用索引
MySQL容許對數據庫表進行索引,以此能迅速查找記錄,而無需一開始就掃描整個表,由此顯著地加快查詢速度。每一個表最多能夠作到16個索引,此外MySQL還支持多列索引及全文檢索。shell
給表添加一個索引很是簡單,只需調用一個CREATE INDEX命令併爲索引指定它的域便可。列表A給出了一個例子:數據庫
|
列表 A服務器
這裏,對users表的username域作索引,以確保在WHERE或者HAVING子句中引用這一域的SELECT查詢語句運行速度比沒有添加索引時要快。經過SHOW INDEX命令能夠查看索引已被建立(列表B)。網絡
列表 B併發
值得注意的是:索引就像一把雙刃劍。對錶的每一域作索引一般沒有必要,且極可能致使運行速度減慢,由於向表中插入或修改數據時,MySQL不得不每次都爲這些額外的工做從新創建索引。另外一方面,避免對錶的每一域作索引一樣不是一個很是好的主意,由於在提升插入記錄的速度時,致使查詢操做的速度減慢。這就須要找到一個平衡點,好比在設計索引系統時,考慮表的主要功能(數據修復及編輯)不失爲一種明智的選擇。函數
2、優化查詢性能工具
在分析查詢性能時,考慮EXPLAIN關鍵字一樣很管用。EXPLAIN關鍵字通常放在SELECT查詢語句的前面,用於描述MySQL如何執行查詢操做、以及MySQL成功返回結果集須要執行的行數。下面的一個簡單例子能夠說明(列表C)這一過程:
列表 C
這裏查詢是基於兩個錶鏈接。EXPLAIN關鍵字描述了MySQL是如何處理鏈接這兩個表。必須清楚的是,當前設計要求MySQL處理的是 country表中的一條記錄以及city表中的整個4019條記錄。這就意味着,還可以使用其餘的優化技巧改進其查詢方法。例如,給city表添加以下索引(列表D):
|
列表 D
如今,當咱們從新使用EXPLAIN關鍵字進行查詢時,咱們能夠看到一個顯著的改進(列表E):
列表 E
在這個例子中,MySQL如今只須要掃描city表中的333條記錄就可產生一個結果集,其掃描記錄數幾乎減小了90%!天然,數據庫資源的查詢速度更快,效率更高。
3、調整內部變量
MySQL是如此的開放,因此可輕鬆地進一步調整其缺省設置以得到更優的性能及穩定性。須要優化的一些關鍵變量以下:
改變索引緩衝區長度(key_buffer)
通常,該變量控制緩衝區的長度在處理索引表(讀/寫操做)時使用。MySQL使用手冊指出該變量能夠不斷增長以確保索引表的最佳性能,並推薦使用與系統內存25%的大小做爲該變量的值。這是MySQL十分重要的配置變量之一,若是你對優化和提升系統性能有興趣,能夠從改變 key_buffer_size變量的值開始。
改變表長(read_buffer_size)
當一個查詢不斷地掃描某一個表,MySQL會爲它分配一段內存緩衝區。read_buffer_size變量控制這一緩衝區的大小。若是你認爲連續掃描進行得太慢,能夠經過增長該變量值以及內存緩衝區大小提升其性能。
設定打開表的數目的最大值(table_cache)
該變量控制MySQL在任什麼時候候打開表的最大數目,由此能控制服務器響應輸入請求的能力。它跟max_connections變量密切相關,增長 table_cache值可以使MySQL打開更多的表,就如增長max_connections值可增長鏈接數同樣。當收到大量不一樣數據庫及表的請求時,能夠考慮改變這一值的大小。
對緩長查詢設定一個時間限制(long_query_time)
MySQL帶有「慢查詢日誌」,它會自動地記錄全部的在一個特定的時間範圍內還沒有結束的查詢。這個日誌對於跟蹤那些低效率或者行爲不端的查詢以及尋找優化對象都很是有用。long_query_time變量控制這一最大時間限定,以秒爲單位。
以上討論並給出用於分析和優化SQL查詢的三種工具的使用方法,以此提升你的應用程序性能。使用它們快樂地優化吧!
使用EXPLAIN語句檢查SQL語句
當你在一條SELECT語句前放上關鍵詞EXPLAIN,MySQL解釋它將如何處理SELECT,提供有關表如何聯結和以什麼次序聯結的信息。
藉助於EXPLAIN,你能夠知道你何時必須爲表加入索引以獲得一個使用索引找到記錄的更快的SELECT。
EXPLAIN tbl_name
or EXPLAIN SELECT select_options
EXPLAIN tbl_name是DESCRIBE tbl_name或SHOW COLUMNS FROM tbl_name的一個同義詞。
從EXPLAIN的輸出包括下面列:
·table
輸出的行所引用的表。
· type
聯結類型。各類類型的信息在下面給出。
不一樣的聯結類型列在下面,以最好到最差類型的次序:
system const eq_ref ref range index ALL possible_keys
· key
key列顯示MySQL實際決定使用的鍵。若是沒有索引被選擇,鍵是NULL。
· key_len
key_len列顯示MySQL決定使用的鍵長度。若是鍵是NULL,長度是NULL。注意這告訴咱們MySQL將實際使用一個多部鍵值的幾個部分。
· ref
ref列顯示哪一個列或常數與key一塊兒用於從表中選擇行。
· rows
rows列顯示MySQL相信它必須檢驗以執行查詢的行數。
·Extra
若是Extra列包括文字Only index,這意味着信息只用索引樹中的信息檢索出的。一般,這比掃描整個表要快。若是Extra列包括文字where used,它意味着一個WHERE子句將被用來限制哪些行與下一個表匹配或發向客戶。
經過相乘EXPLAIN輸出的rows行的全部值,你能獲得一個關於一個聯結要多好的提示。這應該粗略地告訴你MySQL必須檢驗多少行以執行查詢。
例如,下面一個全鏈接:
|
其結論爲:
+---------+------+---------------+------+---------+------+------+------------+
| table | type | possible_keys | key | key_len | ref | rows | Extra |
+---------+------+---------------+------+---------+------+------+------------+
| student | ALL | NULL | NULL | NULL | NULL | 13 | |
| pet | ALL | NULL | NULL | NULL | NULL | 9 | where used |
+---------+------+---------------+------+---------+------+------+------------+
SELECT 查詢的速度
總的來講,當你想要使一個較慢的SELECT ... WHERE更快,檢查的第一件事情是你是否能增長一個索引。在不一樣表之間的全部引用一般應該用索引完成。你可使用EXPLAIN來肯定哪一個索引用於一條SELECT語句。
一些通常的建議:
·爲了幫助MySQL更好地優化查詢,在它已經裝載了相關數據後,在一個表上運行myisamchk --analyze。這爲每個更新一個值,指出有相同值地平均行數(固然,對惟一索引,這老是1。)
·爲了根據一個索引排序一個索引和數據,使用myisamchk --sort-index --sort-records=1(若是你想要在索引1上排序)。若是你有一個惟一索引,你想要根據該索引地次序讀取全部的記錄,這是使它更快的一個好方法。然而注意,這個排序沒有被最佳地編寫,而且對一個大表將花很長時間!
MySQL怎樣優化WHERE子句
where優化被放在SELECT中,由於他們最主要在那裏使用裏,可是一樣的優化被用於DELETE和UPDATE語句。
也要注意,本節是不徹底的。MySQL確實做了許多優化而咱們沒有時間所有記錄他們。
由MySQL實施的一些優化列在下面:
一、刪除沒必要要的括號:
((a AND b) AND c OR (((a AND b) AND (c AND d))))
-> (a AND b AND c) OR (a AND b AND c AND d)
二、常數調入:
(a-> b>5 AND b=c AND a=5
三、刪除常數條件(因常數調入所需):
(B>=5 AND B=5) OR (B=6 AND 5=5) OR (B=7 AND 5=6)
-> B=5 OR B=6
四、索引使用的常數表達式僅計算一次。
五、在一個單個表上的沒有一個WHERE的COUNT(*)直接從表中檢索信息。當僅使用一個表時,對任何NOT NULL表達式也這樣作。
六、無效常數表達式的早期檢測。MySQL快速檢測某些SELECT語句是不可能的而且不返回行。
七、若是你不使用GROUP BY或分組函數(COUNT()、MIN()……),HAVING與WHERE合併。
八、爲每一個子聯結(sub join),構造一個更簡單的WHERE以獲得一個更快的WHERE計算而且也儘快跳過記錄。
九、全部常數的表在查詢中的任何其餘表前被首先讀出。一個常數的表是:
·一個空表或一個有1行的表。
·與在一個UNIQUE索引、或一個PRIMARY KEY的WHERE子句一塊兒使用的表,這裏全部的索引部分使用一個常數表達式而且索引部分被定義爲NOT NULL。
全部下列的表用做常數表
mysql> SELECT * FROM t WHERE primary_key=1; |
十、對聯結表的最好聯結組合是經過嘗試全部可能性來找到:(。若是全部在ORDER BY和GROUP BY的列來自同一個表,那麼當廉潔時,該表首先被選中。
十一、若是有一個ORDER BY子句和一個不一樣的GROUP BY子句,或若是ORDER BY或GROUP BY包含不是來自聯結隊列中的第一個表的其餘表的列,建立一個臨時表。
十二、若是你使用SQL_SMALL_RESULT,MySQL將使用一個在內存中的表。
1三、由於DISTINCT被變換到在全部的列上的一個GROUP BY,DISTINCT與ORDER BY結合也將在許多狀況下須要一張臨時表。
1四、每一個表的索引被查詢而且使用跨越少於30% 的行的索引。若是這樣的索引沒能找到,使用一個快速的表掃描。
1五、在一些狀況下,MySQL能從索引中讀出行,甚至不諮詢數據文件。若是索引使用的全部列是數字的,那麼只有索引樹被用來解答查詢。
1六、在每一個記錄被輸出前,那些不匹配HAVING子句的行被跳過。
下面是一些很快的查詢例子
|
下列查詢僅使用索引樹就可解決(假設索引列是數字的):
|
下列查詢使用索引以排序順序檢索,不用一次另外的排序:
mysql> SELECT ... FROM tbl_name ORDER BY key_part1,key_part2,... |
MySQL怎樣優化LEFT JOIN
在MySQL中,A LEFT JOIN B實現以下:
一、表B被設置爲依賴於表A。
二、表A被設置爲依賴於全部用在LEFT JOIN條件的表(除B外)。
三、全部LEFT JOIN條件被移到WHERE子句中。
四、進行全部標準的聯結優化,除了一個表老是在全部它依賴的表以後被讀取。若是有一個循環依賴,MySQL將發出一個錯誤。
五、進行全部標準的WHERE優化。
六、若是在A中有一行匹配WHERE子句,可是在B中沒有任何行匹配LEFT JOIN條件,那麼在B中生成全部列設置爲NULL的一行。
七、若是你使用LEFT JOIN來找出在某些表中不存在的行而且在WHERE部分你有下列測試:column_name IS NULL,這裏column_name 被聲明爲NOT NULL的列,那麼MySQL在它已經找到了匹配LEFT JOIN條件的一行後,將中止在更多的行後尋找(對一特定的鍵組合)。
MySQL怎樣優化LIMIT
在一些狀況中,當你使用LIMIT #而不使用HAVING時,MySQL將以不一樣方式處理查詢。
一、若是你用LIMIT只選擇一些行,當MySQL通常比較喜歡作完整的表掃描時,它將在一些狀況下使用索引。
二、若是你使用LIMIT #與ORDER BY,MySQL一旦找到了第一個 # 行,將結束排序而不是排序整個表。
三、當結合LIMIT #和DISTINCT時,MySQL一旦找到#個惟一的行,它將中止。
四、在一些狀況下,一個GROUP BY能經過順序讀取鍵(或在鍵上作排序)來解決,並而後計算摘要直到鍵值改變。在這種狀況下,LIMIT #將不計算任何沒必要要的GROUP。
五、只要MySQL已經發送了第一個#行到客戶,它將放棄查詢。
六、LIMIT 0將老是快速返回一個空集合。這對檢查查詢而且獲得結果列的列類型是有用的。
七、臨時表的大小使用LIMIT #計算須要多少空間來解決查詢。
記錄轉載和修改的速度
不少時候關心的是優化 SELECT 查詢,由於它們是最經常使用的查詢,並且肯定怎樣優化它們並不老是直截了當。相對來講,將數據裝入數據庫是直截了當的。然而,也存在可用來改善數據裝載操做效率的策略,其基本原理以下:
·成批裝載較單行裝載更快,由於在裝載每一個記錄後,不須要刷新索引高速緩存;可在成批記錄裝入後才刷新。
·在表無索引時裝載比索引後裝載更快。若是有索引,不只必須增長記錄到數據文件,並且還要修改每一個索引以反映增長了的新記錄。
·較短的 SQL 語句比較長的 SQL 語句要快,由於它們涉及服務器方的分析較少,並且還由於將它們經過網絡從客戶機發送到服務器更快。
這些因素中有一些彷佛微不足道(特別是最後一個因素),但若是要裝載大量的數據,即便是很小的因素也會產生很大的不一樣結果。
INSERT查詢的速度
插入一個記錄的時間由下列組成:
·鏈接:(3)
·發送查詢給服務器:(2)
·分析查詢:(2)
·插入記錄:(1 x 記錄大小)
·插入索引:(1 x 索引)
·關閉:(1)
這裏的數字有點與整體時間成正比。這不考慮打開表的初始開銷(它爲每一個併發運行的查詢作一次)。
表的大小以N log N (B 樹)的速度減慢索引的插入。
加快插入的一些方法:
·若是你同時從同一客戶插入不少行,使用多個值表的INSERT語句。這比使用分開INSERT語句快(在一些狀況中幾倍)。
·若是你從不一樣客戶插入不少行,你能經過使用INSERT DELAYED語句獲得更高的速度。
·注意,用MyISAM,若是在表中沒有刪除的行,能在SELECT:s正在運行的同時插入行。
·當從一個文本文件裝載一個表時,使用LOAD DATA INFILE。這一般比使用不少INSERT語句快20倍。
·當表有不少索引時,有可能多作些工做使得LOAD DATA INFILE更快些。使用下列過程:
一、有選擇地用CREATE TABLE建立表。例如使用mysql或Perl-DBI。
二、執行FLUSH TABLES,或外殼命令mysqladmin flush-tables。
三、使用myisamchk --keys-used=0 -rq /path/to/db/tbl_name。這將從表中刪除全部索引的使用。
四、用LOAD DATA INFILE把數據插入到表中,這將不更新任何索引,所以很快。
五、若是你有myisampack而且想要壓縮表,在它上面運行myisampack。
六、用myisamchk -r -q /path/to/db/tbl_name再建立索引。這將在將它寫入磁盤前在內存中建立索引樹,而且它更快,由於避免大量磁盤尋道。結果索引樹也被完美地平衡。
七、執行FLUSH TABLES,或外殼命令mysqladmin flush-tables。
這個過程將被構造進在MySQL的某個將來版本的LOAD DATA INFILE。
·你能夠鎖定你的表以加速插入
|
主要的速度差異是索引緩衝區僅被清洗到磁盤上一次,在全部INSERT語句完成後。通常有與有不一樣的INSERT語句那樣奪的索引緩衝區清洗。若是你能用一個單個語句插入全部的行,鎖定就不須要。鎖定也將下降多鏈接測試的總體時間,可是對某些線程最大等待時間將上升(由於他們等待鎖)。例如:
thread 1 does 1000 inserts |
若是你不使用鎖定,二、3和4將在1和5前完成。若是你使用鎖定,二、3和4將可能不在1或5前完成,可是總體時間應該快大約40%。由於INSERT, UPDATE和DELETE操做在MySQL中是很快的,經過爲多於大約5次接二連三地插入或更新一行的東西加鎖,你將得到更好的總體性能。若是你作不少一行的插入,你能夠作一個LOCK TABLES,偶爾隨後作一個UNLOCK TABLES(大約每1000行)以容許另外的線程存取表。這仍然將致使得到好的性能。固然,LOAD DATA INFILE對裝載數據仍然是更快的。
爲了對LOAD DATA INFILE和INSERT獲得一些更快的速度,擴大關鍵字緩衝區。
UPDATE查詢的速度
更改查詢被優化爲有一個寫開銷的一個SELECT查詢。寫速度依賴於被更新數據大小和被更新索引的數量。
使更改更快的另外一個方法是推遲更改而且而後一行一行地作不少更改。若是你鎖定表,作一行一行地不少更改比一次作一個快。
注意,動態記錄格式的更改一個較長總長的記錄,可能切開記錄。所以若是你常常這樣作,時不時地OPTIMIZE TABLE是很是重要的。
DELETE查詢的速度
刪除一個記錄的時間精確地與索引數量成正比。爲了更快速地刪除記錄,你能夠增長索引緩存的大小。
從一個表刪除全部行比刪除行的一大部分也要得多。
索引對有效裝載數據的影響
若是表是索引的,則可利用批量插入(LOAD DATA 或多行的 INSERT 語句)來減小索引的開銷。這樣會最小化索引更新的影響,由於索引只須要在全部行處理過期才進行刷新,而不是在每行處理後就刷新。
·若是須要將大量數據裝入一個新表,應該建立該表且在未索引時裝載,裝載數據後才建立索引,這樣作較快。一次建立索引(而不是每行修改一次索引)較快。
·若是在裝載以前刪除或禁用索引,裝入數據後再從新建立或啓用索引可能使裝載更快。
·若是想對數據裝載使用刪除或禁用策略,必定要作一些實驗,看這樣作是否值得(若是將少許數據裝入一個大表中,重建和索引所花費的時間可能比裝載數據的時間還要長)。
可用DROP INDEX和CREATE INDEX 來刪除和重建索引。
另外一種可供選擇的方法是利用 myisamchk 或 isamchk 禁用和啓用索引。這須要在 MySQL 服務器主機上有一個賬戶,並對錶文件有寫入權。爲了禁用表索引,可進入相應的數據庫目錄,執行下列命令之一:
|
對具備 .MYI 擴展名的索引文件的 MyISAM 表使用 myisamchk,對具備 .ISM 擴展名的索引文件的 ISAM 表使用 isamchk。在向表中裝入數據後,按以下激活索引:
|
n 爲表具備的索引數目。可用 --description 選項調用相應的實用程序得出這個值:
|
若是決定使用索引禁用和激活,應該使用第13章中介紹的表修復鎖定協議以阻止服務器同時更改鎖(雖然此時不對錶進行修復,但要對它像表修復過程同樣進行修改,所以須要使用相同的鎖定協議)。
上述數據裝載原理也適用於與須要執行不一樣操做的客戶機有關的固定查詢。例如,通常但願避免在頻繁更新的表上長時間運行 SELECT 查詢。長時間運行 SELECT 查詢會產生大量爭用,並下降寫入程序的性能。一種可能的解決方法爲,若是執行寫入的主要是 INSERT 操做,那麼先將記錄存入一個臨時表,而後按期地將這些記錄加入主表中。若是須要當即訪問新記錄,這不是一個可行的方法。但只要能在一個較短的時間內不訪問它們,就可使用這個方法。使用臨時表有兩個方面的好處。首先,它減小了與主表上 SELECT 查詢語句的爭用,所以,執行更快。其次,從臨時表將記錄裝入主表的總時間較分別裝載記錄的總時間少;相應的索引高速緩存只需在每一個批量裝載結束時進行刷新,而不是在每行裝載後刷新。
這個策略的一個應用是進入 Web 服務器的Web 頁訪問 MySQL 數據庫。在此情形下,可能沒有保證記錄當即進入主表的較高權限。
若是數據並不徹底是那種在系統非正常關閉事件中插入的單個記錄,那麼減小索引刷新的另外一策略是使用 MyISAM 表的 DELAYED_KEY_WRITE 表建立選項(若是將 MySQL 用於某些數據錄入工做時可能會出現這種狀況)。此選項使索引高速緩存只偶爾刷新,而不是在每次插入後都要刷新。
若是但願在服務器範圍內利用延遲索引刷新,只要利用 --delayed-key-write 選項啓動 mysqld 便可。在此情形下,索引塊寫操做延遲到必須刷新塊以便爲其餘索引值騰出空間爲止,或延遲到執行了一個 flush-tables 命令後,或延遲到該索引表關閉。