1. 程式人生 > >由淺入深探究mysql索引結構原理、效能分析與優化

由淺入深探究mysql索引結構原理、效能分析與優化

第一部分:基礎知識

第二部分:MYISAMINNODB索引結構

1、 簡單介紹B-tree B+ tree

2、 MyisAM索引結構

3、 Annode索引結構

4、 MyisAM索引與InnoDB索引相比較

第三部分:MYSQL優化

1、表資料型別選擇

2、sql語句優化

(1)     最左字首原則

(1.1)  能正確的利用索引

(1.2)  不能正確的利用索引

(1.3)  如果一個查詢where子句中確實不需要password列,那就用“補洞”。

(1.4)  like

(2)     Order by 優化

(2.1) filesort優化演算法.

(2.2) 單獨order by 

用不了索引,索引考慮加where 或加limit

(2.3) where + orerby 型別,where滿足最左字首原則,且orderby的列和where子句用到的索引的列的子集。即是(a,b,c)索引,where滿足最左字首原則且order by中列abc的任意組合

(2.4) where + orerby+limit

(2.5) 如何考慮order by來建索引

(3)     隔離列

(4)     ORINUNION ALL,可以嘗試用UNION ALL

(4.1) or會遍歷表就算有索引

(4.2)關於in

(4.2) UNION All

(5)     範索引選擇性

(6)     重複或多餘索引

3、系統配置與維護優化

(1)     重要的一些變數

(2)     Fds optimizeAnalyzecheckrepair維護操作

(3)     表結構的更新與維護

第四部分:圖說mysql查詢執行流程

第一部分:基礎知識:

索引

官方介紹索引是幫助MySQL高效獲取資料的資料結構。筆者理解索引相當於一本書的目錄,通過目錄就知道要的資料在哪裡,不用一頁一頁查閱找出需要的資料。關鍵字index

-------------------------------------------------------------

唯一索引

強調唯一,就是索引值必須唯一,關鍵字

unique index

建立索引:

1create unique index 索引名 on 表名(列名);

2alter table 表名 add unique index 索引名 (列名);

刪除索引:

1、  drop index 索引名 on 表名;

2、  alter table 表名 drop index 索引名;

-------------------------------------------------------------

主鍵

主鍵就是唯一索引的一種,主鍵要求建表時指定,一般用auto_increatment列,關鍵字是primary key

主鍵建立:

creat table test2 (id int not null primary key auto_increment);

-------------------------------------------------------------

全文索引

InnoDB不支援,Myisam支援效能比較好,一般在 CHARVARCHAR  TEXT 列上建立。

Create table 表名( id int not null primary anto_increment,title

varchar(100),FULLTEXT(title))type=myisam

------------------------------

單列索引與多列索引

索引可以是單列索引也可以是多列索引(也叫複合索引)。按照上面形式創建出來的索引是單列索引,現在先看看建立多列索引:

create table test3 (id int not null primary key auto_increment,uname char

(8) not null default '',password char(12) not null,INDEX(uname,password))type

=myisam;

注意:INDEX(a, b, c)可以當做a(a, b)的索引來使用,但和bc(b,c)的索引來使用這是一個最左字首的優化方法,在後面會有詳細的介紹,你只要知道有這樣兩個概念

-------------------------------------------------------------

聚集索引

一種索引,該索引中鍵值的邏輯順序決定了表中相應行的物理順序。聚集索引確定表中資料的物理順序。Mysqlmyisam表是沒有聚集索引的,innodb有(主鍵就是聚集索引),聚集索引在下面介紹innodb結構的時有詳細介紹。

-------------------------------------------------------------

查看錶的索引

通過命令:Show index from 表名

如:

  1. mysql> show index from test3;  
  2. +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+----+
  3. Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | 
  4. Packed | Null | Index_type | Comment |  
  5. +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+----+
  6. | test3 |          0 | PRIMARY  |        1  |    id          |     A     |   0          |     NULL | 
  7. NULL   |     | BTREE      |         |  
  8. +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+

 Table:表名

Key_name:什麼型別索引(這了是主鍵)

Column_name:索引列的欄位名

Cardinality:索引基數,很關鍵的一個引數,平均數值組=索引基數/表總資料行,平均數值組越接近1就越有可能利用索引

Index_type:如果索引是全文索引,則是fulltext,這裡是b+tree索引,b+tre也是這篇文章研究的重點之一

其他的就不詳細介紹,更多:

第二部分:MYISAMINNODB索引結構

1、 簡單介紹B-tree B+ tree

B-tree結構檢視

一棵m階的B-tree樹,則有以下性質

1Ki表示關鍵字值,上圖中,k1<k2<…<ki<k0<Kn(可以看出,一個節點的左子節點關鍵字值<該關鍵字值<右子節點關鍵字值)

2Pi表示指向子節點的指標,左指標指向左子節點,右指標指向右子節點。即是:p1[指向值]<k1<p2[指向值]<k2……

3)所有關鍵字必須唯一值(這也是建立myisam innodb表必須要主鍵的原因),每個節點包含一個說明該節點多少個關鍵字,如上圖第二行的in

4)節點:

l  每個節點最可以有m個子節點。

l  根節點若非葉子節點,至少2個子節點,最多m個子節點

l  每個非根,非葉子節點至少[m/2]子節點或叫子樹([]表示向上取整),最多m個子節點

5)關鍵字:

l  根節點的關鍵字個數1~m-1

l  非根非葉子節點的關鍵字個數[m/2]-1~m-1,m=3,則該類節點關鍵字個數:2-1~2

6)關鍵字數k和指向子節點個數指標p的關係:

l  k+1=p ,注意根據儲存資料的具體需求,左右指標為空時要有標誌位表示沒有

  B+tree結構示意圖如下:

B+樹是B-樹的變體,也是一種多路搜尋樹:

l  非葉子結點的子樹指標與關鍵字個數相同

l  為所有葉子結點增加一個鏈指標(紅點標誌的箭頭)

B+樹是B-樹的變體,也是一種多路搜尋樹:

l  非葉子結點的子樹指標與關鍵字個數相同

l  為所有葉子結點增加一個鏈指標(紅點標誌的箭頭)

2、 MyisAM索引結構

MyisAM索引用的B+tree來儲存資料,MyisAM索引的指標指向的是鍵值的地址,地址儲存的是資料,如下圖:

(1)結構講解:上圖3階樹,主鍵是Col2,Col值就是改行資料儲存的實體地址,其中紅色部分是說明標註。

l  1標註部分也許會迷惑,前面不是說關鍵字15右指標的指向鍵值要大於15,怎麼下面還有15關鍵字?因為B+tree的所以葉子節點包含所有關鍵字且是按照升序排列(主鍵索引唯一,輔助索引可以不唯一),所以等於關鍵字的資料值在右子樹

l  2標註是相應關鍵字儲存對應資料的實體地址,注意這也是之後和InnoDB索引不同的地方之一

l  2標註也是一個所說MyiAM表的索引和資料是分離的,索引儲存在”表名.MYI”檔案內,而資料儲存在“表名.MYD”檔案內,2標註的實體地址就是“表名.MYD”檔案內相應資料的實體地址。(InnoDB表的索引檔案和資料檔案在一起)

l  輔助索引和主鍵索引沒什麼大的區別,輔助索引的索引值是可以重複的(但InnoDB輔助索引和主鍵索引有很明顯的區別,這裡先提醒注意一下)

3、 Annode索引結構

1)首先有一個表,內容和主鍵索引結構如下兩圖:

Col1

Col2

Col3

1

15

phpben

2

20

mhycoe

3

23

phpyu

4

25

bearpa

5

40

phpgoo

6

45

phphao

7

48

phpxue

……

結構上:由上圖可以看出InnoDB的索引結構很MyisAM的有很明顯的區別

 l  MyisAM表的索引和資料是分開的,用指標指向資料的實體地址,而InnoDB表中索引和資料是儲存在一起。看紅框1可一看出一行資料都儲存了。

 l  還有一個上圖多了三行的隱藏資料列(虛線表),這是因為MyisAM不支援事務,InnoDB處理事務在效能上併發控制上比較好,看圖中的紅框2中的DB_TRX_ID是事務ID,自動增長;db_roll_ptr是回滾指標,用於事務出錯時資料回滾恢復;db_row_id是記錄行號,這個值其實在主鍵索引中就是主鍵值,這裡標出重複是為了容易介紹,還有的是若不是主鍵索引(輔助索引),db_row_id會找表中unique的列作為值,若沒有unique列則系統自動建立一個。關於InnoDB跟多事務MVCC點此:http://www.phpben.com/?post=72 

2)加入上表中Col1是主鍵(下圖標錯),而Col2是輔助索引,則相應的輔助索引結構圖:

可以看出InnoDB輔助索引並沒有儲存相應的所有列資料,而是儲存了主鍵的鍵值(圖中123….)這樣做利弊也是很明顯:

l  在已有主鍵索引,避免資料冗餘,同時在修改資料的時候只需修改輔助索引值。

l  但輔助索引查詢資料事要檢索兩次,先找到相應的主鍵索引值然後在去檢索主鍵索引找到對應的資料。這也是網上很多mysql效能優化時提到的“主鍵儘可能簡短”的原因,主鍵越長輔助索引也就越大,當然主鍵索引也越大。

4、 MyisAM索引與InnoDB索引相比較

MyisAM支援全文索引(FULLTEXT)、壓縮索引,InnoDB不支援

AnnoDB支援事務,MyisAM不支援

MyisAM順序儲存資料,索引葉子節點儲存對應資料行地址,輔助索引很主鍵索引相差無幾;AnnoDB主鍵節點同時儲存資料行,其他輔助索引儲存的是主鍵索引的值

MyisAM鍵值分離,索引載入記憶體(key_buffer_size,資料快取依賴作業系統;InnoDB鍵值一起儲存,索引與資料一起載入InnoDB緩衝池

MyisAM主鍵(唯一)索引按升序來儲存儲存,InnoDB則不一定

MyisAM索引的基數值(Cardinalityshow index 命令可以看見)是精確的,InnoDB則是估計值。這裡涉及到資訊統計的知識,MyisAM統計資訊是儲存磁碟中,在alter表或Analyze table操作更新此資訊,而InnoDB則是在表第一次開啟的時候估計值儲存在快取區內

MyisAM處理字串索引時用增量儲存的方式,如第一個索引是‘preform’,第二個是‘preformence’,則第二個儲存是‘7ance‘,這個明顯的好處是縮短索引,但是缺陷就是不支援倒序提取索引,必須順序遍歷獲取索引

第三部分:MYSQL優化

mysql優化是一個重大課題之一,這裡會重點詳細的介紹mysql優化,包括表資料型別選擇,sql語句優化,系統配置與維護優化三類。

1、  表資料型別選擇

(1) 能小就用小。表資料型別第一個原則是:使用能正確的表示和儲存資料的最短型別。這樣可以減少對磁碟空間、記憶體、cpu快取的使用。

(2)避免用NULL,這個也是網上優化技術博文傳的最多的一個。理由是額外增加位元組,還有使索引,索引統計和值更復雜。很多還忽略一

     個count()的問題,count()是不會統計列值為null的行數。更多關於NULL可參考:http://www.phpben.com/?post=71

(3) 字串如何選擇char和varchar?一般phper能想到就是char是固定大小,varchar能動態儲存資料。這裡整理一下這兩者的區別:

屬性

Char

Varchar

值域大小

最長字元數255(不是位元組),不管什麼編碼,超過此值則自動擷取255個字元儲存並沒有報錯。

65535個位元組,開始兩位儲存長度,超過255個字元,用2位儲存長度,否則1位,具體字元長度根據編碼來確定,如utf8

則字元最長是21845

如何處理字串末尾空格

去掉末尾空格,取值出來比較的時候自動加上進行比較

Version<=4.1,字串末尾空格被刪掉,version>5.0則保留

儲存空間

固定空間,比喻char(10)不管字串是否有10個字元都分配10個字元的空間

Varchar內節約空間,但更新可能發生變化,若varchar(10),開始若儲存5個字元,當update7個時有myisam可能把行拆開,innodb可能分頁,這樣開銷就增大

適用場合

適用於儲存很短或固定或長度相似字元,如MD5加密的密碼char(33)、暱稱char(8)

當最大長度遠大於平均長度並且發生更新的時候。

注意當一些英文或資料的時候,最好用每個字元用位元組少的型別,如latin1

(4) 整型、整形優先原則

Tinyintsmallintmediumintintbigint,分別需要816243264

值域範圍:-2^(n-1)~ 2^(n-1)-1

很多程式設計師在設計資料表的時候很習慣的用int,壓根不考慮這個問題

筆者建議:能用tinyint的絕不用smallint

誤區int(1) int(11)是一樣的,唯一區別是mysql客戶端顯示的時候顯示多少位。

整形優先原則:能用整形的不用其他型別替換,如ip可以轉換成整形儲存,如商品價格‘50.00元’則儲存成50

(5)精確度與空間的轉換。在儲存相同數值範圍的資料時,浮點數型別通常都會比DECIMAL型別使用更少的空間。FLOAT欄位使用4位元組儲存

 資料。DOUBLE型別需要個位元組並擁有更高的精確度和更大的數值範圍,DECIMAL型別的資料將會轉換成DOUBLE型別。

2、  sql語句優化  

  1. mysql> create table one (
  2. id smallint(10) not null auto_increment primary key,  
  3. username char(8) not null,  
  4. password char(4) not null,  
  5. `level` tinyint (1) default 0,  
  6. last_login char(15) not null,  
  7. index(username,password,last_login))engine=innodb;  

這是test表,其中id是主鍵,多列索引(username,password,last_login),裡面有10000多條資料.

  1. Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null |
  2.  Index_type | Comment |  
  3. +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+
  4. | one   |        0 | PRIMARY  |           1 | id          | A         |20242 |  NULL | NULL  |    |
  5.  BTREE     |         |  
  6. +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+
  7. | one   |        1 | username |            1 | username    | A         |10121 |  NULL | NULL  |     | 
  8. BTREE     |         |  
  9. +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+
  10. | one   |        1 | username |            2 | password    | A         |10121 |  NULL | NULL  | YES  |
  11.  BTREE     |         |  
  12. +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+
  13. | one   |        1 | username |              3 | last_login  | A         |20242 |  NULL | NULL  |     |
  14.  BTREE      |         |  
  15. +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+

(1)    最左字首原則

定義:最左字首原則指的的是在sql where 字句中一些條件或表示式中出現的列的順序要保持和多索引的一致或以多列索引順序出現,只要出現非順序出現、斷層都無法利用到多列索引。

舉例說明:上面給出一個多列索引(username,password,last_login),當三列在where中出現的順序如(username,password,last_login)(username,password)(username)才能用到索引,如下面幾個順序(password,last_login)(passwrod)(last_login)---這三者不從username開始,(username,last_login)---斷層,少了password,都無法利用到索引。

因為B+tree多列索引儲存的順序是按照索引建立的順序,檢索索引時按照此順序檢索

測試:以下測試不精確,這裡只是說明如何才能正確按照最左字首原則使用索引。還有的是以下的測試用的時間0.00sec看不出什麼時間區別,因為資料量只有20003條,加上沒有在實體機上執行,很多未可預知的影響因素都沒考慮進去。當在大資料量,高併發的時候,最左字首原則對與提高效能方面是不可否認的。

Ps:最左字首原則中where字句有or出現還是會遍歷全表

(1.1)能正確的利用索引

l  Where子句表示式順序是(username)

  1. mysql> explain select * from one where username='abgvwfnt';  
  2. +----+-------------+-------+------+---------------+----------+---------+-------+------+-------------+  
  3. | id | select_type | table | type | possible_keys | key      | key_len | ref   |rows | Extra       |  
  4. +----+-------------+-------+------+---------------+----------+---------+-------+------+-------------+  
  5. |  1 | SIMPLE      | one   | ref  | username      | username | 24      | const |5 | Using where |  
  6. +----+-------------+-------+------+---------------+----------+---------+-------+------+-------------+  
  7. 1 row in set (0.00 sec)  

l  Where子句表示式順序是(username,password)

  1. mysql> explain select * from one where username='abgvwfnt' and password='123456';  
  2. +----+-------------+-------+------+---------------+----------+---------+-------------+------+-------------+  
  3. | id | select_type | table | type | possible_keys | key      | key_len | ref | rows | Extra       |  
  4. +----+-------------+-------+------+---------------+----------+---------+-------------+------+-------------+  
  5. |  1 | SIMPLE      | one   | ref  | username      | username | 43      | const,const |    1 | Using where |  
  6. +----+-------------+-------+------+---------------+----------+---------+-------------+------+-------------+  
  7. 1 row in set (0.00 sec)  

l  Where子句表示式順序是(username,password, last_login)

  1. mysql> explain select * from one where username='abgvwfnt' and password='123456'and last_login='1338251170';  
  2. +----+-------------+-------+------+---------------+----------+---------+-------------------+------+-------------+  
  3. | id | select_type | table | type | possible_keys | key      | key_len | ref| rows | Extra       |  
  4. +----+-------------+-------+------+---------------+----------+---------+-------------------+------+-------------+  
  5. |  1 | SIMPLE   | one   | ref  | username     | username | 83      | const,const,const |    1 | Using where |  
  6. +----+-------------+-------+------+---------------+----------+---------+-------------------+------+-------------+  
  7. 1 row in set (0.00 sec)  

上面可以看出type=ref 是多列索引,key_len分別是244383,這說明用到的索引分別是(username), (username,password), (username,password, last_login );row分別是511檢索的資料行都很少,因為這三個查詢都按照索引字首原則,可以利用到索引。

(1.2)不能正確的利用索引

l  Where子句表示式順序是(password, last_login)

  1. mysql> explain select * from one where password='123456'and last_login='1338251170';  
  2. +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+  
  3. | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows| Extra       |  
  4. +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+  
  5. |  1 | SIMPLE      | one   | ALL  | NULL          | NULL | NULL    | NULL | 20146 | Using where |  
  6. +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+  
  7. 1 row in set (0.00 sec)  

l  Where 子句表示式順序是(last_login)

  1. mysql> explain select * from one where last_login='1338252525';  
  2. +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+  
  3. | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows| Extra       |  
  4. +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+  
  5. |  1 | SIMPLE      | one   | ALL  | NULL          | NULL | NULL    | NULL | 20146 | Using where |  
  6. +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+  
  7. 1 row in set (0.00 sec)  

以上的兩條語句都不是以username開始,這樣是用不了索引,通過type=all(全表掃描),key_len=nullrows都很大20146

Psone表裡只有20003條資料,為什麼出現20146,這是優化器對錶的一個估算值,不精確的。

l  Where 子句表示式雖然順序是(username,password, last_login)