MySql優化之索引原理與 SQL 優化

1.Sql性能不高緣由

  • 查詢數據過多(能不能拆,條件過濾儘可能少)
  • 關聯了太多的表,太多join (join 原理。用 A 表的每一條數據 掃描 B表的全部數據。因此儘可能先過濾。)
  • 沒有利用到索引
  • 服務器調優及各個參數設置(緩衝、線程數等)

2.Sql編寫順序與執行順序

  • 編寫順序
select distinct  '字段'   
from '表'   
join '表' on '條件'   
where '條件'   
group by '字段'   
having '條件'   
order by '字段'   
limit '條件'
複製代碼
  • 執行順序
from '表'   
on '條件'
'join類型' join '表'
where '條件'
group by '字段'
having '條件'
select distinct '字段'
order by '字段'
limit '條件'
複製代碼
  • Sql解析圖

3.索引

3.1什麼是索引

索引(Index)是幫助MySQL高效獲取數據的數據結構。在數據以外,數據庫系統還維護着知足特定查找算法的數據結構,這些數據結構以某種方式指向數據,這樣就能夠在這些數據結構上實現高效的查找算法.這種數據結構,就是 索引.
通常來講索引自己也很大,不可能所有存儲在內存中,所以每每以索引文件的形式存放在磁盤中.咱們日常所說的索引,若是沒有特別說明都是指BTree索引(平衡多路搜索樹).其中彙集索引,次要索引,覆蓋索引複合索引,前綴索引,惟一索引默認都是使用的BTree索引,統稱索引. 除了BTree索引以後,還有哈希索引mysql

3.2索引原理

3.2.1說明

  • 索引(Index)是幫助MySQL高效獲取數據的數據結構。不一樣的引擎使用的數據結構也不盡相同.
  • MyISAM採起的是BTree, InnoDB採起的是B+TREE
    咱們工做的裏面的數據庫通常用的是InnoDB,因此咱們重點來說解B+TREE.B+樹索引是B+樹在數據庫中的一種實現,是最多見也是數據庫中使用最爲頻繁的一種索引。B+樹中的B表明平衡(balance),而不是二叉(binary),由於B+樹是從最先的平衡二叉樹演化而來的。在講B+樹以前必須先了解二叉查找樹、平衡二叉樹(AVLTree)和平衡多路查找樹(B-Tree),B+樹即由這些樹逐步優化而來。

3.2.2二叉查找樹

二叉樹具備如下性質:左子樹的鍵值小於根的鍵值,右子樹的鍵值大於根的鍵值。 以下圖所示就是一棵二叉查找樹算法

對該二叉樹的節點進行查找發現深度爲1的節點的查找次數爲1,深度爲2的查找次數爲2,深度爲n的節點的查找次 數爲n,所以其平均查找次數爲 (1+2+2+3+3+3) / 6 = 2.3次

二叉查找樹能夠任意地構造,一樣是2,3,5,6,7,8這六個數字,也能夠按照下圖的方式來構造:sql

可是這棵二叉樹的查詢效率就低了。所以若想二叉樹的查詢效率儘量高,須要這棵二叉樹是平衡的,從而引出新 的定義——平衡二叉樹,或稱`AVL`樹。

3.2.3平衡二叉樹(AVL Tree)

平衡二叉樹(AVL樹)在符合二叉查找樹的條件下,還知足任何節點的兩個子樹的高度最大差爲1。下面的兩張 圖片,左邊是AVL樹,它的任何節點的兩個子樹的高度差<=1;右邊的不是AVL樹,其根節點的左子樹高度爲3,而 右子樹高度爲1;數據庫

若是在AVL樹中進行插入或刪除節點,可能致使AVL樹失去平衡,這種失去平衡的二叉樹能夠歸納爲四種姿態: LL(左左)、RR(右右)、LR(左右)、RL(右左)。它們的示意圖以下:

這四種失去平衡的姿態都有各自的定義:緩存

LL:LeftLeft,也稱「左左」。插入或刪除一個節點後,根節點的左孩子(Left Child)的左孩子(Left Child)還有非 空節點,致使根節點的左子樹高度比右子樹高度高2,AVL樹失去平衡。
RR:RightRight,也稱「右右」。插入或刪除一個節點後,根節點的右孩子(Right Child)的右孩子(Right Child) 還有非空節點,致使根節點的右子樹高度比左子樹高度高2,AVL樹失去平衡。
LR:LeftRight,也稱「左右」。插入或刪除一個節點後,根節點的左孩子(Left Child)的右孩子(Right Child)還有 非空節點,致使根節點的左子樹高度比右子樹高度高2,AVL樹失去平衡。
RL:RightLeft,也稱「右左」。插入或刪除一個節點後,根節點的右孩子(Right Child)的左孩子(Left Child)還有 非空節點,致使根節點的右子樹高度比左子樹高度高2,AVL樹失去平衡。服務器

AVL樹失去平衡以後,能夠經過旋轉使其恢復平衡。下面分別介紹四種失去平衡的狀況下對應的旋轉方法。 LL的旋轉。LL失去平衡的狀況下,能夠經過一次旋轉讓AVL樹恢復平衡。步驟以下:數據結構

  1. 將根節點的左孩子做爲新根節點。
  2. 將新根節點的右孩子做爲原根節點的左孩子。
  3. 將原根節點做爲新根節點的右孩子。

LL旋轉示意圖以下:併發

RR的旋轉:RR失去平衡的狀況下,旋轉方法與LL旋轉對稱,步驟以下:函數

  1. 將根節點的右孩子做爲新根節點。
  2. 將新根節點的左孩子做爲原根節點的右孩子。
  3. 將原根節點做爲新根節點的左孩子。

RR旋轉示意圖以下:高併發

LR的旋轉:LR失去平衡的狀況下,須要進行兩次旋轉,步驟以下:

  1. 圍繞根節點的左孩子進行RR旋轉。
  2. 圍繞根節點進行LL旋轉。

LR的旋轉示意圖以下:

RL的旋轉:RL失去平衡的狀況下也須要進行兩次旋轉,旋轉方法與LR旋轉對稱,步驟以下:

  1. 圍繞根節點的右孩子進行LL旋轉。
  2. 圍繞根節點進行RR旋轉。

RL的旋轉示意圖以下:

3.2.4平衡多路查找樹(B-Tree)

B-Tree是爲磁盤等外存儲設備設計的一種平衡查找樹。所以在講B-Tree以前先了解下磁盤的相關知識。

系統從磁盤讀取數據到內存時是以磁盤塊(block)爲基本單位的,位於同一個磁盤塊中的數據會被一次性讀取出 來,而不是須要什麼取什麼。

InnoDB存儲引擎中有頁(Page)的概念,頁是其磁盤管理的最小單位。InnoDB存儲引擎中默認每一個頁的大小爲 16KB,可經過參數innodb_page_size將頁的大小設置爲4K、8K、16K.

而系統一個磁盤塊的存儲空間每每沒有這麼大,所以InnoDB每次申請磁盤空間時都會是若干地址連續磁盤塊來達到 頁的大小16KB。InnoDB在把磁盤數據讀入到磁盤時會以頁爲基本單位,在查詢數據時若是一個頁中的每條數據都 能有助於定位數據記錄的位置,這將會減小磁盤I/O次數,提升查詢效率。

一棵m階的B-Tree有以下特性:

  1. 每一個節點最多有m個孩子。
  2. 除了根節點和葉子節點外,其它每一個節點至少有Ceil(m/2)個孩子。
  3. 若根節點不是葉子節點,則至少有2個孩子
  4. 全部葉子節點都在同一層,且不包含其它關鍵字信息
  5. 每一個非終端節點包含n個關鍵字信息(P0,P1,…Pn, k1,…kn)
  6. 關鍵字的個數n知足:ceil(m/2)-1 <= n <= m-1
  7. ki(i=1,…n)爲關鍵字,且關鍵字升序排序。
  8. Pi(i=1,…n)爲指向子樹根節點的指針。P(i-1)指向的子樹的全部節點關鍵字均小於ki,但都大於k(i-1)

B-Tree中的每一個節點根據實際狀況能夠包含大量的關鍵字信息和分支,以下圖所示爲一個3階的B-Tree:

每一個節點佔用一個盤塊的磁盤空間,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指針,指針存儲 的是子節點所在磁盤塊的地址。兩個關鍵詞劃分紅的三個範圍域對應三個指針指向的子樹的數據的範圍域。以根節 點爲例,關鍵字爲17和35,P1指針指向的子樹的數據範圍爲小於17,P2指針指向的子樹的數據範圍爲17~35,P3 指針指向的子樹的數據範圍爲大於35。

模擬查找關鍵字29的過程:

  1. 根據根節點找到磁盤塊1,讀入內存。【磁盤I/O操做第1次】
  2. 比較關鍵字29在區間(17,35),找到磁盤塊1的指針P2。
  3. 根據P2指針找到磁盤塊3,讀入內存。【磁盤I/O操做第2次】
  4. 比較關鍵字29在區間(26,30),找到磁盤塊3的指針P2。
  5. 根據P2指針找到磁盤塊8,讀入內存。【磁盤I/O操做第3次】
  6. 在磁盤塊8中的關鍵字列表中找到關鍵字29。

分析上面過程,發現須要3次磁盤I/O操做,和3次內存查找操做。因爲內存中的關鍵字是一個有序表結構,能夠利用 二分法查找提升效率。而3次磁盤I/O操做是影響整個B-Tree查找效率的決定因素。B-Tree相對於AVLTree縮減了節 點個數,使每次磁盤I/O取到內存的數據都發揮了做用,從而提升了查詢效率。

3.2.5B+Tree

B+Tree是在B-Tree基礎上的一種優化,使其更適合實現外存儲索引結構,InnoDB存儲引擎就是用B+Tree實現其索 引結構。

從上一節中的B-Tree結構圖中能夠看到每一個節點中不只包含數據的key值,還有data值。而每個頁的存儲空間是 有限的,若是data數據較大時將會致使每一個節點(即一個頁)能存儲的key的數量很小,當存儲的數據量很大時同 樣會致使B-Tree的深度較大,增大查詢時的磁盤I/O次數,進而影響查詢效率。在B+Tree中,全部數據記錄節點都 是按照鍵值大小順序存放在同一層的葉子節點上,而非葉子節點上只存儲key值信息,這樣能夠大大加大每一個節點 存儲的key值數量,下降B+Tree的高度。

B+Tree相對於B-Tree有幾點不一樣:

  1. 非葉子節點只存儲鍵值信息。
  2. 全部葉子節點之間都有一個鏈指針。
  3. 數據記錄都存放在葉子節點中。

將上一節中的B-Tree優化,因爲B+Tree的非葉子節點只存儲鍵值信息,假設每一個磁盤塊能存儲4個鍵值及指針信 息,則變成B+Tree後其結構以下圖所示:

一般在B+Tree上有兩個頭指針,一個指向根節點,另外一個指向關鍵字最小的葉子節點,並且全部葉子節點(即數據 節點)之間是一種鏈式環結構。所以能夠對B+Tree進行兩種查找運算:一種是對於主鍵的範圍查找和分頁查找,另 一種是從根節點開始,進行隨機查找。

可能上面例子中只有22條數據記錄,看不出B+Tree的優勢,下面作一個推算:

InnoDB存儲引擎中頁的大小爲16KB,通常表的主鍵類型爲INT(佔用4個字節)或BIGINT(佔用8個字節),指針 類型也通常爲4或8個字節,也就是說一個頁(B+Tree中的一個節點)中大概存儲16KB/(8B+8B)=1K個鍵值(由於 是估值,爲方便計算,這裏的K取值爲〖10〗^3)。也就是說一個深度爲3的B+Tree索引能夠維護10^3 * 10^3 * 10^3 = 10億 條記錄。

實際狀況中每一個節點可能不能填充滿,所以在數據庫中,B+Tree的高度通常都在2~4層。mysql的InnoDB存儲引擎 在設計時是將根節點常駐內存的,也就是說查找某一鍵值的行記錄時最多隻須要1~3次磁盤I/O操做。

數據庫中的B+Tree索引能夠分爲彙集索引(clustered index)和輔助索引(secondary index)。上面的B+Tree示 例圖在數據庫中的實現即爲彙集索引,彙集索引的B+Tree中的葉子節點存放的是整張表的行記錄數據。輔助索引與 彙集索引的區別在於輔助索引的葉子節點並不包含行記錄的所有數據,而是存儲相應行數據的彙集索引鍵,即主 鍵。當經過輔助索引來查詢數據時,InnoDB存儲引擎會遍歷輔助索引找到主鍵,而後再經過主鍵在彙集索引中找到 完整的行記錄數據

3.3索引的優缺點

  • 優勢:
    提升數據查詢的效率,下降數據庫的IO成本
    經過索引對數據進行排序,下降數據排序的成本,下降CPU的消耗
  • 缺點:
    索引自己也是一張表,該表保存了主鍵與索引字段,並指向實體表的記錄,因此索引列也要佔用空間
    雖然索引大大提升了查詢的速度,同時反向影響增刪改操做的效率,由於表中數據變化以後,會致使索引內容不許, 因此也須要更新索引表信息,增長了數據庫的工做量
    隨着業務的不斷變化,以前創建的索引可能不能知足查詢需求,須要消耗咱們的時間去更新索引

3.4索引的類別

3.4.1普通索引

是最基本的索引,它沒有任何限制.

CREATE index 索引名 on 表名(列名)
複製代碼

3.4.2惟一索引

與前面的普通索引相似,不一樣的就是:索引列的值必須惟一,但容許有空值。若是是組合索引,則列值的組合必須 惟一。

CREATE UNIQUE index 索引名 on 表名(列名)
複製代碼

3.4.3主鍵索引

是一種特殊的惟一索引,一個表只能有一個主鍵,不容許有空值。通常是在建表的時候同時建立主鍵索引.也就 是說主鍵約束默認索引

3.4.4複合索引

指多個字段上建立的索引,只有在查詢條件中使用了建立索引時的第一個字段,索引纔會被使用。使用組合索引時 遵循最左前綴集合

CREATE index 索引名 on 表名(列名,列名...)
複製代碼

3.4.5全文索引

主要用來查找文本中的關鍵字,而不是直接與索引中的值相比較。fulltext索引跟其它索引大不相同,它更像是一個 搜索引擎,而不是簡單的where語句的參數匹配。fulltext索引配合match against操做使用,而不是通常的where 語句加like。它能夠在create table,alter table ,create index使用,不過目前只有char、varchar,text 列上能夠 建立全文索引。值得一提的是,在數據量較大時候,現將數據放入一個沒有全局索引的表中,而後再用CREATE index建立fulltext索引,要比先爲一張表創建fulltext而後再將數據寫入的速度快不少

CREATE TABLE `table` ( 
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    `title` char(255) CHARACTER NOT NULL ,
    `content` text CHARACTER NULL ,
    `time` int(10) NULL DEFAULT NULL ,
    PRIMARY KEY (`id`), FULLTEXT (content) 
);
複製代碼

3.5索引的基本語法

  • 建立
ALTER mytable ADD [UNIQUE] INDEX [indexName] ON 表名(列名)
複製代碼
  • 刪除
DROP INDEX [indexName] ON 表名;
複製代碼
  • 查看
SHOW INDEX FROM 表名
複製代碼
  • alter命令
‐‐ 有四種方式來添加數據表的索引: 
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): 該語句添加一個主鍵,這意味着索引值必須是惟一的, 且不能爲NULLALTER TABLE tbl_name ADD UNIQUE index_name (column_list): 這條語句建立索引的值必須是惟一的(除了NULL 外,NULL可能會出現屢次)。
ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出現屢次。 
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):該語句指定了索引爲 FULLTEXT ,用於全文索引。
複製代碼

3.6索引的使用場景

3.6.1適合使用索引

  1. 頻繁做爲查詢條件的字段應該建立索引
  2. 多表查詢中與其它表進行關聯的字段,外鍵關係創建索引
  3. 單列索引/複合索引的選擇,高併發下傾向於建立複合索引
  4. 查詢中常常用來排序的字段
  5. 查詢中常常用來統計或者分組字段

3.6.2不適合使用索引

  1. 頻繁更新的字段: 每次更新都會影響索引樹
  2. where條件查詢中用不到的字段
  3. 表記錄太少
  4. 常常增刪改的表: 更新了表,索引也得更新才行
  5. 注意: 若是一張表中,重複的記錄很是多,爲它創建索引就沒有太大意義

4.性能分析

4.1Query Optimizer

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

4.2MySQL常見瓶頸

  1. CPU飽和:CPU飽和的時候,通常發生在數據裝入內存或從磁盤上讀取數據的時候
  2. IO瓶頸: 磁盤IO瓶頸發生在裝入數據遠大於內存容量的時候
  3. 服務器硬件的性能瓶頸

4.3執行計劃Explai

4.3.1Explain概述

使用explain關鍵字能夠模擬優化器執行SQL查詢語句,從而知道MYSQL是如何處理SQL語句的.咱們能夠用執行 計劃來分析查詢語句或者表結構的性能瓶頸

4.3.2Explain做用

  1. 查看錶的讀取順序
  2. 查看數據庫讀取操做的操做類型
  3. 查看哪些索引有可能被用到
  4. 查看哪些索引真正被用到
  5. 查看錶之間的引用
  6. 查看錶中有多少行記錄被優化器查詢

4.3.3語法

  • 語法
explain sql語句
複製代碼

4.3.4各字段解釋

  • 準備工做
create table t1(
id int primary key, 
name varchar(20), 
col1 varchar(20), 
col2 varchar(20), 
col3 varchar(20) 
);
create table t2( 
id int primary key,
name varchar(20), 
col1 varchar(20), 
col2 varchar(20), 
col3 varchar(20) 
);
create table t3( 
id int primary key,
name varchar(20),
col1 varchar(20),
col2 varchar(20), 
col3 varchar(20) 
);
insert into t1 values(1,'zs1','col1','col2','col3'); 
insert into t2 values(1,'zs2','col2','col2','col3'); 
insert into t3 values(1,'zs3','col3','col2','col3'); 
create index ind_t1_c1 on t1(col1);
create index ind_t2_c1 on t2(col1); 
create index ind_t3_c1 on t3(col1);
create index ind_t1_c12 on t1(col1,col2);
create index ind_t2_c12 on t2(col1,col2); 
create index ind_t3_c12 on t3(col1,col2);
複製代碼
  • 執行explain sql語句後:

4.3.4.1 id

  • select 查詢的序列號,包含一組數字,表示查詢中執行Select子句或操做表的順序

  • 三種狀況:

    1. id值相同,執行順序由上而下
    explain select t2.* from t1,t2,t3 where t1.id = t2.id and t1.id= t3.id and t1.name = 'zs';
    複製代碼
    2. id值不一樣,id值越大優先級越高,越先被執行
    explain select t2.* from t2 where id = (select id from t1 where id = (select t3.id from t3 where t3.name='zs3'));
    複製代碼
    3. id值有相同的也有不一樣的,若是id相同,從上往下執行,id值越大,優先級越高,越先執行
    explain select t2.* from (select t3.id from t3 where t3.name='zs3') s1,t2 where s1.id = t2.id;
    複製代碼

4.3.4.2select_type

查詢類型,主要用於區別

  • SIMPLE : 簡單的select查詢,查詢中不包含子查詢或者UNION
  • PRIMARY: 查詢中若包含複雜的子查詢,最外層的查詢則標記爲PRIMARY
  • SUBQUERY : 在SELECT或者WHERE列表中包含子查詢
  • DERIVED : 在from列表中包含子查詢被標記爲DRIVED衍生,MYSQL會遞歸執行這些子查詢,把結果放到臨時表 中
  • UNION: 若第二個SELECT出如今union以後,則被標記爲UNION, 若union包含在from子句的子查詢中,外層 select被標記爲:derived
  • UNION RESULT: 從union表獲取結果的select
explain select col1,col2 from t1 union select col1,col2 from t2;
複製代碼

4.3.4.3table

顯示這一行的數據是和哪張表相關

4.3.4.4type

訪問類型: all, index,range,ref,eq_ref, const,system,null
最好到最差依次是: system > const > eq_ref>ref >range > index > all , 最好能優化到range級別或則ref級別

  • system: 表中只有一行記錄(系統表), 這是const類型的特例, 基本上不會出現
  • const: 經過索引一次查詢就找到了,const用於比較primary key或者unique索引,由於只匹配一行數據,因此很 快,如將主鍵置於where列表中,mysql就會將該查詢轉換爲一個常量
explain select * from (select * from t1 where id=1) s1;
複製代碼
  • eq_ref: 惟一性索引掃描, 對於每一個索引鍵,表中只有一條記錄與之匹配, 常見於主鍵或者惟一索引掃描
explain select * from t1,t2 where t1.id = t2.id;
複製代碼
  • ref : 非惟一性索引掃描,返回匹配某個單獨值的全部行,本質上也是一種索引訪問,它返回全部符合條件的行,然而 它可能返回多個符合條件的行
explain select * from t1 where col1='zs1';
複製代碼
  • range : 只檢索給定範圍的行, 使用一個索引來選擇行.key列顯示的是真正使用了哪一個索引,通常就是在where條 件中使用between,>,<,in 等範圍的條件,這種在索引範圍內的掃描比全表掃描要好,由於它只在某個範圍中掃描, 不須要掃描所有的索引
explain select * from t1 where id between 1 and 10;
複製代碼
  • index : 掃描整個索引表, index 和all的區別爲index類型只遍歷索引樹. 這一般比all快,由於索引文件一般比數據 文件小,雖然index和all都是讀全表,可是index是從索引中讀取,而all是從硬盤中讀取數據
explain select id from t1;
複製代碼
  • all : full table scan全表掃描 ,將遍歷全表以找到匹配的行
explain select * from t1;
複製代碼
  • 注意: 開發中,咱們得保證查詢至少達到range級別,最好能達到ref. 若是百萬條數據出現all, 通常狀況下就須要考慮使用索引優化了

4.3.4.5possible_keys

SQL查詢中可能用到的索引,但查詢的過程當中不必定真正使用

4.3.4.6key

查詢過程當中真正使用的索引,若是爲null,則表示沒有使用索引
查詢中使用了覆蓋索引,則該索引僅出如今key列表中

explain select t2.* from t1,t2,t3 where t1.col1 = ' ' and t1.id = t2.id and t1.id= t3.id;
複製代碼
explain select col1 from t1;
複製代碼

4.3.4.7key_len

索引中使用的字節數,可經過該列計算查詢中使用的索引的長度,在不損失精確度的狀況下,長度越短越好, key_len顯 示的值爲索引字段的最大可能長度,並不是實際使用長度, 即key_len是根據表定義計算而得

explain select * from t1 where col1='c1';
複製代碼
explain select * from t1 where col1='col1' and col2 = 'col2';
‐‐ 注意: 爲了演示這個結果,咱們刪除了c1上面的索引 
alter table t1 drop index ind_t1_c1; 
‐‐ 執行完成以後,再次建立索引 
create index ind_t1_c1 on t1(col1);
複製代碼

4.3.4.8ref

顯示索引的哪一列被使用了,若是可能的話,是一個常數.哪些列或者常量被用於查找索引列上的值

explain select * from t1,t2 where t1.col1 = t2.col1 and t1.col2 = 'col2';
複製代碼

4.3.4.9rows

根據表統計信息及索引選用的狀況,估算找出所需記錄要讀取的行數 (有多少行記錄被優化器讀取) ,越少越好

4.3.4.10extra

包含其它一些很是重要的額外信息

  • Using filesort : 說明mysql會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取,Mysql中無 法利用索引完成的排序操做稱爲文件排序
explain select col1 from t1 where col1='col1' order by col3;
複製代碼
‐‐ 上面這條SQL語句出現了using filesort,可是咱們去執行下面這條SQL語句的時候它,又不會出現using filesort
explain select col1 from t1 where col1='col1' order by col2;
複製代碼
‐‐ 如何優化第一條SQL語句 ? 
create index ind_t1_c13 on t1(col1,col3);
explain select col1 from t1 where col1='col1' order by col3;
複製代碼
  • Using temporary : 使用了臨時表保存中間結果,Mysql在對查詢結果排序時使用了臨時表,常見於order by 和分 組查詢group by
explain select col1 from t1 where col1>'col1' group by col2;
複製代碼
explain select col1 from t1 where col1 >'col1' group by col1,col2;
複製代碼
  • Using index :
  • 查詢操做中使用了覆蓋索引(查詢的列和索引列一致),避免訪問了表的數據行,效率好
  • 若是同時出現了using where, 代表索引被用來執行索引鍵值的查找
  • 若是沒有同時出現using where, 代表索引用來讀取數據而非執行查找動做
  • 覆蓋索引: 查詢的列和索引列一致, 換句話說查詢的列要被所鍵的索引覆蓋,就是select中數據列只需從索引中就 能讀取,沒必要讀取原來的數據行,MySql能夠利用索引返回select列表中的字段,而沒必要根據索引再次讀取數據文件
explain select col2 from t1 where col1='col1';
複製代碼
explain select col2 from t1;
複製代碼
  • using where : 代表使用了where條件過濾
  • using join buffer : 代表使用了鏈接緩存, join次數太多了可能會出現
  • impossible where : where子句中的值老是false,不能用來獲取任何數據
explain select * from t1 where col1='zs' and col1='ls';
複製代碼
  • select tables optimized away :
  • 在沒有group by 子句的狀況下, 基於索引優化min/max操做或者對於MyISAM存儲引擎優化count(*)操做,沒必要 等到執行階段再進行計算,查詢執行計劃生成階段即完成優化
  • distinct : 優化distinct操做,在找到第一個匹配的數據後即中止查找一樣的值的動做

5.優化實戰

5.1單表查詢優化

需求: 查詢 category_id 爲1 且 comments 大於 1 的狀況下,views 最多的 article_id。

  • 建表語句
CREATE TABLE IF NOT EXISTS `article` (
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`author_id` INT(10) UNSIGNED NOT NULL, 
`category_id` INT(10) UNSIGNED NOT NULL, 
`views` INT(10) UNSIGNED NOT NULL,
`comments` INT(10) UNSIGNED NOT NULL,
`title` VARBINARY(255) NOT NULL,
`content` TEXT NOT NULL 
);
INSERT INTO `article`(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES
(1, 1, 1, 1, '1', '1'),
(2, 2, 2, 2, '2', '2'),
(1, 1, 3, 3, '3', '3');
複製代碼
  • 執行計劃
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
複製代碼
  • 沒有索引狀況
結論:很顯然,type 是 ALL,即最壞的狀況。Extra 裏還出現了 Using filesort,也是最壞的狀況。優化是必須的。
  • 優化一:
create index idx_article_ccv on article(category_id,comments,views);
複製代碼

結論: type 變成了 range,這是能夠忍受的。可是 extra 裏使用 Using filesort 還是沒法接受的。可是咱們已經創建 了索引,爲啥沒用呢? 這是由於按照 BTree 索引的工做原理, 先排序 category_id, 若是遇到相同的 category_id 則再 排序 comments,若是遇到相同的 comments 則再排序 views。當 comments 字段在聯合索引裏處於中間位置時,因 comments > 1 條件是一個範圍值(所謂 range),MySQL 沒法利用索引再對後面的 views 部分進行檢索,即 range 類 型查詢字段後面的索引無效。

  • 優化二:
‐‐ 先刪除優化一索引 
DROP INDEX idx_article_ccv ON article;
‐‐ 建立索引 
create index idx_article_cv on article(category_id,views);
複製代碼

5.2關聯查詢優化

5.2.1示例

需求: 使用左外鏈接查詢class和book

  • 建表語句
CREATE TABLE IF NOT EXISTS `class` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`) 
);
CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, 
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`bookid`) 
);
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
複製代碼
  • 執行計劃
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
複製代碼
  • 優化
ALTER TABLE `book` ADD INDEX Y ( `card`);
複製代碼

能夠看到第二行的 type 變爲了 ref,rows 也變成了優化比較明顯。
這是由左鏈接特性決定的。LEFT JOIN 條件用於肯定如何從右表搜索行,左邊必定都有,因此右邊是咱們的關鍵點,必定 須要創建索引。
也就是說: 外鏈接在相反方建立索引

5.2.2優化建議

  • 保證被驅動表的join字段已經被索引 (被驅動表 join 後的表爲被驅動表 (須要被查詢))
  • left join 時,選擇小表做爲驅動表,大表做爲被驅動表。
  • inner join 時,mysql會本身幫你把小結果集的表選爲驅動表。

5.3子查詢優化

  • 子查詢儘可能不要放在被驅動表,有可能使用不到索引。
select a.name ,bc.name from t_emp a left join 
    (select b.id , c.name from t_dept b
    inner join t_emp c on b.ceo = c.id)bc
    on bc.id = a.deptid 
上段查詢中用到了子查詢,必然 bc 表沒有索引。確定會進行全表掃描
上段查詢 能夠直接使用 兩個 left join 優化 
select a.name , c.name from t_emp a 
    left outer join t_dept b on a.deptid = b.id 
    left outer join t_emp c on b.ceo=c.id 
全部條件均可以使用到索引 
若必須用到子查詢,可將子查詢設置爲驅動表,由於驅動表的type 確定是 all,而子查詢返回的結果表沒有索引,一定 也是all
複製代碼

5.4order by優化

5.4.1環境準備

  • 建表語句
CREATE TABLE tblA(
id int primary key not null auto_increment, 
age INT,
birth TIMESTAMP NOT NULL,
name varchar(200) 
);

INSERT INTO tblA(age,birth,name) VALUES(22,NOW(),'abc');
INSERT INTO tblA(age,birth,name) VALUES(23,NOW(),'bcd'); 
INSERT INTO tblA(age,birth,name) VALUES(24,NOW(),'def');

CREATE INDEX idx_A_ageBirth ON tblA(age,birth,name);
複製代碼

5.4.2說明

MySQL支持二種方式的排序,FileSort和Index,Index效率高. 它指MySQL掃描索引自己完成排序。FileSort方式效 率較低。

5.4.3ORDER BY會使用Index方式排序

  • ORDER BY 語句使用索引最左前列
EXPLAIN SELECT * FROM tbla WHERE age > 1 ORDER BY age
複製代碼
  • 使用Where子句與Order BY子句條件列組合知足索引最左前列
EXPLAIN SELECT * FROM tbla WHERE age = 1 ORDER BY birth
複製代碼
  • where子句中若是出現索引的範圍查詢(即explain中出現range)會致使order by 索引失效
  • Order by 複合條件排序, 按照索引順序, 先後的排序類別不一致,會致使order by 索引失效

5.5GROUP BY優化

5.5.1說明

group by實質是先排序後進行分組,遵守索引建的最佳左前綴

5.5.2注意

where高於having,能寫在where限定的條件就不要去having限定了

5.6limit優化

5.6.1說明

limit經常使用於分頁處理,時常會伴隨order by 從句使用, 所以大多時候會使用Filesorts,這樣會形成大量的IO問題

5.6.2優化

  • 步驟一: 使用有索引的列或者主鍵進行Order By操做
  • 步驟二: 記錄上次返回的主鍵,在下次查詢的時候使用註解過濾(若是主鍵不是連續的,是字符串類型,能夠建立一 個列記錄)

6.總結

  • 全值匹配我最愛
  • 最佳左前綴法則(若是索引了多列,要遵照最左前綴法則。指的是查詢從索引的最左前列開始而且不跳過索引 中的列)
  • 不在索引列上作任何操做(計算、函數、(自動or手動)類型轉換),會致使索引失效而轉向全表掃描
  • 存儲引擎不能使用索引中範圍條件右邊的列
  • 儘可能使用覆蓋索引(只訪問索引的查詢(索引列和查詢列一致)),減小select *
  • mysql 在使用不等於(!= 或者<>)的時候沒法使用索引會致使全表掃描
  • is not null 也沒法使用索引,可是is null是可使用索引的
  • like以通配符開頭('%abc...')mysql索引失效會變成全表掃描的操做
  • 字符串不加單引號索引失效
  • 少用or,用它來鏈接時會索引失效
相關文章
相關標籤/搜索