mysql索引結構原理、性能分析與優化

原文  http://wulijun.github.com/2012/08/21/mysql-index-implementation-and-optimization.htmlphp

第一部分:基礎知識

索引

官方介紹索引是幫助MySQL高效獲取數據的數據結構。筆者理解索引至關於一本書的目錄,經過目錄就知道要的資料在哪裏, 不用一頁一頁查閱找出須要的資料。html

惟一索引(unique index)

強調惟一,就是索引值必須惟一。node

建立索引:mysql

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

刪除索引:git

drop index 索引名 on 表名;alter table 表名 drop index 索引名;

主鍵

主鍵就是惟一索引的一種,主鍵要求建表時指定,通常用auto_increment列,關鍵字是primary key程序員

主鍵建立:github

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

全文索引

InnoDB不支持,MyISAM支持性能比較好,通常在 CHAR、VARCHAR 或 TEXT 列上建立。算法

Create table 表名( 
    id int not null primary key anto_increment,
    title varchar(100),FULLTEXT(title)
)type=MyISAM;

單列索引與多列索引

索引能夠是單列索引也能夠是多列索引(也叫複合索引)。按照上面形式建立出來的索引是單列索引,如今先看看建立多列索引:sql

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)的索引來使用,但不能看成b、c或(b,c)的索引來使用。這是一個最左前綴的 優化方法,在後面會有詳細的介紹,你只要知道有這樣兩個概念。數據庫

聚簇索引

一種索引,該索引中鍵值的邏輯順序決定了表中相應行的物理順序。 聚簇索引肯定表中數據的物理順序。Mysql中MyISAM 表是沒有聚簇索引的,innodb有(主鍵就是聚簇索引),聚簇索引在下面介紹innodb結構的時有詳細介紹。

查看錶的索引

經過命令:Show index from 表名 如:

mysql> show index from test3;  +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+----+| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | 
Packed | Null | Index_type | Comment |  
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+----+| test3 |          0 | PRIMARY  |        1  |    id          |     A     |   0          |     NULL | 
NULL   |     | BTREE      |         |  
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+Table:表名
Key_name:什麼類型索引(這裏是主鍵)
Column_name:索引列的字段名
Cardinality:索引基數,很關鍵的一個參數,平均數值組=索引基數/表總數據行,平均數值組越接近1就越有可能利用索引
Index_type:若是索引是全文索引,則是fulltext,這裏是b+tree索引,b+tree也是這篇文章研究的重點之一

第二部分:MyISAM和INNODB索引結構

簡單介紹B-tree B+ tree樹

B-tree結構視圖B-tree結構視圖

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

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

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

  3. 全部關鍵字必須惟一值(這也是建立MyISAM 和innodb表必需要主鍵的緣由),每一個節點包含一個說明該節點多少個關鍵字,如上圖第二行的i和n

  4. 節點:

    • 每一個節點最能夠有m個子節點。

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

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

  5. 關鍵字:

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

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

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

    • k+1=p ,注意根據儲存數據的具體需求,左右指針爲空時要有標誌位表示沒有 B+tree結構示意圖以下:B+tree結構示意圖

B+樹是B-樹的變體,也是一種多路搜索樹: * 非葉子結點的子樹指針與關鍵字個數相同 * 爲全部葉子結點增長一個鏈指針(紅點標誌的箭頭)

MyISAM索引結構

MyISAM索引用的B+ tree來儲存數據,MyISAM索引的指針指向的是鍵值的地址,地址存儲的是數據,以下圖:

MyISAM索引用的B+ tree

結構講解:上圖3階樹,主鍵是Col2,Col值就是改行數據保存的物理地址,其中紅色部分是說明標註。

  • 1標註部分也許會迷惑,前面不是說關鍵字15右指針的指向鍵值要大於15,怎麼下面還有15關鍵字?由於B+tree的全部葉子節點 包含全部關鍵字且是按照升序排列(主鍵索引惟一,輔助索引能夠不惟一),因此等於關鍵字的數據值在右子樹

  • 2標註是相應關鍵字存儲對應數據的物理地址,注意這也是以後和InnoDB索引不一樣的地方之一

  • 2標註也是一個所說MyISAM表的索引和數據是分離的,索引保存在」表名.MYI」文件內,而數據保存在「表名.MYD」文件內,2標註 的物理地址就是「表名.MYD」文件內相應數據的物理地址。(InnoDB表的索引文件和數據文件在一塊兒)

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

Innode索引結構

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

結構上:由上圖能夠看出InnoDB的索引結構很MyISAM的有很明顯的區別

  • MyISAM表的索引和數據是分開的,用指針指向數據的物理地址,而InnoDB表中索引和數據是儲存在一塊兒。看紅框1可看出一行 數據都保存了。

  • 還有一個上圖多了三行的隱藏數據列(虛線表),這是由於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輔助索引並無保存相應的全部列數據,而是保存了主鍵的鍵值(圖中一、二、3….)這樣作利弊也是很明顯:

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

  • 但輔助索引查找數據事要檢索兩次,先找到相應的主鍵索引值而後在去檢索主鍵索引找到對應的數據。這也是網上不少 mysql性能優化時提到的「主鍵儘量簡短」的緣由,主鍵越長輔助索引也就越大,固然主鍵索引也越大。

MyISAM索引與InnoDB索引相比較

  • MyISAM支持全文索引(FULLTEXT)、壓縮索引,InnoDB不支持

  • InnoDB支持事務,MyISAM不支持

  • MyISAM順序儲存數據,索引葉子節點保存對應數據行地址,輔助索引很主鍵索引相差無幾;InnoDB主鍵節點同時保存數據行,其餘輔助索引保存的是主鍵索引的值

  • MyISAM鍵值分離,索引載入內存(key_buffer_size),數據緩存依賴操做系統;InnoDB鍵值一塊兒保存,索引與數據一塊兒載入InnoDB緩衝池

  • MyISAM主鍵(惟一)索引按升序來存儲存儲,InnoDB則不必定

  • MyISAM索引的基數值(Cardinality,show index 命令能夠看見)是精確的,InnoDB則是估計值。這裏涉及到信息統計的知識,MyISAM統計信息是保存磁盤中,在alter表或Analyze table操做更新此信息,而InnoDB則是在表第一次打開的時候估計值保存在緩存區內

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

第三部分:MYSQL優化

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

表數據類型選擇

  1. 能小就用小。表數據類型第一個原則是:使用能正確的表示和存儲數據的最短類型。這樣能夠減小對磁盤空間、內存、cpu緩存的使用。

  2. 避免用NULL,這個也是網上優化技術博文傳的最多的一個。理由是額外增長字節,還有使索引,索引統計和值更復雜。不少還忽略一 個count(列)的問題,count(列)是不會統計列值爲null的行數。更多關於NULL可參考: http://www.phpben.com/?post=71

  3. 字符串如何選擇char和varchar?通常phper能想到就是char是固定大小,varchar能動態儲存數據。這裏整理一下這二者的區別:

注意當一些英文或數據的時候,最好用每一個字符用字節少的類型,如latin1

  1. 整型、整形優先原則

    Tinyint、smallint、mediumint、int、bigint,分別須要八、1六、2四、3二、64。 
    值域範圍:-2 (n-1)~ 2 (n-1)-1 
    不少程序員在設計數據表的時候很習慣的用int,壓根不考慮這個問題 
    筆者建議:能用tinyint的毫不用smallint 
    誤區:int(1) 和int(11)是同樣的,惟一區別是mysql客戶端顯示的時候顯示多少位。 
    整形優先原則:能用整形的不用其餘類型替換,如ip能夠轉換成整形保存,如商品價格‘50.00元’則保存成50

  2. 精確度與空間的轉換。在存儲相同數值範圍的數據時,浮點數類型一般都會比DECIMAL類型使用更少的空間。FLOAT字段使用4 字節存儲 數據。DOUBLE類型須要8 個字節並擁有更高的精確度和更大的數值範圍,DECIMAL類型的數據將會轉換成DOUBLE類型。

sql語句優化

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

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

| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null |
 Index_type | Comment |  
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+
| one   |        0 | PRIMARY  |           1 | id          | A         |20242 |  NULL | NULL  |    |
 BTREE     |         |  
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+
| one   |        1 | username |            1 | username    | A         |10121 |  NULL | NULL  |     | 
BTREE     |         |  
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+
| one   |        1 | username |            2 | password    | A         |10121 |  NULL | NULL  | YES  |
 BTREE     |         |  
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+
| one   |        1 | username |              3 | last_login  | A         |20242 |  NULL | NULL  |     |
 BTREE      |         |  
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+

最左前綴原則

定義:最左前綴原則指的的是在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出現仍是會遍歷全表

能正確的利用索引
  • Where子句表達式 順序是(username)

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

  • Where子句表達式 順序是(username,password)

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

  • Where子句表達式 順序是(username,password, last_login)

    mysql> explain select * from one where username='abgvwfnt' and password='123456'and last_login='1338251170'; 
    +----+-------------+-------+------+---------------+----------+---------+-------------------+------+-------------+ 
    | id | select_type | table | type | possible_keys | key | key_len | ref| rows | Extra | 
    +----+-------------+-------+------+---------------+----------+---------+-------------------+------+-------------+ 
    | 1 | SIMPLE | one | ref | username | username | 83 | const,const,const | 1 | Using where | 
    +----+-------------+-------+------+---------------+----------+---------+-------------------+------+-------------+ 
    1 row in set (0.00 sec) 
    上面能夠看出type=ref 是多列索引,key_len分別是2四、4三、83,這說明用到的索引分別是(username), (username,password), (username,password, last_login );row分別是五、一、1檢索的數據行都不多,由於這三個查詢都按照索引前綴原則,能夠利用到 索引。

不能正確的利用索引
  • Where子句表達式 順序是(password, last_login)

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

  • Where 子句表達式順序是(last_login)

    mysql> explain select * from one where last_login='1338252525'; 
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows| Extra | 
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 
    | 1 | SIMPLE | one | ALL | NULL | NULL | NULL | NULL | 20146 | Using where | 
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 
    1 row in set (0.00 sec) 
    以上的兩條語句都不是以username開始,這樣是用不了索引,經過type=all(全表掃描),key_len=null,rows都很大20146 
    Ps:one表裏只有20003條數據,爲何出現20146,這是優化器對錶的一個估算值,不精確的。

  • Where 子句表達式雖然順序是(username,password, last_login)或(username,password)但第一個是有範圍’<’、’>’,’<=’, ’>=’等出現

    mysql> explain select * from one where username>'abgvwfnt' and password ='123456'and last_login='1338251170'; 
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows| Extra | 
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 
    | 1 | SIMPLE | one | ALL | username | NULL | NULL | NULL | 20146 | Using where | 
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 
    1 row in set (0.00 sec) 
    這個查詢很明顯是遍歷全部表,一個索引都沒用到,非第一列出現範圍(password列或last_login列),則能利用索引到首先出 現範圍的一列,也就是「where username='abgvwfnt' and password >'123456'and last_login='1338251170';」或 「where username='abgvwfnt' and password >'123456'and last_login<'1338251170';」索引長度ref_len=43,索引檢索到 password列,因此考慮多列索引的時候把那些查詢語句用的比較的列放在最後(或非第一位)。

  • 斷層,便是where順序(username, last_login)

    mysql> explain select * from one where username='abgvwfnt' and last_login='1338252525'; 
    +----+-------------+-------+------+---------------+----------+---------+-------+------+-------------+ 
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 
    +----+-------------+-------+------+---------------+----------+---------+-------+------+-------------+ 
    | 1 | SIMPLE | one | ref | username | username | 24 | const |5 | Using where | 
    +----+-------------+-------+------+---------------+----------+---------+-------+------+-------------+ 
    1 row in set (0.00 sec) 
    注意這裏的key_len=24=8*3(8是username的長度,3是utf8編碼),rows=5,和下面一條sql語句搜索出來同樣

  • mysql> select * from one where username='abgvwfnt'; 
    +-------+----------+----------+-------+------------+ 
    | id | username | password | level | last_login | 
    +-------+----------+----------+-------+------------+ 
    | 3597 | abgvwfnt | 234567 | 0 | 1338251420 | 
    | 7693 | abgvwfnt | 456789 | 0 | 1338251717 | 
    | 11789 | abgvwfnt | 456789 | 0 | 1338251992 | 
    | 15885 | abgvwfnt | 456789 | 0 | 1338252258 | 
    | 19981 | abgvwfnt | 456789 | 0 | 1338252525 | 
    +-------+----------+----------+-------+------------+ 
    5 rows in set (0.00 sec)

    mysql> select * from one where username='abgvwfnt' and last_login='1338252525'; 
    +-------+----------+----------+-------+------------+ 
    | id | username | password | level | last_login | 
    +-------+----------+----------+-------+------------+ 
    | 19981 | abgvwfnt | 456789 | 0 | 1338252525 | 
    +-------+----------+----------+-------+------------+ 
    1 row in set (0.00 sec) 
    這個就是要的返回結果,因此能夠知道斷層(username,last_login),這樣只用到username索引,把用到索引的數據再從新檢查 last_login條件,這個相對全表查詢來講仍是有性能上優化,這也是不少sql優化文章中提到的where 範圍查詢要放在最後 (這不絕對,但能夠利用一部分索引)

  • 若是一個查詢where子句中確實不須要password列,那就用「補洞」。

    mysql> select distinct(password) from one; 
    +----------+ 
    | password | 
    +----------+ 
    | 234567 | 
    | 345678 | 
    | 456789 | 
    | 123456 | 
    +----------+ 
    4 rows in set (0.08 sec)

    能夠看出password列中只有這幾個值,固然在現實中不可能密碼有這麼多同樣的,再說數據也可能不斷更新,這裏只是舉例說明補 洞的方法

    mysql> explain select * from one where username='abgvwfnt' and password in('123456','234567','345678','456789') and last_login='1338251170'; 
    +----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+ 
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 
    +----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+ 
    | 1 | SIMPLE | one | range | username | username| 83 | NULL |4 | Using where | 
    +----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+ 
    1 row in set (0.00 sec) 
    能夠看出ref=83 全部的索引都用到了,type=range是由於用了in子句。 這個被「補洞」列中的值應該是有限的,可預知的,如性別,其值只有男和女(加多一個不男不女也無妨)。 「補洞」方法也有瓶頸,當不少列,且須要補洞的相應列(能夠多列)的值雖有限但不少(如中國城市)的時候,優化器在優化時組合 起來的數量是很大,這樣的話就要作好基準測試和性能分析,權衡得失,取得一個合理的優化方法。

  • like

    mysql> explain select * from one where username like 'abgvwfnt%'; 
    +----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+ 
    | id | select_type | table | type | possible_keys | key | key_len | ref | 
    rows | Extra | 
    +----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+ 
    | 1 | SIMPLE | one | range | username | username | 24 | NULL | 5 | Using where | 
    +----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+ 
    1 row in set (0.00 sec)

    mysql> explain select * from one where username like '%abgvwfnt%'; 
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows| Extra | 
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 
    | 1 | SIMPLE | one | ALL | NULL | NULL | NULL | NULL | 20259 | Using where | 
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 
    1 row in set (0.01 sec) 
    對比就知道like操做abgvwfnt%能用到索引,%abgvwfnt%用不到

Order by 優化

  • filesort優化算法

    在mysql version()<4.1以前,優化器採用的是filesort第一種優化算法,先提取鍵值和指針,排序後再去提取數據,先後要搜索數據 兩次,第一次若能使用索引則使用,第二次是隨機讀(固然不一樣引擎也不一樣)。mysql version()>=4.1,更新了一個新算法,就是在第 一次讀的時候也把selcet的列也讀出來,而後在sort_buffer_size中排序(不夠大則建臨時表保存排序順序),這算法只須要一次讀 取數據。因此有這個廣爲人傳的一個優化方法,那就是增大sort_buffer_size。Filesort第二種算法要用到更多空間, sort_buffer_size不夠大反而會影響速度,因此mysql開發團隊定了個變量max_length_for_sort_data,當算法中讀出來的須要列 的數據的大小超過該變量的值才使用,因此通常性能分析的時候會嘗試把max_length_for_sort_data改小。

  • 單獨order by 用不了索引,索引考慮加where 或加limit

    先建一個索引(last_login),建的過程就不給出了

    mysql> explain select * from one order by last_login desc; 
    +----+-------------+-------+------+---------------+------+---------+------+-------+----------------+ 
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows 
    | Extra | 
    +----+-------------+-------+------+---------------+------+---------+------+-------+----------------+ 
    | 1 | SIMPLE | one | ALL | NULL | NULL | NULL | NULL | 2046 
    3 | Using filesort | 
    +----+-------------+-------+------+---------------+------+---------+------+-------+----------------+ 
    1 row in set (0.00 sec)

    mysql> explain select * from one order by last_login desc limit 10; 
    +----+-------------+-------+-------+---------------+------------+---------+------+------+-------+ 
    | id | select_type | table | type | possible_keys | key | key_len | ref 
    | rows | Extra | 
    +----+-------------+-------+-------+---------------+------------+---------+------+------+-------+ 
    | 1 | SIMPLE | one | index | NULL | last_login | 4 | NULL 
    | 10 | | 
    +----+-------------+-------+-------+---------------+------------+---------+------+------+-------+ 
    1 row in set (0.00 sec) 
    開始沒limit查詢是遍歷表的,加了limit後,索引可使用,看key_len 和key

  • where + orerby 類型,where知足最左前綴原則,且orderby的列和where子句用到的索引的列的子集。便是(a,b,c)索引, where知足最左前綴原則且order by中列a、b、c的任意組合

    mysql> explain select * from one where username='abgvwfnt' and password ='123456 
    ' and last_login='1338251001' order by password desc,last_login desc; 
    +----+-------------+-------+------+---------------+----------+---------+-------------------+------+-------------+ 
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 
    +----+-------------+-------+------+---------------+----------+---------+-------------------+------+-------------+ 
    | 1 | SIMPLE | one | ref | username | username | 83 | const,const,const | 1 | Using where | 
    +----+-------------+-------+------+---------------+----------+---------+-------------------+------+-------------+ 
    1 row in set (0.00 sec)

    mysql> explain select * from one where username='abgvwfnt' and password ='123456 
    ' and last_login='1338251001' order by password desc,level desc; 
    +----+-------------+-------+------+---------------+----------+---------+-------------------+------+----------------------------+ 
    | id | select_type | table | type | possible_keys | key | key_len | ref| rows | Extra | 
    +----+-------------+-------+------+---------------+----------+---------+-------------------+------+-----------------------------+ 
    | 1 | SIMPLE | one | ref | username | username | 83 | const,const,const | 1 | Using where; Using filesort | 
    +----+-------------+-------+------+---------------+----------+---------+-------------------+------+-----------------------------+

    1 row in set (0.00 sec) 
    上面兩條語句明顯的區別是多了一個非索引列level的排序,在extra這列對了Using filesort。 筆者測試結果:where知足最左前綴且order by中的列是該多列索引的子集時(也就是說orerby中沒最左前綴原則限制),無論是否 有asc ,desc混合出現,都能用索引來知足order by。由於篇幅比較大,這裏就不一一列出。

    Ps:很優化博文都說order by中的列要where中出現的列(是索引)的順序一致,筆者認爲不夠嚴謹。

  • where + orerby+limit

    這個其實也差很少,只要where最左前綴,orderby也正確,limit在此影響不大

如何考慮order by來建索引

這個迴歸到建立索引的問題來,在比較經常使用的oder by的列和where中經常使用的列創建多列索引,這樣優化起來的廣度和擴張性都比較好, 固然若是要考慮UNION、JOIN、COUNT、IN等進來就複雜不少了

隔離列

隔離列是隻查詢語句中把索引列隔離出來,也就是說不能在語句中把列包含進表達式中,如id+1=二、inet_aton('210.38.196.138')--- ip轉換成整數、convert(123,char(3))---數字轉換成字符串、date函數等mysql內置的大多函數。

非隔離列影響性能很大甚至是致命的,這也就是趕集網石展的《三十六軍規》中的一條,雖然他沒說明是隔離列。 如下就測試一下:

首先創建一個索引(last_login ),這裏就不給出創建的代碼了,且把last_login改爲整型(這裏只是爲了方便測試,並非影響條件)

mysql> explain select * from one where last_login = 8388605;  +----+-------------+-------+------+---------------+------------+---------+-------+-------+-------------+  | id | select_type | table | type | possible_keys | key        | key_len | ref | rows  | Extra       |  
+----+-------------+-------+------+---------------+------------+---------+-------+-------+-------------+  |  1 | SIMPLE      | one   | ref  | last_login    | last_login | 3       | const   | 1 | Using where |  
+----+-------------+-------+------+---------------+------------+---------+-------+-------+-------------+  1 row in set, 1 warning (0.00 sec)  
容易看出建的索引已起效

mysql> explain select * from one where last_login +1= 8388606 ;  +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+  | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra       |  
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+  |  1 | SIMPLE      | one   | ALL  | NULL          | NULL | NULL    | NULL | 2049  
7 | Using where |  
+----+-------------+-------+------+---------------+------+---------+------+-------+-------------+  1 row in set (0.00 sec)  
last_login +1=8388608非隔離列的出現致使查找的列20197,說明是遍歷整張表且索引不能使用。
這是由於這條語句要找出全部last_login的數據,而後+1再和20197比較,優化器在這方面比較差,性能不好。
因此要儘量的把列隔離出來,如last_login +1= 8388606改爲login_login=8388607,或者把計算、轉換等操做先用php函數處理
過再傳遞給mysql服務器

OR、IN、UNION ALL,能夠嘗試用UNION ALL

  • or會遍歷表就算有索引

    mysql> explain select * from one where username = 'abgvwfnt' or password='123456'; 
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows| Extra | 
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 
    | 1 | SIMPLE | one | ALL | username | NULL | NULL | NULL | 20259 | Using where | 
    +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 
    1 row in set (0.00 sec)

  • 對於in,這個是有爭議的,網上不少優化方案中都提到儘可能少用in,這不全面,其實在in裏面若是是常量的話,可一大膽的用in, 這個也是趕集網石展、阿里hellodab的觀點(筆者從微博中獲知)。應用hellodab一句話「MySQL用IN效率很差,一般是指in中 嵌套一個子查詢,由於MySQL的查詢重寫可能會產生一個很差的執行計劃,而若是in裏面是常量的話,我認爲性能沒有任何問題, 能夠放心使用」---------固然對於這個比較的話,沒有實戰數據的話很難辯解,就算有,影響性能的因素也不少,也許會每一個 dba都有不一樣的測試結果.這也簽名最左前綴中「補洞」一個方法

  • UNION All 直接返回並集,能夠避免去重的開銷。之所說「嘗試」用UNION All 替代 OR來優化sql語句,由於這不是一直能優化的了, 這裏只是做爲一個方法去嘗試。

索引選擇性

索引選擇性是不重複的索引值也叫基數(cardinality)表中數據行數的比值,索引選擇性=基數/數據行,基數能夠經過 「show index from 表名」查看。高索引選擇性的好處就是mysql查找匹配的時候能夠過濾更多的行,惟一索引的選擇性最佳,值爲1。 那麼對於非惟一索引或者說要被建立索引的列的數據內容很長,那就要選擇索引前綴。這裏就簡單說明一下:

mysql> select count(distinct(username))/count(*)  from one;  +------------------------------------+  | count(distinct(username))/count(*) |  
+------------------------------------+  |                             0.2047 |  
+------------------------------------+  1 row in set (0.09 sec)

count(distinct(username))/count( )就是索引選擇性,這裏0.2過小了。假如username列數據很長,則能夠經過 select count(distinct(concat(first_name, left(last_name, N))/count( ) from one;測試出接近1的索引選擇性, 其中N是索引的長度,窮舉法去找出N的值,而後再建索引。

重複或多餘索引

不少phper開始都覺得建索引相對多點性能就好點,壓根沒考慮到有些索引是重複的,好比建一個(username),(username,password), (username,password,last_login),很明顯第一個索引是重複的,由於後二者都能知足其功能。要有個意識就是,在知足功能需求的 狀況下建最少索引。對於INNODB引擎的索引來講,每次修改數據都要把主鍵索引,輔助索引中相應索引值修改,這可能會出現大量數 據遷移,分頁,以及碎片的出現。

系統配置與維護優化

重要的一些變量

  • key_buffer_size索引塊緩存區大小, 針對MyISAM存儲引擎,該值越大,性能越好.可是超過操做系統能承受的最大值,反而會使mysql變得不穩定. ----這是很重要的參數

  • sort_buffer_size 這是索引在排序緩衝區大小,若排序數據大小超過該值,則建立臨時文件,注意和MyISAM_sort_buffer_size的區別----這是很重要的參數

  • read_rnd_buffer_size當排序後按排序後的順序讀取行時,則經過該緩衝區讀取行,避免搜索硬盤。將該變量設置爲較大的值能夠大大改進ORDER BY的性能。可是,這是爲每一個客戶端分配的緩衝區,所以你不該將全局變量設置爲較大的值。相反,只爲須要運行大查詢的客戶端更改會話變量

  • join_buffer_size用於表間關聯(join)的緩存大小

  • tmp_table_size緩存表的大小

  • table_cache容許 MySQL 打開的表的最大個數,而且這些都cache在內存中

  • delay_key_write針對MyISAM存儲引擎,延遲更新索引.意思是說,update記錄時,先將數據up到磁盤,但不up索引,將索引存在內存裏,當表關閉時,將內存索引,寫到磁盤

更多參數查看: http://www.phpben.com/?post=70

optimize、Analyze、check、repair維護操做

  • optimize 數據在插入,更新,刪除的時候不免一些數據遷移,分頁,以後就出現一些碎片,長此以往碎片積累起來影響性能, 這就須要DBA按期的優化數據庫減小碎片,這就經過optimize命令。如對MyISAM表操做:optimize table 表名

    對於InnoDB表是不支持optimize操做,不然提示「Table does not support optimize, doing recreate + analyze instead」, 固然也能夠經過命令:alter table one type=innodb; 來替代。

  • Analyze 用來分析和存儲表的關鍵字的分佈,使得系統得到準確的統計信息,影響 SQL 的執行計劃的生成。對於數據基本沒有發生 變化的表,是不須要常常進行表分析的。可是若是表的數據量變化很明顯,用戶感受實際的執行計劃和預期的執行計劃不 同的時候, 執行一次表分析可能有助於產生預期的執行計劃。Analyze table 表名

  • Check檢查表或者視圖是否存在錯誤,對 MyISAM 和 InnoDB 存儲引擎的表有做用。對於 MyISAM 存儲引擎的表進行表檢查, 也會同時更新關鍵字統計數據

  • Repair optimize須要有足夠的硬盤空間,不然可能會破壞表,致使不能操做,那就要用上repair,注意INNODB不支持repair操做

以上的操做出現的都是以下這是check

+----------+-------+--------------+-------------+  | Table  | Op  | Msg_type| Msg_text |  
+----------+-------+--------------+-------------+  | test.one | check | status  | OK     |  
+----------+-------+--------------+-------------+

其中op是option 能夠是repair check optimize,msg_type 表示信息類型,msg_text 表示信息類型,這裏就說明表的狀態正常。 如在innodb表使用repair就出現note | The storage engine for the table doesn't support repair

注意:以上操做最好在數據庫訪問量最低的時候操做,由於涉及到不少表鎖定,掃描,數據遷移等操做,不然可能致使一些功能沒法 正常使用甚至數據庫崩潰。

表結構的更新與維護

  • 改表結構。當要在數據量千萬級的數據表中使用alter更改表結構的時候,這是一個棘手問題。一種方法是在低併發低訪問量的時 候用日常的alter更改表。另一種就是建另外一個與要修改的表,這個表除了要修改的結構屬性外其餘的和原表如出一轍,這樣就 能獲得一個相應的.frm文件,而後用flush with read lock 鎖定讀,而後覆蓋用新建的.frm文件覆蓋原表的.frm, 最後unlock table 釋放表。

  • 創建新的索引。通常方法這裏不說。

    這個方法對於大表也是頗有效的。這也是爲何不少dba堅持說「先導數據庫在建索引,這樣效率更快」

    • 建立沒索引的a表,導入數據造成.MYD文件。

    • 建立包括索引b表,造成.FRM和.MYI文件

    • 鎖定讀寫

    • 把b表的.FRM和.MYI文件改爲a表名字

    • 解鎖

    • 用repair建立索引。

  • 按期檢查mysql服務器 按期使用show status、show processlist等命令檢查數據庫。這裏就不細說,這提及來也篇幅是比較大的,筆者對這個也不是很瞭解

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

1. 查詢緩存,判斷sql語句是否徹底匹配,再判斷是否有權限,兩個判斷爲假則到解析器解析語句,爲真則提取數據結果返回給用戶。 2. 解析器解析。解析器先詞法分析,語法分析,檢查錯誤好比引號有沒閉合等,而後生成解析樹。 3. 預處理。預處理解決解析器沒法決解的語義,如檢查表和列是否存在,別名是否有錯,生成新的解析樹。 4. 優化器作大量的優化操做。 5. 生成執行計劃。 6. 查詢執行引擎,負責調度引擎獲取相應數據 7. 返回結果。

原文連接: http://www.phpben.com/?post=74

參考: 
http://www.cnblogs.com/hustcat/archive/2009/10/28/1591648.html http://www.cnblogs.com/oldhorse/archive/2009/11/16/1604009.html http://blog.csdn.net/zuiaituantuan/article/details/5909334 http://www.codinglabs.org/html/theory-of-mysql-index.html http://isky000.com/database/mysql_order_by_implement http://dev.mysql.com/doc/refman/5.0/en/server-system-variables.html http://www.docin.com/p-211669085.html

相關文章
相關標籤/搜索