MySQL索引詳解

 

1、初識索引#

1.1 爲何要有索引?#

通常的應用系統,讀寫比例在10:1左右,並且插入操做和通常的更新操做不多出現性能問題,在生產環境中,咱們遇到最多的,也是最容易出問題的,仍是一些複雜的查詢操做,所以對查詢語句的優化顯然是重中之重。提及加速查詢,就不得不提到索引了。html

1.2 什麼是索引?#

索引在MySQL中也叫是一種「鍵」,是存儲引擎用於快速找到記錄的一種數據結構。索引對於良好的性能很是關鍵,尤爲是當表中的數據量愈來愈大時,索引對於性能的影響愈發重要。mysql

索引優化應該是對查詢性能優化最有效的手段了。索引可以輕易將查詢性能提升好幾個數量級。ios

索引至關於字典的音序表,若是要查某個字,若是不使用音序表,則須要從幾百頁中逐頁去查。算法

1.3 你是否對索引存在誤解?#

索引是應用程序設計和開發的一個重要方面。若索引太多,應用程序的性能可能會受到影響。而索引太少,對查詢性能又會產生影響,要找到一個平衡點,這對應用程序的性能相當重要。一些開發人員老是在過後纔想起添加索引----我一直認爲,這源於一種錯誤的開發模式。若是知道數據的使用,從一開始就應該在須要處添加索引。開發人員每每對數據庫的使用停留在應用的層面,好比編寫SQL語句、存儲過程之類,他們甚至可能不知道索引的存在,或認爲過後讓相關DBA加上便可。DBA每每不夠了解業務的數據流,而添加索引須要經過監控大量的SQL語句進而從中找到問題,這個步驟所需的時間確定是遠大於初始添加索引所需的時間,而且可能會遺漏一部分的索引。固然索引也並非越多越好,我曾經遇到過這樣一個問題:某臺MySQL服務器iostat顯示磁盤使用率一直處於100%,通過分析後發現是因爲開發人員添加了太多的索引,在刪除一些沒必要要的索引以後,磁盤使用率立刻降低爲20%。可見索引的添加也是很是有技術含量的。sql

2、索引的原理#

2.1 索引原理#

索引的目的在於提升查詢效率,與咱們查閱圖書所用的目錄是一個道理:先定位到章,而後定位到該章下的一個小節,而後找到頁數。類似的例子還有:查字典,查火車車次,飛機航班等數據庫

本質都是:經過不斷地縮小想要獲取數據的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,咱們能夠老是用同一種查找方式來鎖定數據。vim

數據庫也是同樣,但顯然要複雜的多,由於不只面臨着等值查詢,還有範圍查詢(>、<、between、in)、模糊查詢(like)、並集查詢(or)等等。數據庫應該選擇怎麼樣的方式來應對全部的問題呢?咱們回想字典的例子,能不能把數據分紅段,而後分段查詢呢?最簡單的若是1000條數據,1到100分紅第一段,101到200分紅第二段,201到300分紅第三段......這樣查第250條數據,只要找第三段就能夠了,一會兒去除了90%的無效數據。但若是是1千萬的記錄呢,分紅幾段比較好?稍有算法基礎的同窗會想到搜索樹,其平均複雜度是lgN,具備不錯的查詢性能。但這裏咱們忽略了一個關鍵的問題,複雜度模型是基於每次相同的操做成原本考慮的。而數據庫實現比較複雜,一方面數據是保存在磁盤上的,另一方面爲了提升性能,每次又能夠把部分數據讀入內存來計算,由於咱們知道訪問磁盤的成本大概是訪問內存的十萬倍左右,因此簡單的搜索樹難以知足複雜的應用場景。性能優化

2.2 磁盤IO與預讀#

前面提到了訪問磁盤,那麼這裏先簡單介紹一下磁盤IO和預讀,磁盤讀取數據靠的是機械運動,每次讀取數據花費的時間能夠分爲尋道時間、旋轉延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁道所須要的時間,主流磁盤通常在5ms如下;旋轉延遲就是咱們常常據說的磁盤轉速,好比一個磁盤7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,通常在零點幾毫秒,相對於前兩個時間能夠忽略不計。那麼訪問一次磁盤的時間,即一次磁盤IO的時間約等於5+4.17 = 9ms左右,聽起來還挺不錯的,但要知道一臺500 -MIPS(Million Instructions Per Second)的機器每秒能夠執行5億條指令,由於指令依靠的是電的性質,換句話說執行一次IO的時間能夠執行約450萬條指令,數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難。下圖是計算機硬件延遲的對比圖,供你們參考:服務器

204-MySQL索引原理-01.png?x-oss-process=style/watermark

考慮到磁盤IO是很是高昂的操做,計算機操做系統作了一些優化,當一次IO時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內,由於局部預讀性原理告訴咱們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。每一次IO讀取的數據咱們稱之爲一頁(page)。具體一頁有多大數據跟操做系統有關,通常爲4k或8k,也就是咱們讀取一頁內的數據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計很是有幫助。數據結構

3、索引的數據結構#

MySQL索引的數據結構-B+樹介紹:http://www.javashuo.com/article/p-rzligenp-cy.html

4、MySQL索引管理#

4.1 功能#

  1. 索引的功能就是加速查找

  2. mysql中的primary key,unique,聯合惟一也都是索引,這些索引除了加速查找之外,還有約束的功能

4.2 MySQL經常使用的索引#

  • 普通索引INDEX:加速查找

  • 惟一索引:

    • 主鍵索引PRIMARY KEY:加速查找+約束(不爲空、不能重複)

    • 惟一索引UNIQUE:加速查找+約束(不能重複)

  • 聯合索引:

    • PRIMARY KEY(id,name):聯合主鍵索引

    • UNIQUE(id,name):聯合惟一索引

    • INDEX(id,name):聯合普通索引

4.3 各個索引應用場景#

Copy舉個例子來講,好比你在爲某商場作一個會員卡的系統。

這個系統有一個會員表
有下列字段:
會員編號 INT
會員姓名 VARCHAR(10)
會員身份證號碼 VARCHAR(18)
會員電話 VARCHAR(10)
會員住址 VARCHAR(50)
會員備註信息 TEXT

那麼這個 會員編號,做爲主鍵,使用 PRIMARY
會員姓名 若是要建索引的話,那麼就是普通的 INDEX
會員身份證號碼 若是要建索引的話,那麼能夠選擇 UNIQUE (惟一的,不容許重複)# 除此以外還有全文索引,即FULLTEXT會員備註信息 , 若是須要建索引的話,能夠選擇全文搜索。
用於搜索很長一篇文章的時候,效果最好。
用在比較短的文本,若是就一兩行字的,普通的 INDEX 也能夠。
但其實對於全文搜索,咱們並不會使用MySQL自帶的該索引,而是會選擇第三方軟件如Sphinx,專門來作全文搜索。# 其餘的如空間索引SPATIAL,瞭解便可,幾乎不用各個索引的應用場景

各個索引的應用場景

4.4 索引的兩大類型hash與btree#

咱們能夠在建立上述索引的時候,爲其指定索引類型,分兩類:

  1. hash類型的索引:查詢單條快,範圍查詢慢

  2. btree類型的索引:b+樹,層數越多,數據量指數級增加(咱們就用它,由於innodb默認支持它)

不一樣的存儲引擎支持的索引類型也不同:

  • InnoDB 支持事務,支持行級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;

  • MyISAM 不支持事務,支持表級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;

  • Memory 不支持事務,支持表級別鎖定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;

  • NDB 支持事務,支持行級別鎖定,支持 Hash 索引,不支持 B-tree、Full-text 等索引;

  • Archive 不支持事務,支持表級別鎖定,不支持 B-tree、Hash、Full-text 等索引;

4.5 建立/刪除索引的語法#

Copy# 方法一:建立表時
      CREATE TABLE 表名 (
                字段名1  數據類型 [完整性約束條件…],
                字段名2  數據類型 [完整性約束條件…],
                [UNIQUE | FULLTEXT | SPATIAL ]   INDEX | KEY
                [索引名]  (字段名[(長度)]  [ASC |DESC]) 
                );


# 方法二:CREATE在已存在的表上建立索引        CREATE  [UNIQUE | FULLTEXT | SPATIAL ]  INDEX  索引名 
                     ON 表名 (字段名[(長度)]  [ASC |DESC]) ;


# 方法三:ALTER TABLE在已存在的表上建立索引        ALTER TABLE 表名 ADD  [UNIQUE | FULLTEXT | SPATIAL ] INDEX
                             索引名 (字段名[(長度)]  [ASC |DESC]) ;
                             
# 刪除索引:DROP INDEX 索引名 ON 表名字;

4.6 示例#

Copy# 方式一create table t1(    id int,    name char,
    age int,
    sex enum('male','female'),    unique key uni_id(id),    index ix_name(name) # index沒有key);create table t1(    id int,    name char,
    age int,
    sex enum('male','female'),    unique key uni_id(id),    index(name) # index沒有key);


# 方式二create index ix_age on t1(age);


# 方式三alter table t1 add index ix_sex(sex);alter table t1 add index(sex);

# 查看
mysql> show create table t1;
| t1    | CREATE TABLE `t1` (  `id` int(11) DEFAULT NULL,  `name` char(1) DEFAULT NULL,  `age` int(11) DEFAULT NULL,  `sex` enum('male','female') DEFAULT NULL,  UNIQUE KEY `uni_id` (`id`),  KEY `ix_name` (`name`),  KEY `ix_age` (`age`),  KEY `ix_sex` (`sex`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

5、測試索引#

5.1 數據準備#

Copy# 1. 準備表create table s1(id int,name varchar(20),
gender char(6),
email varchar(50)
);

# 2. 建立存儲過程,實現批量插入記錄
delimiter $$ # 聲明存儲過程的結束符號爲$$create procedure auto_insert1()BEGIN
    declare i int default 1;
    while(i<3000000)do
        insert into s1 values(i,'eva','female',concat('eva',i,'@oldboy'));        set i=i+1;    end while;END$$ # $$結束
delimiter ; # 從新聲明分號爲結束符號

# 3. 查看存儲過程show create procedure auto_insert1\G 

# 4. 調用存儲過程call auto_insert1();

一、在沒有索引的前提下測試查詢速度

無索引:mysql根本就不知道究竟是否存在id等於333333333的記錄,只能把數據表從頭至尾掃描一遍,此時有多少個磁盤塊就須要進行多少IO操做,因此查詢速度很慢

Copymysql> select * from s1 where id=333333333;
Empty set (0.33 sec)

二、在表中已經存在大量數據的前提下,爲某個字段段創建索引,創建速度會很慢

204-MySQL索引原理-02.png?x-oss-process=style/watermark

三、在索引創建完畢後,以該字段爲查詢條件時,查詢速度提高明顯

204-MySQL索引原理-03.png?x-oss-process=style/watermark

注意:

  1. mysql先去索引表裏根據b+樹的搜索原理很快搜索到id等於333333333的記錄不存在,IO大大下降,於是速度明顯提高

  2. 咱們能夠去mysql的data目錄下找到該表,能夠看到佔用的硬盤空間多了

  3. 須要注意,以下圖

204-MySQL索引原理-04.png?x-oss-process=style/watermark

5.2 小結#

  1. 必定是爲搜索條件的字段建立索引,好比select * from s1 where id = 333;就須要爲id加上索引

  2. 在表中已經有大量數據的狀況下,建索引會很慢,且佔用硬盤空間,建完後查詢速度加快,好比create index idx on s1(id);會掃描表中全部的數據,而後以id爲數據項,建立索引結構,存放於硬盤的表中。建完之後,再查詢就會很快了。

  3. 須要注意的是:innodb表的索引會存放於s1.ibd文件中,而myisam表的索引則會有單獨的索引文件table1.MYI

MySAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在innodb中,表數據文件自己就是按照B+Tree(BTree即Balance True)組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,所以innodb表數據文件自己就是主索引。
由於inndob的數據文件要按照主鍵彙集,因此innodb要求表必需要有主鍵(Myisam能夠沒有),若是沒有顯式定義,則mysql系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則mysql會自動爲innodb表生成一個隱含字段做爲主鍵,這字段的長度爲6個字節,類型爲長整型.

6、正確使用索引#

6.1 索引未命中#

並非說咱們建立了索引就必定會加快查詢速度,若想利用索引達到預想的提升查詢速度的效果,咱們在添加索引時,必須遵循如下問題:

一、範圍問題,或者說條件不明確,條件中出現這些符號或關鍵字:>、>=、<、<=、!= 、between...and...、like、大於號、小於號

204-MySQL索引原理-05.png?x-oss-process=style/watermark

不等於!=

204-MySQL索引原理-06.png?x-oss-process=style/watermark

between ...and...

204-MySQL索引原理-07.png?x-oss-process=style/watermark

like

204-MySQL索引原理-08.png?x-oss-process=style/watermark

二、儘可能選擇區分度高的列做爲索引,區分度的公式是count(distinct col)/count(*),表示字段不重複的比例,比例越大咱們掃描的記錄數越少,惟一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不一樣,這個值也很難肯定,通常須要join的字段咱們都要求是0.1以上,即平均1條掃描10條記錄。

先把表中的索引都刪除,讓咱們專心研究區分度的問題:

Copymysql> desc s1;
+--------+-------------+------+-----+---------+-------+| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+| id     | int(11)     | YES  | MUL | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(5)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  | MUL | NULL    |       |
+--------+-------------+------+-----+---------+-------+rows in set (0.00 sec)

mysql> drop index a on s1;
Query OK, 0 rows affected (0.20 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> drop index d on s1;
Query OK, 0 rows affected (0.18 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> desc s1;
+--------+-------------+------+-----+---------+-------+| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+| id     | int(11)     | YES  |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(5)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+rows in set (0.00 sec)

204-MySQL索引原理-09.png?x-oss-process=style/watermark

分析緣由:

咱們編寫存儲過程爲表s1批量添加記錄,name字段的值均爲egon,也就是說name這個字段的區分度很低(gender字段也是同樣的,咱們稍後再搭理它)

回憶b+樹的結構,查詢的速度與樹的高度成反比,要想將樹的高低控制的很低,須要保證:在某一層內數據項均是按照從左到右,從小到大的順序依次排開,即左1<左2<左3<...

而對於區分度低的字段,沒法找到大小關係,由於值都是相等的,毫無疑問,還想要用b+樹存放這些等值的數據,只能增長樹的高度,字段的區分度越低,則樹的高度越高。極端的狀況,索引字段的值都同樣,那麼b+樹幾乎成了一根棍。本例中就是這種極端的狀況,name字段全部的值均爲'nick'

如今咱們得出一個結論:爲區分度低的字段創建索引,索引樹的高度會很高,然而這具體會帶來什麼影響呢???

  1. 若是條件是name='xxxx',那麼確定是能夠第一時間判斷出'xxxx'是不在索引樹中的(由於樹中全部的值均爲'nick’),因此查詢速度很快

  2. 若是條件正好是name='nick',查詢時,咱們永遠沒法從樹的某個位置獲得一個明確的範圍,只能往下找,往下找,往下找。。。這與全表掃描的IO次數沒有多大區別,因此速度很慢

三、索引列不能在條件中參與計算,保持列「乾淨」,好比from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,緣由很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,須要把全部元素都應用函數才能比較,顯然成本太大。因此語句應該寫成create_time = unix_timestamp(’2014-05-29’)

204-MySQL索引原理-10.png?x-oss-process=style/watermark

四、and/or

  1. and與or的邏輯

    • 條件1 and 條件2:全部條件都成立纔算成立,但凡要有一個條件不成立則最終結果不成立

    • 條件1 or 條件2:只要有一個條件成立則最終結果就成立

  2. and的工做原理

    • 條件:a = 10 and b = 'xxx' and c > 3 and d =4

    • 索引:製做聯合索引(d,a,b,c)

    • 工做原理:對於連續多個and:mysql會按照聯合索引,從左到右的順序找一個區分度高的索引字段(這樣即可以快速鎖定很小的範圍),加速查詢,即按照d—>a->b->c的順序

  3. or的工做原理

    • 條件:a = 10 or b = 'xxx' or c > 3 or d =4

    • 索引:製做聯合索引(d,a,b,c)

    • 工做原理:對於連續多個or:mysql會按照條件的順序,從左到右依次判斷,即a->b->c->d

204-MySQL索引原理-11.png?x-oss-process=style/watermark

在左邊條件成立可是索引字段的區分度低的狀況下(name與gender均屬於這種狀況),會依次往右找到一個區分度高的索引字段,加速查詢。

204-MySQL索引原理-12.png?x-oss-process=style/watermark

204-MySQL索引原理-13.png?x-oss-process=style/watermark

通過分析,在條件爲name='nick' and gender='male' and id>333 and email='xxx'的狀況下,咱們徹底不必爲前三個條件的字段加索引,由於只能用上email字段的索引,前三個字段的索引反而會下降咱們的查詢效率

204-MySQL索引原理-14.png?x-oss-process=style/watermark

五、最左前綴匹配原則,很是重要的原則,對於組合索引mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就中止匹配(指的是範圍大了,有索引速度也慢),好比a = 1 and b = 2 and c > 3 and d = 4 若是創建(a,b,c,d)順序的索引,d是用不到索引的,若是創建(a,b,d,c)的索引則均可以用到,a,b,d的順序能夠任意調整。

204-MySQL索引原理-15.png?x-oss-process=style/watermark

六、其餘狀況

Copy- 使用函數    select * from tb1 where reverse(email) = 'nick';
            
- 類型不一致
    若是列是字符串類型,傳入條件是必須用引號引發來,否則...    select * from tb1 where email = 999;
    
#排序條件爲索引,則select字段必須也是索引字段,不然沒法命中
- order by
    select name from s1 order by email desc;
    當根據索引排序時候,select查詢的字段若是不是索引,則速度仍然很慢    select email from s1 order by email desc;
    特別的:若是對主鍵排序,則仍是速度很快:        select * from tb1 order by nid desc;
 
- 組合索引最左前綴
    若是組合索引爲:(name,email)
    name and email       -- 命中索引
    name                 -- 命中索引
    email                -- 未命中索引- count(1)或count(列)代替count(*)在mysql中沒有差異了

- create index xxxx  on tb(title(19)) #text類型,必須制定長度

6.2 其餘注意事項#

  • 避免使用select *

  • 使用count(*)

  • 建立表時儘可能使用 char 代替 varchar

  • 表的字段順序固定長度的字段優先

  • 組合索引代替多個單列索引(因爲mysql中每次只能使用一個索引,因此常用多個條件查詢時更適合使用組合索引)

  • 儘可能使用短索引

  • 使用鏈接(JOIN)來代替子查詢(Sub-Queries)

  • 連表時注意條件類型需一致

  • 索引散列值(重複少)不適合建索引,例:性別不適合

7、聯合索引和覆蓋索引#

7.1 聯合索引#

聯合索引是指對錶上的多個列合起來作一個索引。聯合索引的建立方法與單個索引的建立方法同樣,不一樣之處僅在於有多個索引列,以下:

Copymysql> create table t(
    -> a int,
    -> b int,
    -> primary key(a),
    -> key idx_a_b(a,b)
    -> );
Query OK, 0 rows affected (0.11 sec)

那麼什麼時候須要使用聯合索引呢?在討論這個問題以前,先來看一下聯合索引內部的結果。從本質上來講,聯合索引就是一棵B+樹,不一樣的是聯合索引的鍵值得數量不是1,而是>=2。接着來討論兩個整型列組成的聯合索引,假定兩個鍵值得名稱分別爲a、b如圖:

204-MySQL索引原理-16.png?x-oss-process=style/watermark

能夠看到這與咱們以前看到的單個鍵的B+樹並無什麼不一樣,鍵值都是排序的,經過葉子結點能夠邏輯上順序地讀出全部數據,就上面的例子來講,即(1,1),(1,2),(2,1),(2,4),(3,1),(3,2),數據按(a,b)的順序進行了存放。

所以,對於查詢select * from table where a=xxx and b=xxx, 顯然是可使用(a,b) 這個聯合索引的,對於單個列a的查詢select * from table where a=xxx,也是可使用(a,b)這個索引的。

但對於b列的查詢select * from table where b=xxx,則不可使用(a,b) 索引,其實你不難發現緣由,葉子節點上b的值爲一、二、一、四、一、2顯然不是排序的,所以對於b列的查詢使用不到(a,b) 索引

聯合索引的第二個好處是在第一個鍵相同的狀況下,已經對第二個鍵進行了排序處理,例如在不少狀況下應用程序都須要查詢某個用戶的購物狀況,並按照時間進行排序,最後取出最近三次的購買記錄,這時使用聯合索引能夠幫咱們避免多一次的排序操做,由於索引自己在葉子節點已經排序了,以下

Copy# ===========準備表==============create table buy_log(
    userid int unsigned not null,
    buy_date date);insert into buy_log values(1,'2009-01-01'),
(2,'2009-01-01'),
(3,'2009-01-01'),
(1,'2009-02-01'),
(3,'2009-02-01'),
(1,'2009-03-01'),
(1,'2009-04-01');alter table buy_log add key(userid);alter table buy_log add key(userid,buy_date);

# ===========驗證==============
mysql> show create table buy_log;
| buy_log | CREATE TABLE `buy_log` (  `userid` int(10) unsigned NOT NULL,  `buy_date` date DEFAULT NULL,  KEY `userid` (`userid`),  KEY `userid_2` (`userid`,`buy_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |

# 能夠看到possible_keys在這裏有兩個索引能夠用,分別是單個索引userid與聯合索引userid_2,可是優化器最終選擇了使用的key是userid由於該索引的葉子節點包含單個鍵值,因此理論上一個頁能存放的記錄應該更多
mysql> explain select * from buy_log where userid=2;
+----+-------------+---------+------+-----------------+--------+---------+-------+------+-------+| id | select_type | table   | type | possible_keys   | key    | key_len | ref   | rows | Extra |
+----+-------------+---------+------+-----------------+--------+---------+-------+------+-------+|  1 | SIMPLE      | buy_log | ref  | userid,userid_2 | userid | 4       | const |    1 |       |
+----+-------------+---------+------+-----------------+--------+---------+-------+------+-------+row in set (0.00 sec)

# 接着假定要取出userid爲1的最近3次的購買記錄,用的就是聯合索引userid_2了,由於在這個索引中,在userid=1的狀況下,buy_date都已經排序好了
mysql> explain select * from buy_log where userid=1 order by buy_date desc limit 3;
+--+-----------+-------+----+---------------+--------+-------+-----+----+------------------------+|id|select_type|table  |type|possible_keys  | key    |key_len|ref  |rows| Extra                  |
+--+-----------+-------+----+---------------+--------+-------+-----+----+------------------------+| 1|SIMPLE     |buy_log|ref |userid,userid_2|userid_2| 4     |const|  4 |Using where; Using index|
+--+-----------+-------+----+---------------+--------+-------+-----+----+------------------------+row in set (0.00 sec)

# ps:若是extra的排序顯示是Using filesort,則意味着在查出數據後須要二次排序(以下查詢語句,沒有先用where userid=3先定位範圍,因而即使命中索引也沒用,須要二次排序)
mysql> explain select * from buy_log order by buy_date desc limit 3;
+--+-----------+-------+-----+-------------+--------+-------+----+----+---------------------------+|id|select_type| table |type |possible_keys|key     |key_len|ref |rows|Extra                      |
+--+-----------+-------+-----+-------------+--------+-------+----+----+---------------------------+| 1|SIMPLE     |buy_log|index| NULL        |userid_2| 8     |NULL|  7 |Using index; Using filesort|
+--+-----------+-------+-----+-------------+--------+-------+----+----+---------------------------+# 對於聯合索引(a,b),下述語句能夠直接使用該索引,無需二次排序select ... from table where a=xxx order by b;

# 而後對於聯合索引(a,b,c)來首,下列語句一樣能夠直接經過索引獲得結果select ... from table where a=xxx order by b;select ... from table where a=xxx and b=xxx order by c;

# 可是對於聯合索引(a,b,c),下列語句不能經過索引直接獲得結果,還須要本身執行一次filesort操做,由於索引(a,c)並未排序select ... from table where a=xxx order by c;

7.2 覆蓋索引#

InnoDB存儲引擎支持覆蓋索引(covering index,或稱索引覆蓋),即從輔助索引中就能夠獲得查詢記錄,而不須要查詢彙集索引中的記錄。

使用覆蓋索引的一個好處是:輔助索引不包含整行記錄的全部信息,故其大小要遠小於彙集索引,所以能夠減小大量的IO操做。


注意:覆蓋索引技術最先是在InnoDB Plugin中完成並實現,這意味着對於InnoDB版本小於1.0的,或者MySQL數據庫版本爲5.0如下的,InnoDB存儲引擎不支持覆蓋索引特性。


對於InnoDB存儲引擎的輔助索引而言,因爲其包含了主鍵信息,所以其葉子節點存放的數據爲(primary key1,priamey key2,...,key1,key2,...)。例如:

Copyselect age from s1 where id=123 and name = 'nick'; #id字段有索引,可是name字段沒有索引,該sql命中了索引,但未覆蓋,須要去彙集索引中再查找詳細信息。
最牛逼的狀況是,索引字段覆蓋了全部,那全程經過索引來加速查詢以及獲取結果就ok了
mysql> desc s1;
+--------+-------------+------+-----+---------+-------+| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+| id | int(11) | NO | | NULL | |
| name | varchar(20) | YES | | NULL | |
| gender | char(6) | YES | | NULL | |
| email | varchar(50) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+rows in set (0.21 sec)

mysql> explain select name from s1 where id=1000; #沒有任何索引
+--+-----------+-----+----------+----+-------------+----+-------+----+-------+--------+-----------+|id|select_type|table|partitions|type|possible_keys|key |key_len|ref | rows  |filtered| Extra     |
+--+-----------+-----+----------+----+-------------+----+-------+----+-------+--------+-----------+| 1| SIMPLE    | s1  | NULL     |ALL | NULL        |NULL| NULL  |NULL|2688336| 10.00  |Using where|
+--+-----------+-----+----------+----+-------------+----+-------+----+-------+--------+-----------+row in set, 1 warning (0.00 sec)

mysql> create index idx_id on s1(id); #建立索引
Query OK, 0 rows affected (4.16 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> explain select name from s1 where id=1000; #命中輔助索引,可是未覆蓋索引,還須要從彙集索引中查找name
+--+-----------+-----+----------+----+-------------+------+-------+-----+----+--------+-----+|id|select_type|table|partitions|type|possible_keys|   key|key_len| ref |rows|filtered|Extra|
+--+-----------+-----+----------+----+-------------+------+-------+-----+----+--------+-----+| 1| SIMPLE    | s1  | NULL     | ref| idx_id      |idx_id| 4     |const| 1  | 100.00 | NULL|
+--+-----------+-----+----------+----+-------------+------+-------+-----+----+--------+-----+row in set, 1 warning (0.08 sec)

mysql> explain select id from s1 where id=1000; #在輔助索引中就找到了所有信息,Using index表明覆蓋索引
+--+-----------+-----+----------+----+-------------+------+-------+-------+------+----------+-----+|id|select_type|table|partitions|type|possible_keys| key  |key_len| ref |rows|filtered| Extra     |
+--+-----------+-----+----------+----+--------------------+-------+-------+------+----------+-----+| 1| SIMPLE    | s1  | NULL     | ref| idx_id      |idx_id|  4    |const| 1  | 100.00 |Using index|
+--+-----------+-----+----------+----+-------------+------+-------+-----+----+--------+-----------+row in set, 1 warning (0.03 sec)

覆蓋索引的另一個好處是對某些統計問題而言的。基於上一小結建立的表buy_log,查詢計劃以下:

Copymysql> explain select count(*) from buy_log;
+--+-----------+-------+-----+-------------+------+-------+----+----+-----------+|id|select_type|table  | type|possible_keys|key   |key_len|ref |rows|Extra      |
+--+-----------+-------+-----+-------------+------+-------+----+----+-----------+| 1| SIMPLE    |buy_log|index| NULL        |userid| 4     |NULL|  7 |Using index|
+--+-----------+-------+-----+-------------+------+-------+----+----+-----------+row in set (0.00 sec)

innodb存儲引擎並不會選擇經過查詢彙集索引來進行統計。因爲buy_log表有輔助索引,而輔助索引遠小於彙集索引,選擇輔助索引能夠減小IO操做,故優化器的選擇如上key爲userid輔助索引

對於(a,b)形式的聯合索引,通常是不能夠選擇b中所謂的查詢條件。但若是是統計操做,而且是覆蓋索引,則優化器仍是會選擇使用該索引,以下:

Copy# 聯合索引userid_2(userid,buy_date),通常狀況,咱們按照buy_date是沒法使用該索引的,但特殊狀況下:查詢語句是統計操做,且是覆蓋索引,則按照buy_date當作查詢條件時,也可使用該聯合索引
mysql> explain select count(*) from buy_log where buy_date >= '2011-01-01' and buy_date < '2011-02-01';
+--+-----------+-------+-----+-------------+--------+-------+----+----+------------------------+|id|select_type| table |type |possible_keys| key    |key_len|ref |rows|Extra                   |
+--+-----------+-------+-----+-------------+--------+-------+----+----+------------------------+| 1| SIMPLE    |buy_log|index| NULL        |userid_2| 8     |NULL|  7 |Using where; Using index|
+--+-----------+-------+-----+-------------+--------+-------+----+----+------------------------+row in set (0.00 sec)

7.3 合併索引#

Copymysql> explain select count(email) from index_t where   id = 1000000  or email='eva100000@oldboy';
+--+-----------+------+--------------+--------------------------------+---------------+--------+-----+----+-----------------------------------------+| id | select_type| table  | type                | possible_keys                              | key                   | key_len | ref    |rows | Extra                                                           |
+--+-----------+------+--------------+--------------------------------+---------------+--------+-----+----+-----------------------------------------+|  1 | SIMPLE      | index_t| index_merge | PRIMARY,email,ind_id,ind_email | PRIMARY,email | 4,51   |NULL|   2    |Using union(PRIMARY,email); Using where |
+--+-----------+------+--------------+--------------------------------+---------------+--------+-----+----+-----------------------------------------+row in set (0.01 sec)

8、查詢優化神器-explain#

MySQL性能分析之Explain:http://www.javashuo.com/article/p-pzshfquv-dk.html

9、慢查詢優化的基本步驟#

  1. 先運行看看是否真的很慢,注意設置SQL_NO_CACHE

  2. where條件單表查,鎖定最小返回記錄表。這句話的意思是把查詢語句的where都應用到表中返回的記錄數最小的表開始查起,單表每一個字段分別查詢,看哪一個字段的區分度最高

  3. explain查看執行計劃,是否與1預期一致(從鎖定記錄較少的表開始查詢)

  4. order by limit 形式的sql語句讓排序的表優先查

  5. 瞭解業務方使用場景

  6. 加索引時參照建索引的幾大原則

  7. 觀察結果,不符合預期繼續從0分析

10、慢日誌管理#

Copy慢日誌
            - 執行時間 > 10
            - 未命中索引
            - 日誌文件路徑
            
        配置:
            - 內存
                show variables like '%query%';
                show variables like '%queries%';
                set global 變量名 = 值
            - 配置文件
                mysqld --defaults-file='E:\wupeiqi\mysql-5.7.16-winx64\mysql-5.7.16-winx64\my-default.ini'
                
                my.conf內容:
                    slow_query_log = ON
                    slow_query_log_file = D:/....
                    
                注意:修改配置文件以後,須要重啓服務
CopyMySQL日誌管理
========================================================
錯誤日誌: 記錄 MySQL 服務器啓動、關閉及運行錯誤等信息
二進制日誌: 又稱binlog日誌,以二進制文件的方式記錄數據庫中除 SELECT 之外的操做
查詢日誌: 記錄查詢的信息
慢查詢日誌: 記錄執行時間超過指定時間的操做
中繼日誌: 備庫將主庫的二進制日誌複製到本身的中繼日誌中,從而在本地進行重放
通用日誌: 審計哪一個帳號、在哪一個時段、作了哪些事件
事務日誌或稱redo日誌: 記錄Innodb事務相關的如事務執行時間、檢查點等
========================================================
1、bin-log1. 啓用
# vim /etc/my.cnf
[mysqld]log-bin[=dir\[filename]]
# service mysqld restart2. 暫停
//僅當前會話SET SQL_LOG_BIN=0;SET SQL_LOG_BIN=1;
3. 查看
查看所有:
# mysqlbinlog mysql.000002
按時間:
# mysqlbinlog mysql.000002 --start-datetime="2012-12-05 10:02:56"# mysqlbinlog mysql.000002 --stop-datetime="2012-12-05 11:02:54"# mysqlbinlog mysql.000002 --start-datetime="2012-12-05 10:02:56" --stop-datetime="2012-12-05 11:02:54" 按字節數:
# mysqlbinlog mysql.000002 --start-position=260# mysqlbinlog mysql.000002 --stop-position=260# mysqlbinlog mysql.000002 --start-position=260 --stop-position=9304. 截斷bin-log(產生新的bin-log文件)
a. 重啓mysql服務器
b. # mysql -uroot -p123 -e 'flush logs'
5. 刪除bin-log文件
# mysql -uroot -p123 -e 'reset master' 


2、查詢日誌
啓用通用查詢日誌
# vim /etc/my.cnf
[mysqld]
log[=dir\[filename]]
# service mysqld restart

3、慢查詢日誌
啓用慢查詢日誌
# vim /etc/my.cnf
[mysqld]
log-slow-queries[=dir\[filename]]
long_query_time=n
# service mysqld restart
MySQL 5.6:
slow-query-log=1
slow-query-log-file=slow.log
long_query_time=3  單位爲秒
查看慢查詢日誌
測試:BENCHMARK(count,expr)
SELECT BENCHMARK(50000000,2*3);
相關文章
相關標籤/搜索