Mysql索引原理與優化


如何查詢數據?

在這里插入圖片描述
在沒有索引的情況下,如果執行select * from t where age = 22,那么要找到age = 22的數據,則是從上往下一個一個比較,直到第6行才能找到,並且數據庫的文件是存在磁盤上的文件中,所以每次比較都算做一次IO操作,也就是6次IO操作,如果數據量大,可以想象查詢成本將會非常大,這種查詢方式被稱為 全表掃描。索引的出現就是解決這個問題的。

索引的核心——數據結構

二叉樹

二叉樹有以下特點:
1)每個結點最多有兩顆子樹,所以二叉樹中不存在度大於2的結點。
2)左子樹和右子樹是有順序的,次序不能任意顛倒。
3)即使樹中某結點只有一棵子樹,也要區分它是左子樹還是右子樹。
4)左節點要小於父節點,右節點大於或等於父節點

在這里插入圖片描述
現在是加了一種基於二叉樹的實現的索引,再次執行select * from t where age = 22。
這次走索引步驟:
1.比較22和30,22小於30,根據二叉樹特性,走左子樹繼續查詢。
2.比較22和22,相等,拿到22對應的磁盤指針地址0XA1,從磁盤指針對應的數據返回。
數據其他同理,通過上面的操作可以發現,僅僅只進行了2次的查詢IO操作,就得到了數據,即使出現最壞的可能性,要查詢18,也只用走3次IO。可見索引對查詢效率的幫助非常大。

存在的問題:
上圖的二叉樹索引是以age作為索引列的,但是如果使用id作為索引列呢?
因為二叉樹的特性:右節點大於或等於父節點。在極端情況下會導致 樹的傾斜,退化成鏈表了,查詢和全表掃描沒什么區別了,所以mysql並沒有使用這種數據結構
在這里插入圖片描述

紅黑樹(二叉平衡樹)

改善了二叉樹在極端情況下會導致 樹的傾斜的缺陷。
紅黑樹的特性:
(1)每個節點或者是黑色,或者是紅色。
(2)根節點是黑色。
(3)每個葉子節點(NIL)是黑色。 [注意:這里葉子節點,是指為空(NIL或NULL)的葉子節點!]
(4)如果一個節點是紅色的,則它的子節點必須是黑色的。
(5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。[這里指到葉子節點的路徑]
在這里插入圖片描述
紅黑樹有個問題,是如果深度太大,依然效率不高

B樹(多路平衡二叉樹)

特性:
B- Tree
葉節點具有相同的深度,葉節點的指針為空
所有索引元素不重復
節點中的數據索引從左到右遞增排列
在這里插入圖片描述
B樹的數據存儲
在這里插入圖片描述
B樹的問題是:如果數據量大的話單個節點存儲數量過多,效率依然不高

B+樹

MYSQL 給單個節點設置的是16KB大小

為什么用B+而不是B樹?
:如果用B樹,那么每個節點上都存了索引和數據,因為每個節點(一頁)的默認長度是16K,介紹頁的官方文檔。所以每個節點的長度存儲數量就少了,既然少了,那么就會往下一層存,這樣樹的高度就增加,查詢效率下降。
mysql的B+樹,每個非葉子節點都只存儲索引,這樣一個非子節點就可以存很多索引了,樹的高度自然比B數那種方式低了,效率就提上來了,並且把數據只放在最后的節點,這樣的話非葉子節點可以存放更多的索引,如下圖
在這里插入圖片描述
MYSQL事實上的對B+樹進行了一些小改造,標准的B+樹在葉子節點上的單向的指向下一個節點,mysql則對其改造成為雙向指向,並且頭尾相連。
特點:
1.非葉子節點不存儲data,只存儲索引(冗余),可以放更多的索引
2.葉子節點包含所有索引字段
3.葉子節點用指針連接,提高區間訪問的性能
在這里插入圖片描述

存儲引擎

InnoDB(聚集 / 聚簇)

innoDB的介紹和優點官方文檔
聚集索引和輔助索引官方文檔
表數據文件本身就是按B + Tree組織的一個索引|結構文件
聚集索引吐節點包含了完整的數據記錄

在這里插入圖片描述
上圖中有個問題,非葉子節點指向下一個節點的指針是雙向的,圖中畫成了單向的
InnoDB存儲了2個文件
在這里插入圖片描述
**t1.frm:**存儲了表的組成結構
t1.idb: 存儲了數據和索引

MyISAM(非聚集)

MyISAM的介紹和優點官方文檔
MyISAM索引文件和數據文件是分離的(非聚集)

這種存儲引擎在大部分情況下的查詢效率比innoDB要高,如果項目中有表不需要事務且查詢操作多,則可以考慮使用MyISAM。
查詢效率高的原因是
1.因為MyISAM是非聚集索引,根據非聚集索引的特性索引是與實際數據分開的,可以理解為多個索引樹對應一張數據表。查詢操作只用走一次索引樹。而innoDB則是聚集索引,使用主索引和輔助索引,如果使用輔助索引查詢會再走一次主索引(當然,這個並不是絕對的)。

在這里插入圖片描述
t2表存儲的文件,3個
在這里插入圖片描述
t2.frm: 存儲了表的組成結構
t2.MYD:(MyISAM Data)表具體的數據
在這里插入圖片描述

t2.MYI: (MyISAM Index)表索引相關的信息
在這里插入圖片描述
圖中有個問題,非葉子節點指向下一個節點的指針是雙向的,圖中畫成了單向的

兩種的查詢區別

在這里插入圖片描述

索引

為什么使用索引,可以避免全表掃描查找索引
在MYSQL中支持2中索引的類型

索引方法

btree和hash索引的比較官方文檔

1.B Tree (默認)

select * from t where t.id > 6
過程:通過根節點找到6所在的節點,然后返回6這個節點后面的所有數據。因為根據b-tree的特性,索引是有序的。

2.HASH

對索引進行hash運算,直接去查詢,一次就可以查到
select * from t where t.id = 6
這種查詢語句在hash索引時非常有優勢,數據量大也是同樣
但是下面這種就范圍查詢,性能非常差
select * from t where t.id > 6

聯合索引

在這里插入圖片描述
上圖綠色代表索引,紫色代表數據(沒有索引)
綠色的索引為聯合索引

問題

1.為什么InnoDB表必須有主鍵,並且推薦使用整型的自增主鍵?

1.1問:為什么說必須有主鍵?但是建沒有主鍵的又可以建成功呢?
是因為MySql在你沒有指定主鍵的時候,會去每列中找列中沒有重復數據的列,把他作為主鍵
但是如果在所有的列中都沒找到沒有重復數據的列,那么MYSQL會在最后一列再添加一列作為索引列(隱藏的),由MYSQL去維護這一列

1.2問:那為什么推薦使用整型的自增主鍵?
因為在使用索引定位數據時,會進行索引的比較,確定范圍。
原因1:
使用UUID,那么會先把字符進行轉換,再去比對,消耗了時間。
使用整型,那么就直接進行比對,沒有字符轉換的過程。
原因2:
UUID占用空間比整型大,大數據量大的情況下,差距就明顯了

1.3為什么推薦使用自增主鍵?
如果使用UUID這種非自增主鍵,那么在放與一條數據到一個非葉子節點是時候,需要要葉子節點里面插入,那如果這個葉子節點滿了呢?那么就會打散這個葉子節點,還有可能觸發重新樹平衡。
而使用自增主鍵,那么會很明確的直接在索引索引的最后面區插入,不存在打破其他的節點,因為根據b-tree的特性,索引是有序排列的。所以推薦使用自增主鍵

2.聯合索引底層的數據結構是什么樣
在這里插入圖片描述
如果是排序查詢的畫,先比較第一個字段,依次再比較后面的字段
3.重復率高的字段到底該不該建索引
一個數據表保存的聚簇索引如下:包含了 id ,姓名,性別字段(性別0/1表示)
在這里插入圖片描述
假如給性別字段建立了索引,下圖為性別字段的輔助索引
在這里插入圖片描述
現在 發送一條sql : select * from table where sex=0;
4秒返回數據
過程:通過sex輔助索引找到所有滿足sex=0的聚簇索引的id,在通過id去主索引表中找完整的數據。假如找到了50W條數據,從第一條開始,第一條的主索引的id為2,去主索引中找id=2的數據,然后返回,這樣反復操作50W次就返回了上面那條SQL的查詢結果。

再發送一條禁用性別索引的sql:select * from table ignore index(sex_index) where sex=0;
2秒返回數據
過程:全表掃描匹配sex=0,就是那種最笨的方式,一條一條的比。

經過測試,在50W的數據量中,禁用索引反而比加了索引的查詢速度快了不是。
為什么?
通過上面2條SQL的執行過程就可以看出來,使用了索引的會先走輔助索引,然后走主索引,增加了大量的IO開銷,而沒有使用索引的,直接查詢主索引,雖然會多出無用的對比過程,但是相比增加IO開銷,是值得的。

SQL優化

解析過程
from>on> join> where> group by> having> select> dinstinct> order by >limit
sql優化主要就是優化索引

1.定位慢sql

SHOW VARIABLES LIKE "%query%"

在這里插入圖片描述
打開慢sql記錄

SET GLOBAL slow_query_log = ON

 

方便測試修改配置,把超過1秒的查詢,認定為慢sql,這個配置需要重連數據庫后生效

SET GLOBAL long_query_time = 1

 

下面這條sql查詢 慢sql查詢的次數

SHOW STATUS LIKE "%slow_queries%"

 

在這里插入圖片描述

打開慢日志文件C:\Program Files\mysql-5.7.28-winx64\data\HaseeNotBook-slow.log

# Time: 2020-05-31T07:55:03.875274Z
# User@Host: root[root] @ localhost [::1]  Id:    44
# Query_time: 7.427691  Lock_time: 0.000089 Rows_sent: 1000  Rows_examined: 1071001
SET timestamp=1590911703;
select age from t1 order by age LIMIT 0, 1000;

 

2.EXPLAIN分析SQL

explan輸出格式官方文檔

EXPLAIN SELECT NAME FROM t1 ORDER BY NAME

 

在這里插入圖片描述

type列

性能排序
依次從最優到最差分別為:system > const > eq_ref > ref > range > index > ALL
一般查詢達到range級別,最好達到ref

通過Explain分析SQL type為All ,結合性能排序,發現all是排在最后的最小的,all是一個全表掃描,

NULL:mysql能夠在優化階段分解查詢語句,在執行階段用不着再訪問表或索引。例如:在 索引列中選取最小值,可以單獨查找索引來完成,不需要在執行時訪問表

select min(id) from test_table; 

 

const, system:mysql能對查詢的某部分進行優化並將其轉化成一個常量(可以看show warnings 的結果)。用於 primary key 或 unique key 的所有列與常數比較時,所以表最多 有一個匹配行,讀取1次,速度比較快。system是const的特例,表里只有一條元組匹配時為 system

select * from test_table where id = 1

 

eq_ref:primary key 或 unique key 索引的所有部分被連接使用 ,最多只會返回一條符合 條件的記錄。這可能是在 const 之外最好的聯接類型了,簡單的 select 查詢不會出現這種 type。

select * from test_table  
left join test1_table
on test_table.id = test1_table.id

 

ref:相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前綴,索引要 和某個值相比較,可能會找到多個符合條件的行。

# age 有輔助索引
select * from test_table where age= 19

 

range:范圍掃描通常出現在 in(), between ,> ,<, >= 等操作中。使用一個索引來檢索給定 范圍的行。

# age 有輔助索引
select * from test_table where age> 19

 

index:掃描全表索引,這通常比ALL快一些。 一般在輔助索引中存在

# age 有輔助索引
select age from test_table

 

ALL:即全表掃描,意味着mysql需要從頭到尾去查找所需要的行。通常情況下這需要增加索引來進行優化

# sex 沒有任何索引
select * from test_table where sex = 1

 

Extra列

在這里插入圖片描述

3.修改SQL,盡量讓SQL走索引

1.添加索引

ALTER TABLE t1 ADD INDEX idx_name(NAME)

 

在這里插入圖片描述
現在查詢走索引了,因為是B+樹,所有的葉子節點都是鏈表連起來的,所以這種排序查詢可以完美走鏈表

2.測試索引
默認sql查詢優化器會自動選擇效率最高的索引,但也有例外,所以可以使用強制使用某個索引去測試哪個索引效率更高。

SELECT NAME FROM t1  FORCE INDEX(idx_name)

 

SQL索引匹配

最左前綴法則

一定要按照聯合索引的順序去查
在這里插入圖片描述
上圖建了一個名稱為 idx_name_age_position 的聯合索引,包含字段name、age、position

使用explain 分析3條sql.
第一條不會走索引,全表掃描
第二條不會走索引,全表掃描
第三條會走索引

不在索引列上做任何操作(計算、函數、類型轉換)

MYSQL只要看到字段上有函數直接不走索引

EXPLAIN SELECT * FROM employees WHERE name = 'LiLei'; 
EXPLAIN SELECT * FROM employees WHERE left(name,3) = 'LiLei';

//給 hire_time 建一個索引 ,date()函數計算不會走索引
EXPLAIN select * from employees where date(hire_time) ='2018-09-30';
//去掉date()函數計算,該外范圍,會走索引
EXPLAIN select * from employees where hire_time >='2018-09-30 00:00:00' and hire_time <='2018-09-30 23:59:59';

 

存儲引擎不能使用索引中范圍條件右邊的列

// position ='manager' 這個將不會走索引,只有name、age走索引了
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age > 22 AND position ='manager';

 

盡量使用覆蓋索引(只訪問索引的查詢(索引列包含查詢列)),減少select *語句

create table person(
    id int not null auto_increment,
    name varchar(50) not null,
    job1 varchar(50) not null,
    job2 varchar(50) not null,
    job3 varchar(50) not null,
    
    primary key id,
    key p_idx_j1(job1,job2,job3)
);

 

數據是:

1,ermao,programmer,doctor,teacher
2,haha,engineer,boss,star

這種數據結構,如果想查詢job1,job2,job3,用 select * from person where job2 = 'boss’是不會走二級索引的,只能在聚集索引的那顆B+樹上做全表掃描,也就是最慢的一種查詢方式。

如果是select job1,job2,job3 from person where job2 = 'boss’這種方式查詢呢?是不是認為job2在聯合索引的中間,所以並不會走二級索引。其實它是會走二級索引的那個B+樹的。我們知道在mysql中(Innodb引擎)聚集索引是一定存在的,如果表結構中定義了主鍵,聚集索引就根據主鍵建立,否則如果有唯一列就用唯一列,否則就會自動在每條記錄中生成一個隱藏的ID列並以此建立聚集索引,聚集索引的葉子節點就是真實的數據。
比如此數據結構,就會有兩棵B+樹,一顆是聚集索引,另一顆是job1,job2,job3定義的聯合索引(二級索引)。二級所索引的葉子節點是索引列加ID,所以走索引的大致流程分兩步,一是先根據索引列在二級索引樹中找到ID,然后根據ID在聚集索引中找到對應的記錄(這一步也稱為回表)。
有沒有發現問題?也就是二級索引中其實已經包含了job1,job2,job3這三列的數據。那如果我查詢語句中查詢的就是這三列的數據,並且搜索條件中也是這三列中的一列,那我還有必要分兩步操作嗎?還有必要進行回表操作嗎?我只需要在二級索引樹中進行搜索即可。所以對於這種情況,如果用select job1,job2,job3 from person where job2 = 'boss’這種指定列的查詢只需走二級索引,無需回表,並且二級索引的每條記錄不含有隱藏列,加載內存的操作會更快。而如果用select * 的話,因為二級索引樹中並沒有name那一列,所以根本走不了二級索引,只能對聚集索引進行全表掃描,在數據兩大的情況下,性能影響還是很可觀的。

使用!=、<>、is null,is not null的時候無法使用索引會導致全表掃描

EXPLAIN SELECT * FROM employees WHERE name != ‘LiLei’;
EXPLAIN SELECT * FROM employees WHERE name is null

 

like以通配符開頭(’$abc…’)mysql索引失效會變成全表掃描操作

%通配符在后面不會走索引,在前面會走索引

EXPLAIN SELECT * FROM employees WHERE name like '%Lei'
//這條會走索引
EXPLAIN SELECT * FROM employees WHERE name like 'Lei%'

 

解決like’%字符串%'索引不被使用的方法?
使用覆蓋索引,查詢字段必須是建立覆蓋索引字段

// name,age,position 為聯合索引
 EXPLAIN SELECT name,age,position FROM employees WHERE name like '%Lei%';

字符串不加單引號索引失效

//會走索引
EXPLAIN SELECT * FROM employees WHERE name = '1000';
//不會走索引,name是字符串,1000是數字,被類型轉換
EXPLAIN SELECT * FROM employees WHERE name = 1000;

 

少用or或in

用它查詢時,mysql不一定使用索引,mysql內部優化器會根據檢索比例、 表大小等多個因素整體評估是否使用索引,詳見范圍查詢優化

EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' or name = 'HanMeimei';

 

范圍查詢優化

//給年齡添加單值索引
ALTER TABLE `employees` 
ADD INDEX `idx_age` (`age`) USING BTREE ;

//下面這條可能不會走索引
explain select * from employees where age >=1 and age <=2000;
//沒走索引原因:mysql內部優化器會根據檢索比例、表大小等多個因素整體評估是否使用索 引。
//比如這個例子,可能是由於單次數據量查詢過大導致優化器最終選擇不走索引 優化方法:
//可以講大的范圍拆分成多個小范圍
explain select * from employees where age >=1 and age <=1000;

 

索引使用總結

在這里插入圖片描述

MYSQL優化實戰

//不走索引
EXPLAIN select * from employees where name > 'a';
//走索引
EXPLAIN select * from employees where name > 'zzz' ;

 

對於上面這兩種 name>‘a’ 和 name>‘zzz’ 的執行結果,mysql最終是否選擇走索引或者一張表涉及多個索引,mysql最 終如何選擇索引,我們可以用trace工具來一查究竟,開啟trace工具會影響mysql性能,所以只能臨時分析sql使用,用 完之后立即關閉

二 limit

例如

EXPLAIN  
select * from zipkin_spans  limit 100000,10

 

在這里插入圖片描述
可以看到limit 是全表掃描的,如果沒有指定排序字段,則是按照插入順序排序。因為沒有任何一個索引去維護插入順序,所以從主索引中進行全表掃描。
加order by

# start_ts 是一個輔助索引字段
#走start_ts 索引
EXPLAIN  
select * from zipkin_spans ORDER BY start_ts limit 10,100
#不走start_ts 索引,全表掃描
EXPLAIN  
select * from zipkin_spans ORDER BY start_ts limit 100000,100

 

上面這兩條的區別僅僅是分頁開始的位置不一樣,為什么一個走索引一個不走索引呢?現在又回到上面那個問題 “重復率高的字段到底該不該建索引?”,和這個原因一樣,mysql認為 不走索引反而效率更高。

那如何優化分頁?
現在我們只是需要從莫一個位置開始后面的100條數據,也就是只要100條數據,並且是根據start_ts進行排序的。可以利用start_ts的輔助索引加上分頁排序去查詢主鍵索引返回100條記錄,然后用100條記錄的主鍵索引在去查詢數據。這樣就可走主索引進行分頁,不再進行全表掃描。

# start_ts 是一個輔助索引字段
EXPLAIN  
select * from zipkin_spans z1
inner join(select id from zipkin_spans ORDER BY start_ts limit 200000,100) z2
on z1.id = z2.id

 

優化總結

1、MySQL支持兩種方式的排序filesort和index,Using index是指MySQL掃描索引本身完成排序。index 效率高,filesort效率低。
2、order by滿足兩種情況會使用Using index。 ①order by語句使用索引最左前列。 ②使用where子句與order by子句條件列組合滿足索引最左前列。
3、盡量在索引列上完成排序,遵循索引建立(索引創建的順序)時的最左前綴法則。
4、如果order by的條件不在索引列上,就會產生Using filesort。
5、能用覆蓋索引盡量用覆蓋索引
6、group by與order by很類似,其實質是先排序后分組,遵照索引創建順 序的最左前綴法則。對於group by的優化如果不需要排序的可以加上order by null禁止排序。注意,where高於having,能寫在where中 的限定條件就不要去having限定了。

MYSQL5.7官方文檔

MYSQL5.7官方文檔
聚集索引和輔助索引官方文檔
優化和索引官方文檔
btree和hash索引的比較官方文檔
explan查詢計划官方文檔
explan輸出格式官方文檔

innoDB的介紹和優點官方文檔
MyISAM的介紹和優點官方文檔

轉載於:https://blog.csdn.net/qq_40690648/article/details/106445034


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM