MySql(八):MySQL性能調優——Query 的優化

1、理解MySQL的Query Optimizer

MySQL Optimizer是一個專門負責優化SELECT 語句的優化器模塊,它主要的功能就是經過計算分析系統中收集的各類統計信息,爲客戶端請求的Query 給出他認爲最優的執行計劃,也就是他認爲最優的數據檢索方式。html

2、Query 語句優化基本思路和原則

在分析如何優化MySQL Query 以前,咱們須要先了解一下Query 語句優化的基本思路和原則。通常來講,Query 語句的優化思路和原則主要體如今如下幾個方面:mysql

重中之重,需熟背算法

1. 優化更須要優化的Query;
2. 定位優化對象的性能瓶頸;
3. 明確的優化目標;
4. 從Explain 入手;
5. 多使用profile
6. 永遠用小結果集驅動大的結果集;
7. 儘量在索引中完成排序;
8. 只取出本身須要的Columns;
9. 僅僅使用最有效的過濾條件;
10. 儘量避免複雜的Join 和子查詢;sql

上面所列的幾點信息,前面4 點能夠理解爲Query優化的一個基本思路,後面部分則是咱們優化中的基本原則數據庫

下面咱們先針對Query 優化的基本思路作一些簡單的分析,理解爲何咱們的Query 優化到底該如何進行。網絡

1)優化更須要優化的Query

爲何咱們須要優化更須要優化的Query?這個地球人都知道的「並不能成爲問題的問題」我想就並不須要我過多解釋吧,哈哈。數據結構

那什麼樣的Query 是更須要優化呢?對於這個問題咱們須要從對整個系統的影響來考慮。什麼Query 的優化能給系統總體帶來更大的收益,就更須要優化。通常來講,高併發低消耗(相對)的Query 對整個系統的影響遠比低併發高消耗的Query 大。咱們能夠經過如下一個很是簡單的案例分析來充分說明問題。併發

假設有一個Query 每小時執行10000 次,每次須要20 個IO。另一個Query 每小時執行10 次,每次須要20000 個IO。咱們先經過IO 消耗方面來分析。能夠看出,兩個Query 每小時所消耗的IO 總數目是同樣的,都是200000 IO/小時。假設咱們優化第一個Query,從20 個IO 下降到18 個IO,也就是僅僅下降了2 個IO,則咱們節省了2 * 10000 = 20000 (IO/小時)。而若是但願經過優化第二個Query 達到相同的效果,咱們必需要讓每一個Query 減小20000 / 10 = 2000 IO。我想你們都會相信讓第一個Query 節省2 個IO遠比第二個Query 節省2000 個IO 來的容易。數據庫設計

其次,若是經過CPU 方面消耗的比較,原理和上面的徹底同樣。只要讓第一個Query 稍微節省一小塊資源,就可讓整個系統節省出一大塊資源,尤爲是在排序,分組這些對CPU 消耗比較多的操做中尤爲突出。函數

最後,咱們從對整個系統的影響來分析。一個頻繁執行的高併發Query 的危險性比一個低併發的Query 要大不少。當一個低併發的Query 走錯執行計劃,所帶來的影響主要只是該Query 的請求者的體驗會變差,對總體系統的影響並不會特別的突出,之少還屬於可控範圍。可是,若是咱們一個高併發的Query 走錯了執行計劃,那所帶來的後果極可能就是災難性的,不少時候可能連自救的機會都不給你就會讓整個系統Crash 掉。曾經我就遇到這樣一個案例,系統中一個併發度較高的Query 語句走錯執行計劃,系統頃刻間Crash,甚至我都尚未反應過來是怎麼回事。當從新啓動數據庫提供服務後,系統負載馬上直線飆升,甚至都來不及登陸數據庫查看當時有哪些Active 的線程在執行哪些Query。若是是遇到一個併發並不過高的Query 走錯執行計劃,至少咱們還能夠控制整個系統不至於系統被直接壓跨,甚至連問題根源都難以抓到。

整體來講就是優先優化併發高的query,高併發的query必定要想辦法優化到最優

2)定位優化對象的性能瓶頸

當咱們拿到一條須要優化的Query 以後,第一件事情是什麼?是反問本身,這條Query 有什麼問題?我爲何要優化他?只有明白了這些問題,咱們才知道咱們須要作什麼,纔可以找到問題的關鍵。而不能就只是以爲某個Query 好像有點慢,須要優化一下,而後就開始一個一個優化方法去輪番嘗試。這樣極可能整個優化過程會消耗大量的人力和時間成本,甚至可能到最後仍是得不到一個好的優化結果。這就像看病同樣,醫生必需要清楚的知道咱們病的根源才能對症下藥。若是隻是知道咱們什麼地方不舒服,而後就開始經過各類藥物嘗試治療,那這樣所帶來的後果可能就很是嚴重了。
因此,在拿到一條須要優化的Query 以後,咱們首先要判斷出這個Query 的瓶頸究竟是IO 仍是CPU。究竟是由於在數據訪問消耗了太多的時間,仍是在數據的運算(如分組排序等)方面花費了太多資源

3)明確的優化目標

當咱們定位到了一條Query 的性能瓶頸以後,就須要經過分析該Query 所完成的功能和Query 對系統的總體影響制訂出一個明確的優化目標。

如何設定優化目標?

通常來講,咱們首先須要清楚的瞭解數據庫目前的總體狀態,同時也要清楚的知道數據庫中與該Query 相關的數據庫對象的各類信息,並且還要了解該Query 在整個應用系統中所實現的功能。瞭解了數據庫總體狀態,咱們就能知道數據庫所能承受的最大壓力,也就清楚了咱們可以接受的最悲觀狀況。把握了該Query 相關數據庫對象的信息,咱們就應該知道實現該Query 的消耗最理想狀況下須要消耗多少資源,最糟糕又須要消耗多少資源。最後,經過該Query 所實現的功能點在整個應用系統中的重要地位,咱們能夠大概的分析出該Query 能夠佔用的系統資源比例,並且咱們也可以知道該Query 的效率給客戶帶來的體驗影響到底有多大。

當咱們清楚了這些信息以後,咱們基本能夠得出該Query 應該知足的一個性能範圍是怎樣的,這也就是咱們的優化目標範圍,而後就是經過尋找相應的優化手段來解決問題了。若是該Query 實現的應用系統功能比較重要,咱們就必須讓目標更偏向於理想值一些,即便在其餘某些方面做出一些讓步與犧牲,好比調整schema 設計,調整索引組成等,可能都是須要的。而若是該Query 所實現的是一些並非太關鍵的功能,那咱們可讓目標更偏向悲觀值一些,而儘可能保證其餘更重要的Query 的性能。這種時候,即便須要調整商業需求,減小功能實現,也不得不該該做出讓步。

4)從Explain 入手

爲何從Explain 入手?由於只有Explain 才能告訴你,這個Query 在數據庫中是以一個什麼樣的執行計劃來實現的。

5)永遠用小結果集驅動大的結果集

不少人喜歡在優化SQL 的時候說用小表驅動大表,我的認爲這樣的說法不太嚴謹。爲何?由於大表通過WHERE 條件過濾以後所返回的結果集並不必定就比小表所返回的結果集大,可能反而更小。

在這種狀況下若是仍然採用小表驅動大表,就會獲得相反的性能效果。其實這樣的結果也很是容易理解,在MySQL 中的Join,只有Nested Loop 一種Join 方式,也就是MySQL 的Join 都是經過嵌套循環來實現的。驅動結果集越大,所須要循環的此時就越多,那麼被驅動表的訪問次數天然也就越多,而每次訪問被驅動表,即便須要的邏輯IO 不多,循環次數多了,總量天然也不可能很小,並且每次循環都不能避免的須要消耗CPU ,因此CPU 運算量也會跟着增長。因此,若是咱們僅僅以表的大小來做爲驅動表的判斷依據,倘若小表過濾後所剩下的結果集比大表多不少,結果就是須要的嵌套循環中帶來更多的循環次數,反之,所須要的循環次數就會更少,整體IO 量和CPU 運算量也會少。並且,就算是非Nested Loop 的Join 算法,如Oracle 中的Hash Join,一樣是小結果集驅動大的結果集是最優的選擇。

因此,在優化Join Query 的時候,最基本的原則就是「小結果集驅動大結果集」,經過這個原則來減小嵌套循環中的循環次數,達到減小IO 總量以及CPU 運算的次數。

能夠看看這篇:聯表查詢時始終以小結果集驅動大結果集

6)儘量在索引中完成排序

參閱:MySQL如何利用索引優化ORDER BY排序語句

7)只取出本身須要的Columns

這個很簡單,不要全部的查詢都直接 select *,而是隻取本身須要的列。

8)僅僅使用最有效的過濾條件

不少人在優化Query 語句的時候很容易進入一個誤區,那就是以爲WHERE 子句中的過濾條件越多越好,實際上這並非一個很是正確的選擇

爲何說過濾條件多不必定是好事呢?請看下面示例:

需求: 查找某個用戶在全部group 中所發的討論message 基本信息。

場景:

一、知道用戶ID 和用戶nick_name
二、信息所在表爲group_message
三、group_message 中存在用戶ID(user_id)和nick_name(author)兩個索引

方案一:將用戶ID 和用戶nick_name 二者都做爲過濾條件放在WHERE 子句中來查詢,Query 的執行計劃以下:

sky@localhost : example 11:29:37> EXPLAIN SELECT * FROM group_message -> WHERE user_id = 1 AND author='1111111111'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: group_message type: ref possible_keys: group_message_author_ind,group_message_uid_ind key: group_message_author_ind key_len: 98 ref: const rows: 1 Extra: Using where
1 row in set (0.00 sec)

方案二:僅僅將用戶ID 做爲過濾條件放在WHERE 子句中來查詢,Query 的執行計劃以下:

sky@localhost : example 11:30:45> EXPLAIN SELECT * FROM group_message -> WHERE user_id = 1\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: group_message type: ref possible_keys: group_message_uid_ind key: group_message_uid_ind key_len: 4 ref: const rows: 1 Extra: 1 row in set (0.00 sec)

方案三:僅將用戶nick_name 做爲過濾條件放在WHERE 子句中來查詢,Query 的執行計劃以下:

sky@localhost : example 11:38:45> EXPLAIN SELECT * FROM group_message -> WHERE author = '1111111111'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: group_message type: ref possible_keys: group_message_author_ind key: group_message_author_ind key_len: 98 ref: const rows: 1 Extra: Using where
1 row in set (0.00 sec)

初略一看三個執行計劃好像都挺好的啊,每個Query 的執行類型都利用到了索引,並且都是「ref」類型。但是仔細一分析,就會發現,group_message_uid_ind 索引的索引鍵長度爲4(key_len:4),因爲user_id 字段類型爲int,因此咱們能夠斷定出Query Optimizer 給出的這個索引鍵長度是徹底準確的。而group_message_author_ind 索引的索引鍵長度爲98(key_len: 98),由於author 字段定義爲varchar(32) ,而所使用的字符集是utf8,32 * 3 + 2 = 98。並且,因爲user_id 與author(來源於nick_name)所有都是一一對應的,因此同一個user_id 有哪些記錄,那麼所對應的author 也會有徹底相同的記錄。因此,一樣的數據在group_message_author_ind 索引中所佔用的存儲空間要遠遠大於group_message_uid_ind 索引所佔用的空間。佔用空間更大,表明咱們訪問該索引所須要讀取的數據量就會更多。因此,選擇group_message_uid_ind 的執行計劃纔是最有的執行計劃。也就是說,上面的方案二纔是最有方案,而使用了更多的WHERE 條件的方案一反而沒有僅僅使用user_id一個過濾條件的方案一優。

可能有些人會說,那若是將user_id 和author 二者創建聯合索引呢?告訴你,效果可能比沒有這個索引的時候更差,由於這個聯合索引的索引鍵更長,索引佔用的空間將會更大。
這個示例並不必定能表明全部場景,僅僅是但願讓你們明白,並非任什麼時候候都是使用的過濾條件越多性能會越好。在實際應用場景中,確定會存在更多更復雜的情形,怎樣使咱們的Query 有一個更優化的執行計劃,更高效的性能,還須要靠你們仔細分析各類執行計劃的具體差異,才能選擇出更優化的Query。

9)儘量避免複雜的Join 和子查詢

咱們都知道,MySQL 在併發這一塊作的並非太好,當併發量過高的時候,系統總體性能可能會急劇降低,尤爲是遇到一些較爲複雜的Query 的時候更是如此。這主要與MySQL 內部資源的爭用鎖定控制有關,如讀寫相斥等等。對於Innodb 存儲引擎因爲實現了行級鎖定可能還要稍微好一些,若是使用的MyISAM 存儲引擎,併發一旦較高的時候,性能降低很是明顯。因此,咱們的Query 語句所涉及到的表越多,所須要鎖定的資源就越多。也就是說,越複雜的Join 語句,所須要鎖定的資源也就越多,所阻塞的其餘線程也就越多。相反,若是咱們將比較複雜的Query 語句分拆成多個較爲簡單的Query 語句分步執行,每次鎖定的資源也就會少不少,所阻塞的其餘線程也要少一些。

可能不少讀者會有疑問,將複雜Join 語句分拆成多個簡單的Query 語句以後,那不是咱們的網絡交互就會更多了嗎?網絡延時方面的整體消耗也就更大了啊,完成整個查詢的時間不是反而更長了嗎?是的,這種狀況是可能存在,但也並非確定就會如此。咱們能夠再分析一下,一個複雜的Join Query語句在執行的時候,所須要鎖定的資源比較多,可能被別人阻塞的機率也就更大,若是是一個簡單的Query,因爲須要鎖定的資源較少,被阻塞的機率也會小不少。因此較爲複雜的Join Query 也有可能在執行以前被阻塞而浪費更多的時間。並且,咱們的數據庫所服務的並非單單這一個Query 請求,還有不少不少其餘的請求,在高併發的系統中,犧牲單個Query 的短暫響應時間而提升總體處理能力也是很是值得的優化自己就是一門平衡與取捨的藝術,只有懂得取捨,平衡總體,才能讓系統更優。

對於子查詢,可能不須要我多說不少人就明白爲何會不被推薦使用。在MySQL 中,子查詢的實現目前還比較差,很可貴到一個很好的執行計劃,不少時候明明有索引能夠利用,可Query Optimizer 就是不用。從MySQL 官方給出的信息說,這一問題將在MySQL6.0 中獲得較好的解決,將會引入SemiJoin 的執行計劃,可MySQL6.0 離咱們投入生產環境使用恐怕還有很遙遠的一段時間。因此,在Query 優化的過程當中,能不用子查詢的時候就儘可能不要使用子查詢

 

10)充分利用 Explain 和 Profi l ing

Explain的使用

 

參閱:

MySQL Explain詳解

11)Profiling 的使用

MySQL 的Query Profiler 是一個使用很是方便的Query 診斷分析工具,經過該工具能夠獲取一條Query 在整個執行過程當中多種資源的消耗狀況,如CPU,IO,IPC,SWAP 等,以及發生的PAGE FAULTS,CONTEXT SWITCHE 等等,同時還能獲得該Query 執行過程當中MySQL 所調用的各個函數在源文件中的位置。下面咱們看看Query Profiler 的具體用法。

1.開啓profiling 參數

root@localhost : (none) 10:53:11> set profiling=1; Query OK, 0 rows affected (0.00 sec)

經過執行「set profiling」命令,能夠開啓關閉Query Profiler 功能。

2.執行Query

... ... root@localhost : test 07:43:18> select status,count(*) -> from test_profiling group by status; +----------------+----------+
| status | count(*) |
+----------------+----------+
| st_xxx1 | 27 |
| st_xxx2 | 6666 |
| st_xxx3 | 292887 |
| st_xxx4 | 15 |
+----------------+----------+
5 rows in set (1.11 sec) ... ...

在開啓Query Profiler 功能以後,MySQL 就會自動記錄全部執行的Query 的profile 信息了。

3.獲取系統中保存的全部Query 的profile 概要信息

root@localhost : test 07:47:35> show profiles; +----------+------------+------------------------------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+------------------------------------------------------------+
| 1 | 0.00183100 | show databases |
| 2 | 0.00007000 | SELECT DATABASE() |
| 3 | 0.00099300 | desc test |
| 4 | 0.00048800 | show tables |
| 5 | 0.00430400 | desc test_profiling |
| 6 | 1.90115800 | select status,count(*) from test_profiling group by status |
+----------+------------+------------------------------------------------------------+
3 rows in set (0.00 sec)

經過執行「SHOW PROFILE」 命令獲取當前系統中保存的多個Query 的profile 的概要信息。

4.針對單個Query 獲取詳細的profile 信息

在獲取到概要信息以後,咱們就能夠根據概要信息中的Query_ID 來獲取某個Query 在執行過程當中詳細的profile 信息了,具體操做以下:

root@localhost : test 07:49:24> show profile cpu, block io for query 6; +----------------------+----------+----------+------------+--------------+---------------+
| Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+----------------------+----------+----------+------------+--------------+---------------+
| starting | 0.000349 | 0.000000 | 0.000000 | 0 | 0 |
| Opening tables | 0.000012 | 0.000000 | 0.000000 | 0 | 0 |
| System lock | 0.000004 | 0.000000 | 0.000000 | 0 | 0 |
| Table lock | 0.000006 | 0.000000 | 0.000000 | 0 | 0 |
| init | 0.000023 | 0.000000 | 0.000000 | 0 | 0 |
| optimizing | 0.000002 | 0.000000 | 0.000000 | 0 | 0 |
| statistics | 0.000007 | 0.000000 | 0.000000 | 0 | 0 |
| preparing | 0.000007 | 0.000000 | 0.000000 | 0 | 0 |
| Creating tmp table | 0.000035 | 0.000999 | 0.000000 | 0 | 0 |
| executing | 0.000002 | 0.000000 | 0.000000 | 0 | 0 |
| Copying to tmp table | 1.900619 | 1.030844 | 0.197970 | 347 | 347 |
| Sorting result | 0.000027 | 0.000000 | 0.000000 | 0 | 0 |
| Sending data | 0.000017 | 0.000000 | 0.000000 | 0 | 0 |
| end | 0.000002 | 0.000000 | 0.000000 | 0 | 0 |
| removing tmp table | 0.000007 | 0.000000 | 0.000000 | 0 | 0 |
| end | 0.000002 | 0.000000 | 0.000000 | 0 | 0 |
| query end | 0.000003 | 0.000000 | 0.000000 | 0 | 0 |
| freeing items | 0.000029 | 0.000000 | 0.000000 | 0 | 0 |
| logging slow query | 0.000001 | 0.000000 | 0.000000 | 0 | 0 |
| logging slow query | 0.000002 | 0.000000 | 0.000000 | 0 | 0 |
| cleaning up | 0.000002 | 0.000000 | 0.000000 | 0 | 0 |
+----------------------+----------+----------+------------+--------------+---------------+

上面的例子中是獲取CPU 和Block IO 的消耗,很是清晰,對於定位性能瓶頸很是適用。但願獲得取其餘的信息,均可以經過執行「SHOW PROFILE *** FOR QUERY n」 來獲取,各位讀者朋友能夠自行測試熟悉。

11)合理設計並利用索引

由於索引對咱們的Query 性能影響很大,因此咱們更應該深刻理解MySQL 中索引的基本實現,以及不一樣索引之間的區別,才能分析出如何設計出最優的索引來最大幅度的提高Query 的執行效率。

在MySQL 中,主要有四種類型的索引,分別爲:B-Tree 索引Hash 索引Fulltext 索引RTree索引,下面針對這四種索引的基本實現方式及存儲結構作一個大概的分析。

1.B-Tree 索引

B-Tree 索引是MySQL 數據庫中使用最爲頻繁的索引類型除了Archive 存儲引擎以外的其餘全部的存儲引擎都支持B-Tree 索引。不只僅在MySQL 中是如此,實際上在其餘的不少數據庫管理系統中B-Tree 索引也一樣是做爲最主要的索引類型,這主要是由於B-Tree 索引的存儲結構在數據庫的數據檢索中有很是優異的表現。

通常來講,MySQL 中的B-Tree 索引的物理文件大多都是以Balance Tree 的結構來存儲的,也就是全部實際須要的數據都存放於Tree 的Leaf Node,並且到任何一個Leaf Node 的最短路徑的長度都是徹底相同的,因此咱們你們都稱之爲B-Tree 索引固然,可能各類數據庫(或MySQL 的各類存儲引擎)在存放本身的B-Tree 索引的時候會對存儲結構稍做改造。如Innodb 存儲引擎的B-Tree 索引實際使用的存儲結構其實是B+Tree,也就是在B-Tree 數據結構的基礎上作了很小的改造,在每個Leaf Node 上面除了存放索引鍵的相關信息以外,還存儲了指向與該Leaf Node 相鄰的後一個LeafNode 的指針信息,這主要是爲了加快檢索多個相鄰Leaf Node 的效率考慮。

Innodb 存儲引擎中,存在兩種不一樣形式的索引,一種是Cluster 形式的主鍵索引(Primary Key),另一種則是和其餘存儲引擎(如MyISAM 存儲引擎)存放形式基本相同的普通B-Tree 索引,這種索引在Innodb 存儲引擎中被稱爲Secondary Index。下面咱們經過圖示來針對這兩種索引的存放形式作一個比較。

圖示中左邊爲Clustered 形式存放的Primary Key,右側則爲普通的B-Tree 索引。兩種索引在Root Node 和Branch Nodes 方面都仍是徹底同樣的。而Leaf Nodes 就出現差別了。在Primary Key中,Leaf Nodes 存放的是表的實際數據,不只僅包括主鍵字段的數據,還包括其餘字段的數據,整個數據以主鍵值有序的排列。而Secondary Index 則和其餘普通的B-Tree 索引沒有太大的差別,只是在Leaf Nodes 除了存放索引鍵的相關信息外,還存放了Innodb 的主鍵值

因此,在Innodb 中若是經過主鍵來訪問數據效率是很是高的,而若是是經過Secondary Index 來訪問數據的話,Innodb 首先經過Secondary Index 的相關信息,經過相應的索引鍵檢索到Leaf Node以後,須要再經過Leaf Node 中存放的主鍵值再經過主鍵索引來獲取相應的數據行。

MyISAM 存儲引擎的主鍵索引和非主鍵索引差異很小,只不過是主鍵索引的索引鍵是一個惟一且非空的鍵而已。並且MyISAM 存儲引擎的索引和Innodb 的Secondary Index 的存儲結構也基本相同,主要的區別只是MyISAM 存儲引擎在Leaf Nodes 上面出了存放索引鍵信息以外,再存放能直接定位到MyISAM 數據文件中相應的數據行的信息(如Row Number),但並不會存放主鍵的鍵值信息。

2.Hash 索引

Hash 索引在MySQL 中使用的並非不少,目前主要是Memory 存儲引擎使用,並且在Memory 存儲引擎中將Hash 索引做爲默認的索引類型。所謂Hash 索引,實際上就是經過必定的Hash 算法,將須要索引的鍵值進行Hash 運算,而後將獲得的Hash 值存入一個Hash 表中。而後每次須要檢索的時候,都會將檢索條件進行相同算法的Hash 運算,而後再和Hash 表中的Hash 值進行比較並得出相應的信息。

在Memory 存儲引擎中,MySQL 還支持非惟一的Hash 索引。可能不少人會比較驚訝,若是是非惟一的Hash 索引,那相同的值該如何處理呢?在Memory 存儲引擎的Hash 索引中,若是遇到非惟一值,存儲引擎會將他們連接到同一個hash 鍵值下以一個鏈表的形式存在,而後在取得實際鍵值的時候時候再過濾不符合的鍵。

因爲Hash 索引結構的特殊性,其檢索效率很是的高,索引的檢索能夠一次定位,而不須要像BTree索引須要從根節點再到枝節點最後才能訪問到頁節點這樣屢次IO 訪問,因此Hash 索引的效率要遠高於B-Tree 索引。

可能不少人又會有疑問了,既然Hash 索引的效率要比B-Tree 高不少,爲何你們不都用Hash索引而還要使用B-Tree 索引呢?任何事物都是有兩面性的,,Hash 索引也同樣,雖然Hash 索引檢索效率很是之高,可是Hash 索引自己因爲其實的特殊性也帶來了不少限制和弊端,主要有如下這些:

1. Hash 索引僅僅只能知足「=」,「IN」和「<=>」查詢,不能使用範圍查詢;

因爲Hash 索引所比較的是進行Hash 運算以後的Hash 值,因此Hash 索引只能用於等值的過濾,而不能用於基於範圍的過濾,由於通過相應的Hash 算法處理以後的Hash 值的大小關係,並不能保證還和Hash 運算以前徹底同樣。

2. Hash 索引沒法被利用來避免數據的排序操做;

因爲Hash 索引中存放的是通過Hash 計算以後的Hash 值,並且Hash 值的大小關係並不必定和Hash 運算前的鍵值的徹底同樣,因此數據庫沒法利用索引的數據來避免任何和排序運算;

3. Hash 索引不能利用部分索引鍵查詢;

對於組合索引,Hash 索引在計算Hash 值的時候是組合索引鍵合併以後再一塊兒計算Hash 值,而不是單獨計算Hash 值,因此當咱們經過組合索引的前面一個或幾個索引鍵進行查詢的時候,Hash 索引也沒法被利用到;

4. Hash 索引在任什麼時候候都不能避免表掃面;

前面咱們已經知道,Hash 索引是將索引鍵經過Hash 運算以後,將Hash 運算結果的Hash 值和所對應的行指針信息存放於一個Hash 表中,並且因爲存在不一樣索引鍵存在相同Hash 值的可能,因此即便咱們僅僅取知足某個Hash 鍵值的數據的記錄條數,都沒法直接從Hash 索引中直接完成查詢,仍是要經過訪問表中的實際數據進行相應的比較而獲得相應的結果。

5. Hash 索引遇到大量Hash 值相等的狀況後性能並不必定就會比B-Tree 索引高;

對於選擇性比較低的索引鍵,若是咱們建立Hash 索引,那麼咱們將會存在大量記錄指針信息存與同一個Hash 值相關連。這樣要定位某一條記錄的時候就會很是的麻煩,可能會浪費很是屢次表數據的訪問,而形成總體性能的地下。

3.Full-text 索引

Full-text 索引也就是咱們常說的全文索引,目前在MySQL 中僅有MyISAM 存儲引擎支持,並且也並非全部的數據類型都支持全文索引。目前來講,僅有CHAR,VARCHAR 和TEXT 這三種數據類型的列能夠建Full-text 索引

通常來講,Fulltext 索引主要用來替代效率低下的LIKE '%***%' 操做。實際上,Full-text 索引並不僅是能簡單的替代傳統的全模糊LIKE 操做,並且能經過多字段組合的Full-text 索引一次全模糊匹配多個字段

Full-text 索引和普通的B-Tree 索引的實現區別較大,雖然他一樣是以B-Tree 形式來存放索引數據,可是他並非經過字段內容的完整匹配,而是經過特定的算法,將字段數據進行分隔後再進行的索引。通常來講MySQL 系統會按照四個字節來分隔。在整個Full-text 索引中,存儲內容被分爲兩部分,一部分是分隔前的索引字符串數據集合,另外一部分是分隔後的詞(或者詞組)的索引信息。因此,Full-text 索引中,真正在B-Tree 索引細細中的並非咱們表中的原始數據,而是分詞以後的索引數據。在B-Tree 索引的節點信息中,存放了各個分隔後的詞信息,以及指向包含該詞的分隔前字符串信息在索引數據集合中的位置信息。

Full-text 索引不只僅能實現模糊匹配查找,在實現了基於天然語言的的匹配度查找。固然,這個匹配讀到底有多準確就須要讀者朋友去自行驗證了。Full-text 經過一些特定的語法信息,針對天然語言作了各類相應規則的匹配,最後給出非負的匹配值。

此外,有一點是須要你們注意的,MySQL 目前的Full-text 索引在中文支持方面還不太好,須要藉助第三方的補丁或者插件來完成。並且Full-text 的建立所消耗的資源也是比較大的,因此在應用於實際生產環境以前仍是儘可能作好評估。

關於Full-text 的實際使用方法因爲不是本書的重點,感興趣的讀者朋友能夠自行參閱MySQL 關於Full-text 相關的使用手冊來了解更爲詳盡的信息。

4.R-Tree 索引

R-Tree 索引多是咱們在其餘數據庫中不多見到的一種索引類型,主要用來解決空間數據檢索的問題。

在MySQL 中,支持一種用來存放空間信息的數據類型GEOMETRY,且基於OpenGIS 規範。在MySQL5.0.16 以前的版本中,僅僅MyISAM 存儲引擎支持該數據類型,可是從MySQL5.0.16 版本開始,BDB,Innodb,NDBCluster 和Archive 存儲引擎也開始支持該數據類型。固然,雖然多種存儲引擎都開始支持GEOMETRY 數據類型,可是僅僅以後MyISAM 存儲引擎支持R-Tree 索引。

在MySQL 中採用了具備二次分裂特性的R-Tree 來索引空間數據信息,而後經過幾何對象(MRB)信息來建立索引。

雖然僅僅只有MyISAM 存儲引擎支持空間索引(R-Tree Index),可是若是咱們是精確的等值匹配,建立在空間數據上面的B-Tree 索引一樣能夠起到優化檢索的效果,空間索引的主要優點在於當咱們使用範圍查找的時候,能夠利用到R-Tree 索引,而這時候,B-Tree 索引就無能爲力了。

對於R-Tree 索引的詳細介紹和使用信息清參閱MySQL 使用手冊。

5.索引的利弊與如何斷定是否須要索引

索引可以極大的提升咱們數據檢索的效率,讓咱們的Query 執行的更快,可是索引在極大提升檢索效率的同時,也給咱們的數據庫帶來了一些負面的影響。下面咱們就分別對MySQL 中索引的利與弊作一個簡單的分析。

5-1.索引的利處

索引除了可以提升數據檢索的效率,下降數據庫的IO 成本外,還有一個很是重要的用途,那就是下降數據的排序成本

咱們知道,每一個索引中索引數據都是按照索引鍵鍵值進行排序後存放的,因此,當咱們的Query 語句中包含排序分組操做的時候,若是咱們的排序字段和索引鍵字段恰好一致,MySQL Query Optimizer就會告訴mysqld 在取得數據以後不用排序了,由於根據索引取得的數據已是知足客戶的排序要求。

那若是是分組操做呢?分組操做沒辦法直接利用索引完成。可是分組操做是須要先進行排序而後才分組的,因此當咱們的Query 語句中包含分組操做,並且分組字段也恰好和索引鍵字段一致,那麼mysqld 一樣能夠利用到索引已經排好序的這個特性而省略掉分組中的排序操做。

排序分組操做主要消耗的是咱們的內存和CPU 資源,若是咱們可以在進行排序分組操做中利用好索引,將會極大的下降CPU 資源的消耗。

5-2.索引的弊端

雖然,索引可以極大的提升數據檢索效率,也可以改善排序分組操做的性能,可是咱們不能忽略的一個問題就是索引是徹底獨立於基礎數據以外的一部分數據

假設咱們在Table ta 中的Column ca 建立了索引idx_ta_ca,那麼任何更新Column ca 的操做,MySQL 都須要在更新表中Column ca 的同時,也更新Column ca 的索引數據,調整由於更新所帶來鍵值變化後的索引信息。而若是咱們沒有對Column ca 進行索引的話,MySQL 所須要作的僅僅只是更新表中Column ca 的信息。這樣,所帶來的最明顯的資源消耗就是增長了更新所帶來的IO 量和調整索引所致的計算量。此外,Column ca 的索引idx_ta_ca 是須要佔用存儲空間的,並且隨着Table ta 數據量的增加,idx_ta_ca 所佔用的空間也會不斷增加。因此索引還會帶來存儲空間資源消耗的增加。

概括總結下索引的優缺點

重點,須要熟背

優勢:

a.提升數據檢索效率

b.下降數據庫的IO成本

c.下降數據的排序成本

缺點:

a.數據更新時須要額外更新相關索引信息,增長了了數據庫IO和計算量

b.須要佔用存儲空間

6.如何斷定是否須要建立索引

下面列出一些基本的斷定策略來幫助咱們分析是否須要建立索引。

6-1.較頻繁的做爲查詢條件的字段應該建立索引;

提升數據查詢檢索的效率最有效的辦法就是減小須要訪問的數據量,從上面所瞭解到的索引的益處中咱們知道了,索引正是咱們減小經過索引鍵字段做爲查詢條件的Query 的IO 量的最有效手段。因此通常來講咱們應該爲較爲頻繁的查詢條件字段建立索引。

6-2.惟一性太差的字段不適合單首創建索引,即便頻繁做爲查詢條件;

惟一性太差的字段主要是指哪些呢?如狀態字段,類型字段等等這些字段中存方的數據可能總共就是那麼幾個幾十個值重複使用,每一個值都會存在於成千上萬或是更多的記錄中。對於這類字段,咱們徹底沒有必要建立單獨的索引的。由於即便咱們建立了索引,MySQL Query Optimizer 大多數時候也不會去選擇使用,若是何時MySQL Query Optimizer 抽了一下風選擇了這種索引,那麼很是遺憾的告訴你,這可能會帶來極大的性能問題。因爲索引字段中每一個值都含有大量的記錄,那麼存儲引擎在根據索引訪問數據的時候會帶來大量的隨機IO,甚至有些時候可能還會出現大量的重複IO。

6-3.更新很是頻繁的字段不適合建立索引;

上面在索引的弊端中咱們已經分析過了,索引中的字段被更新的時候,不只僅須要更新表中的數據,同時還要更新索引數據,以確保索引信息是準確的。這個問題所帶來的是IO 訪問量的較大增長,不只僅影響更新Query 的響應時間,還會影響整個存儲系統的資源消耗,加大整個存儲系統的負載。

那如何定義「很是頻繁」呢?每秒,每分鐘,仍是每小時呢?說實話,這個還真挺難定義的。不少時候仍是經過比較同一時間段內被更新的次數和利用該字段做爲條件的查詢次數來判斷。

6-4.不會出如今WHERE 子句中的字段不應建立索引;

不會還有人會問爲何吧?本身也以爲這是廢話了,哈哈!

7.單鍵索引仍是組合索引

對於這個問題,很難有一個絕對的定論,咱們須要從多方面來分析考慮,平衡兩種方案各自的優劣,而後選擇一種最佳的方案來解決。由於從上一節中咱們瞭解到了索引在提升某些查詢的性能的同時,也會讓某些更新的效率降低。而組合索引中由於有多個字段的存在,理論上被更新的可能性確定比單鍵索引要大不少,這樣可能帶來的附加成本也就比單鍵索引要高。可是,當咱們的WHERE 子句中的查詢條件含有多個字段的時候,經過這多個字段共同組成的組合索引的查詢效率確定比僅僅只用過濾條件中的某一個字段建立的索引要高。由於經過單鍵索引所能過濾的數據並不完整,和經過組合索引相比,存儲引擎須要訪問更多的記錄數,天然就會訪問更多的數據量,也就是說須要更高的IO 成本。

可能有些朋友會說,那咱們能夠經過建立多個單鍵索引啊。確實,咱們能夠將WHERE 子句中的每個字段都建立一個單鍵索引。可是這樣真的有效嗎?在這樣的狀況下,MySQL Query Optimizer 大多數時候都只會選擇其中的一個索引,而後放棄其餘的索引。即便他選擇了同時利用兩個或者更多的索引經過INDEX_MERGE 來優化查詢,可能所收到的效果並不會比選擇其中某一個單鍵索引更高效。由於若是選擇經過INDEX_MERGE 來優化查詢,就須要訪問多個索引,同時還要將經過訪問到的幾個索引進行merge操做,所帶來的成本可能反而會比選擇其中一個最有效的索引來完成查詢更高。

在通常的應用場景中,只要不是其中某個過濾字段在大多數場景下都能過濾出90%以上的數據,並且其餘的過濾字段會存在頻繁的更新,我通常更傾向於建立組合索引尤爲是在併發量較高的場景下更是應該如此。由於當咱們的併發量較高的時候,即便咱們爲每一個Query 節省不多的IO 消耗,但由於執行量很是大,所節省的資源總量仍然是很是可觀的。

固然,咱們建立組合索引並非說就須要將查詢條件中的全部字段都放在一個索引中,咱們還應該儘可能讓一個索引被多個Query 語句所利用儘可能減小同一個表上面索引的數量,減小由於數據更新所帶來的索引更新成本,同時還能夠減小由於索引所消耗的存儲空間。

此外,MySQL 還爲咱們提供了一個減小優化索引自身的功能,那就是前綴索引。在MySQL 中,咱們能夠僅僅使用某個字段的前面部份內容作爲索引鍵來索引該字段,來達到減少索引佔用的存儲空間和提升索引訪問的效率。固然,前綴索引的功能僅僅適用於字段前綴比較隨機重複性很小的字段。若是咱們須要索引的字段的前綴內容有較多的重複,索引的過濾性天然也會隨之下降,經過索引所訪問的數據量就會增長,這時候前綴索引雖然可以減小存儲空間消耗,可是可能會形成Query 訪問效率的極大下降,反而得不償失。

8.Query 的索引選擇

在有些場景下,咱們的Query 因爲存在多個過濾條件,而這多個過濾條件可能會存在於兩個或者更多的索引中。在這種場景下,MySQL Query Optimizer 通常狀況下都可以根據系統的統計信息選擇出一個針對該Query 最優的索引完成查詢,可是在有些狀況下,多是因爲咱們的系通通計信息的不夠準確完整,也多是MySQL Query Optimizer 自身功能的缺陷,會形成他並無選擇一個真正最優的索引而選擇了其餘查詢效率較低的索引。在這種時候,咱們就不得不經過人爲干預,在Query 中增長Hint 提示MySQL Query Optimizer 告訴他該使用哪一個索引而不應使用哪一個索引,或者經過調整查詢條件來達到相同的目的

SELECT * FROM group_message force index(group_message_author_subject) WHERE author = '3' subject LIKE 'weiurazs%'

下面是我對於選擇合適索引的幾點建議,並不必定在任何場景下都合適,但在大多數場景下仍是比較適用的。

1. 對於單鍵索引,儘可能選擇針對當前Query 過濾性更好的索引;

2. 在選擇組合索引的時候,當前Query 中過濾性最好的字段在索引字段順序中排列越靠前越好

3. 在選擇組合索引的時候,儘可能選擇能夠可以包含當前Query 的WHERE 子句中更多字段的索引

4. 儘量經過分析統計信息和調整Query 的寫法來達到選擇合適索引的目的減小經過使用Hint 人爲控制索引的選擇,由於這會使後期的維護成本增長,同時增長維護所帶來的潛在風險。

5. 只要列中包含有NULL值都將不會被包含在索引中,複合索引中只要有一列含有NULL值,那麼這一列對於此複合索引就是無效的。因此咱們在數據庫設計時不要讓字段的默認值爲NULL。

9.MySQL 中索引的限制

在使用索引的同時,咱們還應該瞭解在MySQL 中索引存在的限制,以便在索引應用中儘量的避開限制所帶來的問題。下面列出了目前MySQL 中索引使用相關的限制。

1. MyISAM 存儲引擎索引鍵長度總和不能超過1000 字節;

2. BLOB 和TEXT 類型的列只能建立前綴索引;

3. MySQL 目前不支持函數索引;

4. 使用不等於(!= 或者<>)的時候MySQL 沒法使用索引;

5. 過濾字段使用了函數運算後(如abs(column)),MySQL 沒法使用索引;

6. Join 語句中Join 條件字段類型不一致的時候MySQL 沒法使用索引;

7. 使用LIKE 操做的時候若是條件以通配符開始( '%abc...')MySQL 沒法使用索引;

8. 使用非等值查詢的時候MySQL 沒法使用Hash 索引;

在咱們使用索引的時候,須要注意上面的這些限制,尤爲是要注意沒法使用索引的狀況,由於這很容易讓咱們由於疏忽而形成極大的性能隱患。

10.索引的一些使用技巧

1.只要列中包含有NULL值都將不會被包含在索引中,複合索引中只要有一列含有NULL值,那麼這一列對於此複合索引就是無效的。因此咱們在數據庫設計時不要讓字段的默認值爲NULL。

2.對串列進行索引,若是可能應該指定一個前綴長度。例如,若是有一個CHAR(255)的列,若是在前10個或20個字符內,多數值是唯一的,那麼就不要對整個列進行索引。短索引不只能夠提升查詢速度並且能夠節省磁盤空間和I/O操做。

3.MySQL查詢只使用一個索引,所以若是where子句中已經使用了索引的話,那麼order by中的列是不會使用索引的。所以數據庫默認排序能夠符合要求的狀況下不要使用排序操做;儘可能不要包含多個列的排序,若是須要最好給這些列建立複合索引。

4.通常狀況下不鼓勵使用like操做,若是非使用不可,如何使用也是一個問題。like 「%aaa」 不會使用索引而like 「aaa%」可使用索引。

12)Join 的實現原理及優化思路

在MySQL 中,只有一種Join 算法,就是大名鼎鼎的Nested Loop Join。顧名思義,Nested Loop Join 實際上就是經過驅動表的結果集做爲循環基礎數據,而後一條一條的經過該結果集中的數據做爲過濾條件到下一個表中查詢數據,而後合併結果。若是還有第三個參與Join,則再經過前兩個表的Join 結果集做爲循環基礎數據,再一次經過循環查詢條件到第三個表中查詢數據,如此往復。

三表Join示例

select m.subject msg_subject, c.content msg_content from user_group g,group_message m,group_message_content c where g.user_id = 1
and m.group_id = g.group_id and c.group_msg_id = m.id

爲group_message 表增長了一個group_id 的索引:

create index idx_group_message_gid_uid on group_message(group_id);

查看Query 的執行計劃:

sky@localhost : example 11:17:04> explain select m.subject msg_subject, c.content msg_content -> from user_group g,group_message m,group_message_content c -> where g.user_id = 1
-> and m.group_id = g.group_id -> and c.group_msg_id = m.id\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: g type: ref possible_keys: user_group_gid_ind,user_group_uid_ind,user_group_gid_uid_ind key: user_group_uid_ind key_len: 4 ref: const rows: 2 Extra: *************************** 2. row *************************** id: 1 select_type: SIMPLE table: m type: ref possible_keys: PRIMARY,idx_group_message_gid_uid key: idx_group_message_gid_uid key_len: 4 ref: example.g.group_id rows: 3 Extra: *************************** 3. row *************************** id: 1 select_type: SIMPLE table: c type: ref possible_keys: idx_group_message_content_msg_id key: idx_group_message_content_msg_id key_len: 4 ref: example.m.id rows: 2 Extra:

這個過程能夠經過以下表達式來表示:

for each record g_rec in table user_group that g_rec.user_id=1{ for each record m_rec in group_message that m_rec.group_id=g_rec.group_id{ for each record c_rec in group_message_content that c_rec.group_msg_id=m_rec.id pass the (g_rec.user_id, m_rec.subject, c_rec.content) row combination to output; } }

Join 語句的優化

1. 儘量減小Join 語句中的Nested Loop 的循環總次數;

如何減小Nested Loop 的循環總次數?最有效的辦法只有一個,那就是讓驅動表的結果集儘量的小(★★★★★需牢記,這是第一核心★★★★★)

2. 優先優化Nested Loop 的內層循環;

3.保證Join 語句中被驅動表上Join 條件字段已經被索引;

4. 當沒法保證被驅動表的Join 條件字段被索引且內存資源充足的前提下,不要太吝惜Join Buffer 的設置;

13)ORDER BY,GROUP BY 和 DI STI NCT 優化

ORDER BY,GROUP BY 以及DISTINCT 這三類查詢。考慮到這三類查詢都涉及到數據的排序等操做,因此我將他們放在了一塊兒。

ORDER BY 的實現與優化

在MySQL 中,ORDER BY 的實現有以下兩種類型:
◆ 一種是經過有序索引而直接取得有序的數據,這樣不用進行任何排序操做便可獲得知足客戶端要求的有序數據返回給客戶端;
◆ 另一種則須要經過MySQL 的排序算法將存儲引擎中返回的數據進行排序而後再將排序後的數據返回給客戶端。

1. 加大max_length_for_sort_data 參數的設置;

2. 去掉沒必要要的返回字段;

3. 增大sort_buffer_size 參數設置;

GROUP BY 的實現與優化

在MySQL 中,GROUP BY 的實現一樣有多種(三種)方式,其中有兩種方式會利用現有的索引信息來完成GROUP BY,另一種爲徹底沒法使用索引的場景下使用。下面咱們分別針對這三種實現方式作一個分析。

1. 使用鬆散(Loose)索引掃描實現GROUP BY

何謂鬆散索引掃描實現GROUP BY 呢?實際上就是當MySQL 徹底利用索引掃描來實現GROUP BY 的時候,並不須要掃描全部知足條件的索引鍵便可完成操做得出結果。

2. 使用緊湊(Tight)索引掃描實現GROUP BY

緊湊索引掃描實現GROUP BY 和鬆散索引掃描的區別主要在於他須要在掃描索引的時候,讀取全部知足條件的索引鍵,而後再根據讀取的數據來完成GROUP BY 操做獲得相應結果

3. 使用臨時表實現GROUP BY

MySQL 在進行GROUP BY 操做的時候要想利用全部,必須知足GROUP BY 的字段必須同時存放於同一個索引中,且該索引是一個有序索引(如Hash 索引就不能知足要求)。並且,並不僅是如此,是否可以利用索引來實現GROUP BY 還與使用的聚合函數也有關係。

對於上面三種MySQL 處理GROUP BY 的方式,咱們能夠針對性的得出以下兩種優化思路

1. 儘量讓MySQL 能夠利用索引來完成GROUP BY 操做,固然最好是鬆散索引掃描的方式最佳。在系統容許的狀況下,咱們能夠經過調整索引或者調整Query 這兩種方式來達到目的;

2. 當沒法使用索引完成GROUP BY 的時候,因爲要使用到臨時表且須要filesort,因此咱們必需要有足夠的sort_buffer_size 來供MySQL 排序的時候使用,並且儘可能不要進行大結果集的GROUP BY 操做,由於若是超出系統設置的臨時表大小的時候會出現將臨時表數據copy 到磁盤上面再進行操做,這時候的排序分組操做性能將是成數量級的降低;

更多參閱:MySQL鬆散索引掃描與緊湊索引掃描

DISTINCT 的實現與優化

DISTINCT 實際上和GROUP BY 的操做很是類似,只不過是在GROUP BY 以後的每組中只取出一條記錄而已。

相關文章
相關標籤/搜索