前言
??本文是《java拉勾高薪訓練營》中的mysql章節的內容回顧復習,主要是對MySql的索引原理進行復習整理,以便日常回顧
1 索引類型
??索引可以提升查詢速度,會影響where查詢,以及order by排序
??MySQL索引類型如下:
■ 從索引存儲結構劃分:B Tree索引、Hash索引、FULLTEXT全文索引、R Tree索引
■ 從應用層次劃分:普通索引、唯一索引、主鍵索引、復合索引
■ 從索引鍵值類型劃分:主鍵索引、輔助索引(二級索引)
■ 從數據存儲和索引鍵值邏輯關系劃分:聚集索引(聚簇索引)、非聚集索引(非聚簇索引)
1.1 普通索引
??最基本的索引類型,基于普通字段創建的索引,沒有任何限制。
??創建普通索引的方法如下:
■ CREATE INDEX <索引的名字> ON tablename (字段名);
■ ALTER TABLE tablename ADD INDEX [索引的名字] (字段名);
■ CREATE TABLE tablename ( [...], INDEX [索引的名字] (字段名) );
1.2 唯一索引
??與"普通索引"類似,不同的就是:索引字段的值必須唯一,但允許有空值 。
??在創建或修改表時追加唯一約束,就會自動創建對應的唯一索引。
??創建唯一索引的方法如下:
■ CREATE UNIQUE INDEX <索引的名字> ON tablename (字段名);
■ ALTER TABLE tablename ADD UNIQUE INDEX [索引的名字] (字段名);
■ CREATE TABLE tablename ( [...], UNIQUE [索引的名字] (字段名) ;
1.3 主鍵索引
??它是一種特殊的唯一索引,不允許有空值。
??在創建或修改表時追加主鍵約束即可,每個表只能有一個主鍵。
??創建主鍵索引的方法如下:
■ CREATE TABLE tablename ( [...], PRIMARY KEY (字段名) );
■ ALTER TABLE tablename ADD PRIMARY KEY (字段名);
1.4 復合索引
??單一索引是指索引列為一列的情況,即新建索引的語句只實施在一列上;用戶可以在多個列上建立索引,這種索引叫做組復合索引(組合索引)。
??復合索引可以代替多個單一索引,相比多個單一索引復合索引所需的開銷更小。
??索引同時有兩個概念叫做窄索引和寬索引,窄索引是指索引列為1-2列的索引,寬索引也就是索引列超過2列的索引,設計索引的一個重要原則就是能用窄索引不用寬索引,因為窄索引往往比組合索引更有效。
??創建組合索引的方法如下:
■ CREATE INDEX <索引的名字> ON tablename (字段名1,字段名2...);
■ ALTER TABLE tablename ADD INDEX [索引的名字] (字段名1,字段名2...);
■ CREATE TABLE tablename ( [...], INDEX [索引的名字] (字段名1,字段名2...) );
??復合索引使用注意事項:
■ 何時使用復合索引,要根據where條件建索引,注意不要過多使用索引,過多使用會對更新操作效率有很大影響。
■ 如果表已經建立了(col1,col2),就沒有必要再單獨建立(col1);
■ 如果現在有(col1)索引,如果查詢需要col1和col2條件,可以建立(col1,col2)復合索引,對于查詢有一定提高。
1.5 全文索引
??查詢操作在數據量比較少時,可以使用like模糊查詢,但是對于大量的文本數據檢索,效率很低。如果使用全文索引,查詢速度會比like快很多倍。
??在MySQL 5.6 以前的版本,只有MyISAM存儲引擎支持全文索引,從MySQL 5.6開始MyISAM和InnoDB存儲引擎均支持。
??創建全文索引的方法如下:
■ CREATE FULLTEXT INDEX <索引的名字> ON tablename (字段名);
■ ALTER TABLE tablename ADD FULLTEXT [索引的名字] (字段名);
■ CREATE TABLE tablename ( [...], FULLTEXT KEY [索引的名字] (字段名) ;
??和常用的like模糊查詢不同,全文索引有自己的語法格式,使用 match 和 against 關鍵字,比如
select * from user where match(name) against('aaa');
??全文索引使用注意事項:
??■ 全文索引必須在字符串、文本字段上建立。
??■ 全文索引字段值必須在最小字符和最大字符之間的才會有效。(innodb:3-84;myisam:4-84)
??■ 全文索引字段值要進行切詞處理,按syntax字符進行切割,例如b+aaa,切分成b和aaa
??■ 全文索引匹配查詢,默認使用的是等值匹配,例如a匹配a,不會匹配ab,ac。如果想匹配可以在布爾模式下搜索a*
select * from user where match(name) against('a*' in boolean mode);
2 索引原理
??MySQL官方對索引定義:是存儲引擎用于快速查找記錄的一種數據結構。需要額外開辟空間和數據維護工作。
??■ 索引是物理數據頁存儲,在數據文件中(InnoDB,ibd文件),利用數據頁(page)存儲。
??■ 索引可以加快檢索速度,但是同時也會降低增刪改操作速度,索引維護需要代價。
??索引涉及的理論知識:二分查找法、Hash和B+Tree
2.1 二分查找法
??二分查找法也叫作折半查找法,它是在有序數組中查找指定數據的搜索算法。它的優點是等值查詢、范圍查詢性能優秀,缺點是更新數據、新增數據、刪除數據維護成本高。
2.2 Hash結構
??Hash底層實現是由Hash表來實現的,是根據鍵值 <key,value> 存儲數據的結構。非常適合根據key查找value值,也就是單個key查詢,或者說等值查詢。其結構如下所示:
??從上面結構可以看出,Hash索引可以方便的提供等值查詢,但是對于范圍查詢就需要全表掃描了。
??Hash索引在MySQL 中Hash結構主要應用在Memory原生的Hash索引 、InnoDB 自適應哈希索引。
??InnoDB提供的自適應哈希索引功能強大,接下來重點描述下InnoDB 自適應哈希索引。
??InnoDB自適應哈希索引是為了提升查詢效率,InnoDB存儲引擎會監控表上各個索引頁的查詢,當InnoDB注意到某些索引值訪問非常頻繁時,會在內存中基于B+Tree索引再創建一個哈希索引,使得內存中的 B+Tree 索引具備哈希索引的功能,即能夠快速定值訪問頻繁訪問的索引頁
??InnoDB自適應哈希索引:在使用Hash索引訪問時,一次性查找就能定位數據,等值查詢效率要優于B+Tree。
??自適應哈希索引的建立使得InnoDB存儲引擎能自動根據索引頁訪問的頻率和模式自動地為某些熱點頁建立哈希索引來加速訪問。
??另外InnoDB自適應哈希索引的功能,用戶只能選擇開啟或關閉功能,無法進行人工干涉。
show engine innodb status \G;
show variables like '%innodb_adaptive%';
2.3 B+Tree結構
??MySQL數據庫索引采用的是B+Tree結構,在B-Tree結構上做了優化改造。
??B-Tree結構
■ 索引值和data數據分布在整棵樹結構中
■ 每個節點可以存放多個索引值及對應的data數據
■ 樹節點中的多個索引值從左到右升序排列
??B樹的搜索:從根節點開始,對節點內的索引值序列采用二分法查找,如果命中就結束查找。沒有命中會進入子節點重復查找過程,直到所對應的的節點指針為空,或已經是葉子節點了才結束。
??B+Tree結構
■ 非葉子節點不存儲data數據,只存儲索引值,這樣便于存儲更多的索引值
■ 葉子節點包含了所有的索引值和data數據
■ 葉子節點用指針連接,提高區間的訪問性能
??相比B樹,B+樹進行范圍查找時,只需要查找定位兩個節點的索引值,然后利用葉子節點的指針進行遍歷即可。而B樹需要遍歷范圍內所有的節點和數據,顯然B+Tree效率高。
2.4 聚簇索引和輔助索引
??聚簇索引和非聚簇索引:B+Tree的葉子節點存放主鍵索引值和行記錄就屬于聚簇索引;如果索引值和行記錄分開存放就屬于非聚簇索引。
??主鍵索引和輔助索引:B+Tree的葉子節點存放的是主鍵字段值就屬于主鍵索引;如果存放的是非主鍵值就屬于輔助索引(二級索引)。
在InnoDB引擎中,主鍵索引采用的就是聚簇索引結構存儲。
2.4.1 聚簇索引(聚集索引)
??聚簇索引是一種數據存儲方式,InnoDB的聚簇索引就是按照主鍵順序構建 B+Tree結構。
??B+Tree的葉子節點就是行記錄,行記錄和主鍵值緊湊地存儲在一起。 這也意味著 InnoDB 的主鍵索引就是數據表本身,它按主鍵順序存放了整張表的數據,占用的空間就是整個表數據量的大小。
??通常說的主鍵索引就是聚集索引。
??InnoDB的表要求必須要有聚簇索引:
■ 如果表定義了主鍵,則主鍵索引就是聚簇索引
■ 如果表沒有定義主鍵,則第一個非空unique列作為聚簇索引
■ 否則InnoDB會從建一個隱藏的row-id作為聚簇索引
2.4.2 輔助索引
??InnoDB輔助索引,也叫作二級索引,是根據索引列構建 B+Tree結構。但在 B+Tree 的葉子節點中只存了索引列和主鍵的信息。
??二級索引占用的空間會比聚簇索引小很多, 通常創建輔助索引就是為了提升查詢效率。
??一個表InnoDB只能創建一個聚簇索引,但可以創建多個輔助索引
2.4.3 非聚簇索引
??與InnoDB表存儲不同,MyISAM數據表的索引文件和數據文件是分開的,被稱為非聚簇索引結構。
3 索引分析與優化
3.1 EXPLAIN
??MySQL 提供了一個 EXPLAIN 命令,它可以對 SELECT 語句進行分析,并輸出 SELECT 執行的詳細信息,供開發人員有針對性的優化。例如:
EXPLAIN SELECT * from user WHERE id < 3;
EXPLAIN 命令的輸出內容大致如下:
(1)select_type
?? 表示查詢的類型。常用的值如下:
■ SIMPLE : 表示查詢語句不包含子查詢或union
■ PRIMARY:表示此查詢是最外層的查詢
■ UNION:表示此查詢是UNION的第二個或后續的查詢
■ DEPENDENT UNION:UNION中的第二個或后續的查詢語句,使用了外面查詢結果
■ UNION RESULT:UNION的結果
■ SUBQUERY:SELECT子查詢語句
■ DEPENDENT SUBQUERY:SELECT子查詢語句依賴外層查詢的結果。
(2)type
??表示存儲引擎查詢數據時采用的方式。比較重要的一個屬性,通過它可以判斷出查詢是全表掃描還是基于索引的部分掃描。
??常用屬性值如下,從上至下效率依次增強。
■ ALL:表示全表掃描,性能最差。
■ index:表示基于索引的全表掃描,先掃描索引再掃描全表數據。
■ range:表示使用索引范圍查詢。使用>、>=、<、<=、in等等。
■ ref:表示使用非唯一索引進行單值查詢。
■ eq_ref:一般情況下出現在多表join查詢,表示前面表的每一個記錄,都只能匹配后面表的一行結果。
■ const:表示使用主鍵或唯一索引做等值查詢,常量查詢。
■ NULL:表示不用訪問表,速度最快。
(3)possible_keys
??表示查詢時能夠使用到的索引。注意并不一定會真正使用,顯示的是索引名稱。
(4)key
??表示查詢時真正使用到的索引,顯示的是索引名稱。
(5)rows
??MySQL查詢優化器會根據統計信息,估算SQL要查詢到結果需要掃描多少行記錄。
??原則上rows是越少效率越高,可以直觀的了解到SQL效率高低。
(6)key_len
??表示查詢使用了索引的字節數量。可以判斷是否全部使用了組合索引。
key_len的計算規則如下:
■ 字符串長度跟字符集有關:latin1=1、gbk=2、utf8=3、utf8mb4=4
??char(n):n*字符集長度
??varchar(n):n * 字符集長度 + 2字節
■ 數值類型
??TINYINT:1個字節
??SMALLINT:2個字節
??MEDIUMINT:3個字節
??INT、FLOAT:4個字節
??BIGINT、DOUBLE:8個字節
■ 時間類型
??DATE:3個字節
??TIMESTAMP:4個字節
??DATETIME:8個字節
■ 字段屬性
??NULL屬性占用1個字節,如果一個字段設置了NOT NULL,則沒有此項
(7)Extra
??Extra表示很多額外的信息,各種操作會在Extra提示相關信息,常見幾種如下:
■ Using where
??表示查詢需要通過索引回表查詢數據。
■ Using index
??表示查詢需要通過索引,索引就可以滿足所需數據。
■ Using filesort
??表示查詢出來的結果需要額外排序,數據量小在內存,大的話在磁盤,因此有Using filesort,建議優化。
■ Using temprorary
??查詢使用到了臨時表,一般出現于去重、分組等操作。
3.2 回表查詢
??在之前介紹過,InnoDB索引有聚簇索引和輔助索引。聚簇索引的葉子節點存儲行記錄,InnoDB必須要有,且只有一個。
??輔助索引的葉子節點存儲的是主鍵值和索引字段值,通過輔助索引無法直接定位行記錄,通常情況下,需要掃碼兩遍索引樹。
??先通過輔助索引定位主鍵值,然后再通過聚簇索引定位行記錄,這就叫做回表查詢,它的性能比掃一遍索引樹低。
??總結:通過索引查詢主鍵值,然后再去聚簇索引查詢記錄信息
3.3 覆蓋索引
[圖片上傳失敗...(image-96dd90-1600651655525)]
??MySQL官網,表達了:只需要在一棵索引樹上就能獲取SQL所需的所有列數據,無需回表,速度更快,這就叫做索引覆蓋。
??實現索引覆蓋最常見的方法就是:將被查詢的字段,建立到組合索引。
3.4 最左前綴原則
??復合索引使用時遵循最左前綴原則,最左前綴顧名思義,就是最左優先,即查詢中使用到最左邊的列,那么查詢就會使用到索引,如果從索引的第二列開始查找,索引將失效。
3.5 LIKE查詢
??面試題:MySQL在使用like模糊查詢時,索引能不能起作用?
??回答:MySQL在使用Like模糊查詢時,索引是可以被使用的,只有把%字符寫在后面才會使用到索引。
select * from user where name like '%o%'; //不起作用
select * from user where name like 'o%'; //起作用
select * from user where name like '%o'; //不起作用
3.6 NULL查詢
??面試題:如果MySQL表的某一列含有NULL值,那么包含該列的索引是否有效?
??對MySQL來說,NULL是一個特殊的值,從概念上講,NULL意味著“一個未知值”,它的處理方式與其他值有些不同。
??比如:不能使用=,<,>這樣的運算符,對NULL做算術運算的結果都是NULL,count時不會包括NULL行等,NULL比空字符串需要更多的存儲空間等。
??NULL列需要增加額外空間來記錄其值是否為NULL。對于MyISAM表,每一個空列額外占用一位,四舍五入到最接近的字節
??雖然MySQL可以在含有NULL的列上使用索引,但NULL和其他數據還是有區別的,不建議列上允許為NULL。最好設置NOT NULL,并給一個默認值,比如0和 ‘’ 空字符串等,如果是datetime類型,也可以設置系統當前時間或某個固定的特殊值,例如'1970-01-01 00:00:00'。
3.7 索引與排序
??MySQL查詢支持filesort和index兩種方式的排序。
??filesort是先把結果查出,然后在緩存或磁盤進行排序操作,效率較低。
??index是指利用索引自動實現排序,不需另做排序操作,效率會比較高。
??filesort有兩種排序算法:雙路排序和單路排序。
??■ 雙路排序:需要兩次磁盤掃描讀取,最終得到用戶數據。第一次將排序字段讀取出來,然后排序;第二次去讀取其他字段數據。
??■ 單路排序:從磁盤查詢所需的所有列數據,然后在內存排序將結果返回。如果查詢數據超出緩存sort_buffer,會導致多次磁盤讀取操作,并創建臨時表,最后產生了多次IO,反而會增加負擔。
??■ 解決方案:少使用select *;增加sort_buffer_size容量和max_length_for_sort_data容量。
??如果我們Explain分析SQL,結果中Extra屬性顯示Using filesort,表示使用了filesort排序方式,需要優化。
??如果Extra屬性顯示Using index時,表示覆蓋索引,也表示所有操作在索引上完成,也可以使用index排序方式,建議大家盡可能采用覆蓋索引。
??以下幾種情況,會使用index方式的排序。
??(1)ORDER BY 子句索引列組合滿足索引最左前列
explain select id from user order by id; //對應(id)、(id,name)索引有效
??(2)WHERE子句+ORDER BY子句索引列組合滿足索引最左前列
explain select id from user where age=18 order by name; //對應 (age,name)索引
??以下幾種情況,會使用filesort方式的排序。
??(1)對索引列同時使用了ASC和DESC
explain select id from user order by age asc,name desc; //對應(age,name)索引
??(2)WHERE子句和ORDER BY子句滿足最左前綴,但where子句使用了范圍查詢(例如>、<、in等)
explain select id from user where age>10 order by name; //對應(age,name)索引
??(3)ORDER BY或者WHERE+ORDER BY索引列沒有滿足索引最左前列
explain select id from user order by name; //對應(age,name)索引
??(4)使用了不同的索引,MySQL每次只采用一個索引,ORDER BY涉及了兩個索引
explain select id from user order by name,age; //對應(name)、(age)兩個索引
??(5)WHERE子句與ORDER BY子句,使用了不同的索引
explain select id from user where name='tom' order by age; //對應 (name)、(age)索引
??(6)WHERE子句或者ORDER BY子句中索引列使用了表達式,包括函數表達式
explain select id from user order by abs(age); //對應(age)索引
4 查詢優化
4.1 慢查詢定位
4.1.1 開啟慢查詢日志
??查看 MySQL 數據庫是否開啟了慢查詢日志和慢查詢日志文件的存儲位置的命令如下:
SHOW VARIABLES LIKE 'slow_query_log%'
??通過如下命令開啟慢查詢日志:
SET global slow_query_log = ON;
SET global slow_query_log_file = 'OAK-slow.log';
SET global log_queries_not_using_indexes = ON;
SET long_query_time = 10;
??long_query_time:指定慢查詢的閥值,單位秒。如果SQL執行時間超過閥值,就屬于慢查詢記錄到日志文件中。
??log_queries_not_using_indexes:表示會記錄沒有使用索引的查詢SQL。前提是slow_query_log的值為ON,否則不會奏效
4.1.2 查看慢查詢日志
??(1)文本方式查看
??直接使用文本編輯器打開slow.log日志即可。
■ time:日志記錄的時間
■ User@Host:執行的用戶及主機
■ Query_time:執行的時間
■ Lock_time:鎖表時間
■ Rows_sent:發送給請求方的記錄數,結果數量
■ Rows_examined:語句掃描的記錄條數 SET
■ timestamp:語句執行的時間點
■ select....:執行的具體的SQL語句
(2)使用mysqldumpslow查看
??MySQL 提供了一個慢查詢日志分析工具mysqldumpslow,可以通過該工具分析慢查詢日志內容。
??在 MySQL bin目錄下執行下面命令可以查看該使用格式。
perl mysqldumpslow.pl --help
運行如下命令查看慢查詢日志信息:
perl mysqldumpslow.pl -t 5 -s at C:\ProgramData\MySQL\Data\OAK-slow.log
??除了使用mysqldumpslow工具,也可以使用第三方分析工具,比如pt-query-digest、mysqlsla等。
4.2 慢查詢優化
4.2.1 索引和慢查詢
??(1)如何判斷是否為慢查詢?
??MySQL判斷一條語句是否為慢查詢語句,主要依據SQL語句的執行時間,它把當前語句的執行時間跟 long_query_time 參數做比較,如果語句的執行時間 > long_query_time,就會把這條執行語句記錄到慢查詢日志里面。
??long_query_time 參數的默認值是 10s,該參數值可以根據自己的業務需要進行調整。
??(2)如何判斷是否應用了索引?
?? SQL語句是否使用了索引,可根據SQL語句執行過程中有沒有用到表的索引,可通過 explain命令分析查看,檢查結果中的 key 值,是否為NULL。
??(3)應用了索引是否一定快?
??下面我們來看看下面語句的 explain 的結果,你覺得這條語句有用上索引嗎?比如
select * from user where id>0;
??雖然使用了索引,但是還是從主鍵索引的最左邊的葉節點開始向右掃描整個索引樹,進行了全表掃描,此時索引就失去了意義。
??(4)總結
??查詢是否使用索引,只是表示一個SQL語句的執行過程;而是否為慢查詢,是由它執行的時間決定的,也就是說是否使用了索引和是否是慢查詢兩者之間沒有必然的聯系。
??我們在使用索引時,不要只關注是否起作用,應該關心索引是否減少了查詢掃描的數據行數,如果掃描行數減少了,效率才會得到提升。對于一個大表,不止要創建索引,還要考慮索引過濾性,過濾性好,執行速度才會快。
4.2.2 提高索引過濾性
??假如有一個5000萬記錄的用戶表,通過sex='男'索引過濾后,還需要定位3000萬,SQL執行速度也不會很快。
??其實這個問題涉及到索引的過濾性,比如1萬條記錄利用索引過濾后定位10條、100條、1000條,那他們過濾性是不同的。
??索引過濾性與索引字段、表的數據量、表設計結構都有關系。
??下面我們看一個案例:
表:student
字段:id,name,sex,age
造數據:insert into student (name,sex,age) select name,sex,age from student;
SQL案例:select * from student where age=18 and name like '張%';(全表掃 描)
優化1
alter table student add index(name); //追加name索引
優化2
alter table student add index(age,name); //追加age,name索引
優化3
??可以看到,index condition pushdown 優化的效果還是很不錯的。再進一步優化,我們可以把名字的第一個字和年齡做一個聯合索引,這里可以使用 MySQL 5.7 引入的虛擬列來實現。
//為user表添加first_name虛擬列,以及聯合索引(first_name,age)
alter table student add first_name varchar(2) generated always as (left(name, 1)), add index(first_name, age);
explain select * from student where first_name='張' and age=18;
??慢查詢原因總結
■ 全表掃描:explain分析type屬性all
■ 全索引掃描:explain分析type屬性index
■ 索引過濾性不好:靠索引字段選型、數據量和狀態、表設計
■ 頻繁的回表查詢開銷:盡量少用select *,使用覆蓋索引
4.3 分頁查詢優化
4.3.1 一般性分頁
??一般的分頁查詢使用簡單的 limit 子句就可以實現。limit格式如下:
SELECT * FROM 表名 LIMIT [offset,] rows
■ 第一個參數指定第一個返回記錄行的偏移量,注意從0開始;
■ 第二個參數指定返回記錄行的最大數目;
■ 如果只給定一個參數,它表示返回最大的記錄行數目;
??思考1:如果偏移量固定,返回記錄量對執行時間有什么影響?
select * from user limit 10000,1;
select * from user limit 10000,10;
select * from user limit 10000,100;
select * from user limit 10000,1000;
select * from user limit 10000,10000;
?? 結果:在查詢記錄時,返回記錄量低于100條,查詢時間基本沒有變化,差距不大。隨著查詢記錄 量越大,所花費的時間也會越來越多。
??思考2:如果查詢偏移量變化,返回記錄數固定對執行時間有什么影響?
select * from user limit 1,100;
select * from user limit 10,100;
select * from user limit 100,100;
select * from user limit 1000,100;
select * from user limit 10000,100;
?? 結果:在查詢記錄時,如果查詢記錄量相同,偏移量超過100后就開始隨著偏移量增大,查詢時間急劇的增加。(這種分頁查詢機制,每次都會從數據庫第一條記錄開始掃描,越往后查詢越慢,而且查詢的數據越多,也會拖慢總查詢速度。)
4.3.2 分頁優化方案
??第一步:利用覆蓋索引優化
select * from user limit 10000,100;
select id from user limit 10000,100;
??第二步:利用子查詢優化
select * from user limit 10000,100;
select * from user where id>= (select id from user limit 10000,1) limit 100;
?? 原因:使用了id做主鍵比較(id>=),并且子查詢使用了覆蓋索引進行優化。