MySQL索引詳解

SQL優化主要就是在優化索引。

索引的弊端:
            1.索引本身很大, 可以存放在內存/硬盤(通常爲硬盤)
            2.索引不是所有情況均適用: a.少量數據  b.頻繁更新的字段   c.很少使用的字段
            3.索引會降低增刪改的效率(增刪改查)

優勢:1.提高查詢效率(降低IO使用率)
           2.降低CPU使用率 (...order by age desc,因爲 B樹索引 本身就是一個 好排序的結構,因此在排序時  可以直接使用)

索引分類:

       主鍵索引  :不能重複。id    不能是null
        唯一索引  :不能重複。id    可以是null
        單值索引  :單列, age ;一個表可以多個單值索引,name。
        複合索引  :多個列構成的索引 (相當於 二級目錄 )  (name,age)   (a,b,c,d,...,n)

創建索引:

        方式一:create 索引類型  索引名  on 表(字段)

        單值索引:create index  dept_index on  tb(dept);
        唯一索引:create unique index  name_index on tb(name) ;
        複合索引:create index dept_name_index on tb(dept,name);

        方式二:alter table 表名 索引類型  索引名(字段)

        單值索引:alter table tb add index dept_index(dept) ;
        唯一索引:alter table tb add unique index name_index(name);
        複合索引:alter table tb add index dept_name_index(dept,name);

        注意:如果一個字段是primary key,則改字段默認就是主鍵索引

刪除索引:
        drop index 索引名 on 表名 ;
        drop index name_index on tb ;

查詢索引:
        show index from 表名 ;
        show index from 表名 \G;

SQL性能分析: 

查詢執行計劃: explain +SQL語句
                          explain  select  * from tb ;可以模擬SQL優化器執行SQL語句,從而讓開發人員知道自己編寫的SQL狀況

優化方法,官網:https://dev.mysql.com/doc/refman/5.5/en/optimization.html

 id : 標識符                
 select_type :查詢類型
 table :輸出行表                                                                                                                                                                           partitions:匹配的分區
 type :聯接類型
 possible_keys :可能的索引選擇
 key  :實際使用的索引
 key_len :所選鍵的長度     
 ref  :與索引比較的列
 rows :估計要檢查的行
 filtered:按表條件過濾的行百分比
 Extra :額外的信息

部分列詳細解釋

  1. id: id值相同,從上往下 順序執行,數據小的表 優先查詢;                                                                                                            id值不同:id值越大越優先查詢;
  2. select_type:

PRIMARY:包含子查詢SQL中的 主查詢 (最外層)
SUBQUERY:包含子查詢SQL中的 子查詢 (非最外層)
simple:簡單查詢(不包含子查詢、union)
derived:衍生查詢(使用到了臨時表)
    a.在from子查詢中只有一張表
        explain select  cr.cname  from ( select * from course where tid in (1,2) ) cr                                                                                    b.在from子查詢中, 如果有table1 union table2 ,則table1 就是derived,table2就是union
        explain select  cr.cname  from ( select * from course where tid = 1  union select * from course where tid = 2 ) cr ;
union:上例
union result :從 union 臨時表檢索結果的 select

3.type:索引類型

依次從最優到最差分別爲:system>const>eq_ref>ref>range>index>all ,要對type進行優化的前提:有索引 。                                                                        其中:system,const只是理想情況;實際能達到 ref>range ;                                                                                                            system(忽略): 只有一條數據的系統表 ;或衍生表只有一條數據的主查詢。

const:僅僅能查到一條數據的SQL ,用於Primary key 或unique索引  (類型與索引類型有關)
explain select tid from test01 where tid =1 ;
alter table test01 drop primary key ;
create index test01_index on test01(tid) ;

eq_ref:唯一性索引:對於每個索引鍵的查詢,返回匹配唯一行數據(有且只有1個,不能多 、不能0)
select ... from ..where name = ... .常見於唯一索引和主鍵索引。

 alter table teacherCard add constraint pk_tcid primary key(tcid);
alter table teacher add constraint uk_tcid unique index(tcid) ;


explain select t.tcid from teacher t,teacherCard tc where t.tcid = tc.tcid ;

以上SQL,用到的索引是 t.tcid,即teacher表中的tcid字段;
如果teacher表的數據個數和連接查詢的數據個數一致(都是3條數據),則有可能滿足eq_ref級別;否則無法滿足。


ref:非唯一性索引,對於每個索引鍵的查詢,返回匹配的所有行(0行或者多行)
準備數據:
 insert into teacher values(4,'tz',4) ;
 insert into teacherCard values(4,'tz222');

測試:
alter table teacher add index index_name (tname) ;
explain select * from teacher  where tname = 'tz';


range:檢索指定範圍的行 ,where後面是一個範圍查詢(between  ,> < >=,     特殊:in有時候會失效 ,從而轉爲無索引all)
alter table teacher add index tid_index (tid) ;
explain select t.* from teacher t where t.tid in (1,2) ;
explain select t.* from teacher t where t.tid <3 ;

index:查詢全部索引中數據
explain select tid from teacher ; --tid 是索引, 只需要掃描索引表,不需要所有表中的所有數據


all:查詢全部表中的數據
explain select cid from course ;  --cid不是索引,需要全表掃描,即需要所有表中的所有數據


system/const: 結果只有一條數據
eq_ref:結果多條;但是每條數據是唯一的 ;
ref:結果多條;但是每條數據是是0或多條 ;

4.rows: 被索引優化查詢的數據個數 (實際通過索引而查詢到的數據個數)
    explain select * from course c,teacher t  where c.tid = t.tid
    and t.tname = 'tz' ;

5.key_len:這一列顯示了mysql在索引裏使用的字節數,通過這個值可以算出具體使用了索引中的哪些列

key_len計算規則如下:
字符串
char(n):n字節長度
varchar(n):2字節存儲字符串長度,如果是utf-8,則長度 3n + 2
數值類型
tinyint:1字節
smallint:2字節
int:4字節
bigint:8字節  
時間類型 
date:3字節
timestamp:4字節
datetime:8字節
如果字段允許爲 NULL,需要1字節記錄是否爲 NULL
索引最大長度是768字節,當字符串過長時,mysql會做一個類似左前綴索引的處理,將前半部分的字符提取出來做索引。
 

6. Extra:
    (i).using filesort : 性能消耗大;需要“額外”的一次排序(查詢)  。常見於 order by 語句中。
        explain select * from test02 where a1 ='' order by a2 ; --using filesort
       小結:對於單索引, 如果排序和查找是同一個字段,則不會出現using filesort;如果排序和查找不是同一個字段,則會出現         using filesort;
       避免: where哪些字段,就order by哪些字段


複合索引:不能跨列(最佳左前綴)
drop index idx_a1 on test02;
drop index idx_a2 on test02;
drop index idx_a3 on test02;

alter table test02 add index idx_a1_a2_a3 (a1,a2,a3) ;
explain select *from test02 where a1='' order by a3 ;  --using filesort
explain select *from test02 where a2='' order by a3 ; --using filesort
explain select *from test02 where a1='' order by a2 ;
explain select *from test02 where a2='' order by a1 ; --using filesort
    小結:避免: where和order by 按照複合索引的順序使用,不要跨列或無序使用。

    (ii). using temporary:性能損耗大 ,用到了臨時表。一般出現在group by 語句中。
    explain select a1 from test02 where a1 in ('1','2','3') group by a1 ;
    explain select a1 from test02 where a1 in ('1','2','3') group by a2 ; --using temporary
    避免:查詢那些列,就根據那些列 group by .

    (iii). using index :性能提升; 索引覆蓋(覆蓋索引)。原因:不讀取原文件,只從索引文件中獲取數據 (不需要回表查詢)
        只要使用到的列 全部都在索引中,就是索引覆蓋using index

    例如:test02表中有一個複合索引(a1,a2,a3)
        explain select a1,a2 from test02 where a1='' or a2= '' ; --using index   
        
        drop index idx_a1_a2_a3 on test02;

        alter table test02 add index idx_a1_a2(a1,a2) ;
        explain select a1,a3 from test02 where a1='' or a3= '' ;

        
        如果用到了索引覆蓋(using index時),會對 possible_keys和key造成影響:
        a.如果沒有where,則索引只出現在key中;
        b.如果有where,則索引 出現在key和possible_keys中。

        explain select a1,a2 from test02 where a1='' or a2= '' ;
        explain select a1,a2 from test02  ;

    (iii).using where (需要回表查詢)
        假設age是索引列
        但查詢語句select age,name from ...where age =...,此語句中必須回原表查Name,因此會顯示using where.
        
    explain select a1,a3 from test02 where a3 = '' ; --a3需要回原表查詢
    

    (iv). impossible where :where子句永遠爲false
        explain select * from test02 where a1='x' and a1='y'  ;

索引數據結構:

  1. 二叉樹
  2. 紅黑樹
  3. Hash表
  4. B-Tree

根據這個鏈接可以模仿這些如何進行增加節點,轉換的:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

爲什麼索引會採用B+Tree數據結果呢?以下分析:

二叉樹,在某種情況下回會退化成鏈表結構:

紅黑樹會進行自適應,進行平衡,但是數據量大的時候也會出現問題,深度過高,會增加I/O次數:

hash表:hash查詢速度也快,但是進行範圍查找就不行。

B-Tree:橫向擴展,高度低。(橫向查找在同一頁中,不需要更多的磁盤I/O,內存中的遍歷速度可以忽略不計)

B+Tree:有指針,能很好的支撐範圍查找。(葉子節點中其實是雙向指針)

索引最佳實踐
使用的表
CREATE TABLE `employees` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` int(11) NOT NULL DEFAULT '0' COMMENT '年齡',
  `position` varchar(20) NOT NULL DEFAULT '' COMMENT '職位',
  `hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入職時間',
  PRIMARY KEY (`id`),
  KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='員工記錄表';

INSERT INTO employees(name,age,position,hire_time) VALUES('LiLei',22,'manager',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('HanMeimei', 23,'dev',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());

最佳實踐
1. 全值匹配
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei';
 

EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22;
 

EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';
 

2.最佳左前綴法則
 如果索引了多列,要遵守最左前綴法則。指的是查詢從索引的最左前列開始並且不跳過索引中的列。
EXPLAIN SELECT * FROM employees WHERE age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE position = 'manager';
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
 

3.不在索引列上做任何操作(計算、函數、(自動or手動)類型轉換),會導致索引失效而轉向全表掃描
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
EXPLAIN SELECT * FROM employees WHERE left(name,3) = 'LiLei';
 

4.存儲引擎不能使用索引中範圍條件右邊的列
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age > 22 AND position ='manager';
 

5.儘量使用覆蓋索引(只訪問索引的查詢(索引列包含查詢列)),減少select *語句
EXPLAIN SELECT name,age FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
 
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
 

6.mysql在使用不等於(!=或者<>)的時候無法使用索引會導致全表掃描
EXPLAIN SELECT * FROM employees WHERE name != 'LiLei'
 

7.is null,is not null 也無法使用索引
EXPLAIN SELECT * FROM employees WHERE name is null
 

8.like以通配符開頭('$abc...')mysql索引失效會變成全表掃描操作
EXPLAIN SELECT * FROM employees WHERE name like '%Lei'
 
EXPLAIN SELECT * FROM employees WHERE name like 'Lei%'
 

問題:解決like'%字符串%'索引不被使用的方法?
a)使用覆蓋索引,查詢字段必須是建立覆蓋索引字段
EXPLAIN SELECT name,age,position FROM employees WHERE name like '%Lei%';
 
b)當覆蓋索引指向的字段是varchar(380)及380以上的字段時,覆蓋索引會失效!

9.字符串不加單引號索引失效
EXPLAIN SELECT * FROM employees WHERE name = '1000';
EXPLAIN SELECT * FROM employees WHERE name = 1000;
 

10.少用or,用它連接時很多情況下索引會失效
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' or name = 'HanMeimei';

like KK%相當於=常量,%KK和%KK% 相當於範圍
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章