一、索引
1、索引概述
1.1、介紹
1.2、特點
2、索引結構
2.1、概述


2.2、B+Tree

- 綠色框框起來的部分,是索引部分,僅僅起到索引數據的作用,不存儲數據。
- 紅色框框起來的部分,是數據存儲部分,在其葉子節點中要存儲具體的數據。
2.3、MySQL里的B+tree索引結構
MySQL索引數據結構對經典的B+Tree進行了優化。在原B+Tree的基礎上,增加一個指向相鄰葉子節點的鏈表指針,就形成了帶有順序指針的B+Tree,提高區間訪問的性能,利于排序。

2.4、Hash
2.4.1、結構


2.4.2、特點
- Hash索引只能用于對等比較(=,in),不支持范圍查詢(between,>,< ,...)
- 無法利用索引完成排序操作
- 查詢效率高,通常(不存在hash沖突的情況)只需要一次檢索就可以了,效率通常要高于B+tree索引
2.4.3、存儲引擎支持
思考題: 為什么InnoDB存儲引擎選擇使用B+tree索引結構?
- 相對于二叉樹,層級更少,搜索效率高;
- 對于B-tree,無論是葉子節點還是非葉子節點,都會保存數據,這樣導致一頁中存儲的鍵值減少,指針跟著減少,要同樣保存大量數據,只能增加樹的高度,導致性能降低;
- 相對Hash索引,B+tree支持范圍匹配及排序操作;
3、索引分類
3.1、索引分類

3.2、聚集索引&二級索引

- 如果存在主鍵,主鍵索引就是聚集索引。
- 如果不存在主鍵,將使用第一個唯一(UNIQUE)索引作為聚集索引。
- 如果表沒有主鍵,或沒有合適的唯一索引,則InnoDB會自動生成一個rowid作為隱藏的聚集索 引。

- 聚集索引的葉子節點下掛的是這一行的數據 。
- 二級索引的葉子節點下掛的是該字段值對應的主鍵值。

- 由于是根據name字段進行查詢,所以先根據name='Arm'到name字段的二級索引中進行匹配查找。但是在二級索引中只能查找到 Arm 對應的主鍵值 10。
- 由于查詢返回的數據是*,所以此時,還需要根據主鍵值10,到聚集索引中查找10對應的記錄,最終找到10對應的行row。
- 最終拿到這一行的數據,直接返回即可。
4、索引語法
- 創建索引 :?
CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name ( index_col_name,... ) ;
- 查找索引 :?
SHOW INDEX FROM table_name ;
- 刪除索引 :?
DROP INDEX index_name ON table_name ;
5、SQL性能分析
5.1、SQL執行頻率
-- session 是查看當前會話 ;
-- global 是查詢全局數據 ;
SHOW GLOBAL STATUS LIKE 'Com_______';
Com_delete:? ? 刪除次數Com_insert:? ? 插入次數Com_select:? ? 查詢次數Com_update:?? 更新次數

5.2、慢查詢日志

如果要開啟慢查詢日志,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
# 開啟MySQL慢日志查詢開關
slow_query_log=1
# 設置慢日志的時間為2秒,SQL語句執行時間超過2秒,就會視為慢查詢,記錄慢查詢日志
long_query_time=2
systemctl restart mysqld
然后,再次查看開關情況,慢查詢日志就已經打開了。
- 執行如下SQL語句 :?
select * from tb_user; -- 這條SQL執行效率比較高, 執行耗時 0.00sec select count(*) from tb_sku; -- 由于tb_sku表中, 預先存入了1000w的記錄, count一次,耗時13.35sec
- 檢查慢查詢日志 :
最終我們發現,在慢查詢日志中,只會記錄執行時間超多我們預設時間(2s)的SQL,執行較快的SQL是不會記錄的。
那這樣,通過慢查詢日志,就可以定位出執行效率比較低的SQL,從而有針對性的進行優化。
5.3、profile詳情
SELECT @@have_profiling ;
SET profiling = 1;
select * from tb_user;
select * from tb_user where id = 1;
select * from tb_user where name = '白起';
select count(*) from tb_sku;
-- 查看每一條SQL的耗時基本情況
show profiles;
-- 查看指定query_id的SQL語句各個階段的耗時情況
show profile for query query_id;
-- 查看指定query_id的SQL語句CPU的使用情況
show profile cpu for query query_id;


5.4、explain
-- 直接在select語句之前加上關鍵字 explain / desc
EXPLAIN SELECT 字段列表 FROM 表名 WHERE 條件 ;


6、索引使用
6.1、驗證索引效率

select * from tb_sku where id = 1\G;
SELECT * FROM tb_sku WHERE sn = '100000003145001';
create index idx_sku_sn on tb_sku(sn) ;
SELECT * FROM tb_sku WHERE sn = '100000003145001';
6.2、最左前綴法則

explain select * from tb_user where profession = '軟件工程' and age = 31 and status = '0';
explain select * from tb_user where profession = '軟件工程' and age = 31;
explain select * from tb_user where profession = '軟件工程';
explain select * from tb_user where age = 31 and status = '0';
explain select * from tb_user where status = '0';
而通過上面的這兩組測試,我們也可以看到索引并未生效,原因是因為不滿足最左前綴法則,聯合索引最左邊的列profession不存在。
explain select * from tb_user where profession = '軟件工程' and status = '0';
思考題:?
當執行SQL語句:explain select * from tb_user where age = 31 and status = '0' and profession = '軟件工程';
時,是否滿足最左前綴法則,走不走上述的聯合索引,索引長度?
可以看到,是完全滿足最左前綴法則的,索引長度 54 ,聯合索引是生效的。注意 : 最左前綴法則中指的最左邊的列,是指在查詢時,聯合索引的最左邊的字段 ( 即是第一個字段 ) 必須存在,與我們編寫 SQL 時,條件編寫的先后順序無關。
6.3、范圍查詢
explain select * from tb_user where profession = '軟件工程' and age > 30 and status = '0';
6.4、索引失效情況
6.4.1、索引列運算
不要在索引列上進行運算操作, 索引將失效。
在tb_user表中,除了前面介紹的聯合索引之外,還有一個索引,是phone字段的單列索引。
A. 當根據phone字段進行等值匹配查詢時, 索引生效。
explain select * from tb_user where phone = '17799990015';
explain select * from tb_user where substring(phone,10,2) = '15';
6.4.2、字符串不加引號
explain select * from tb_user where profession like '軟件%';
explain select * from tb_user where profession like '%工程';
explain select * from tb_user where profession like '%工%';
6.4.4、or連接條件
explain select * from tb_user where id = 10 or age = 23;
explain select * from tb_user where phone = '17799990017' or age = 23;
?由于age沒有索引,所以即使id、phone有索引,索引也會失效。所以需要針對于age也要建立索引。
create index idx_user_age on tb_user(age);
?建立了索引之后,我們再次執行上述的SQL語句,看看前后執行計劃的變化
?
?最終,我們發現,當or連接的條件,左右兩側字段都有索引時,索引才會生效。
?6.4.5、數據分布影響
?如果MySQL評估使用索引比全表更慢,則不使用索引。
select * from tb_user where phone >= '17799990005';
select * from tb_user where phone >= '17799990015';
?
explain select * from tb_user where profession is null;
explain select * from tb_user where profession is not null;
?接下來,我們做一個操作將profession字段值全部更新為null。
然后,再次執行上述的兩條SQL,查看SQL語句的執行計劃。
6.5、SQL提示
目前tb_user表的數據情況如下
?索引情況如下:
?把上述的 idx_user_age, idx_email 這兩個之前測試使用過的索引直接刪除。
drop index idx_user_age on tb_user;
drop index idx_email on tb_user;
?
查詢走了聯合索引。?
create index idx_user_pro on tb_user(profession);
?C. 創建單列索引后,再次執行A中的SQL語句,查看執行計劃,看看到底走哪個索引。
use index : 建議 MySQL 使用哪一個索引完成此次查詢(僅僅是建議, mysql 內部還會再次進行評估)。explain select * from tb_user use index(idx_user_pro) where profession = '軟件工程';
ignore index : 忽略指定的索引。explain select * from tb_user ignore index(idx_user_pro) where profession = '軟件工程';
force index : 強制使用索引
explain select * from tb_user force index(idx_user_pro) where profession = '軟件工程';
?6.6、覆蓋索引
?接下來,我們來看一組SQL的執行計劃,看看執行計劃的差別,然后再來具體做一個解析
explain select id, profession from tb_user where profession = '軟件工程' and age = 31 and status = '0' ;explain select id,profession,age, status from tb_user where profession = '軟件工程' and age = 31 and status = '0' ;explain select id,profession,age, status, name from tb_user where profession = '軟件工程' and age = 31 and status = '0' ;explain select * from tb_user where profession = '軟件工程' and age = 31 and status = '0';
?上述這幾條SQL的執行結果為:


?id是主鍵,是一個聚集索引。 name字段建立了普通索引,是一個二級索引(輔助索引)。
?B. 執行SQL : select * from tb_user where id = 2;
?根據id查詢,直接走聚集索引查詢,一次索引掃描,直接返回數據,性能高。
C. 執行SQL:selet id,name from tb_user where name = 'Arm';
?D. 執行SQL:selet id,name,gender from tb_user where name = 'Arm';
?
思考題:一張表 , 有四個字段 (id, username, password, status), 由于數據量大 , 需要對以下SQL 語句進行優化 , 該如何進行才是最優方案 :select id,username,password from tb_user where username = 'itcast';
答案: 針對于 username, password建立聯合索引, sql為:
create index idx_user_name_pass on tb_user(username,password);
這樣可以避免上述的 SQL 語句,在查詢的過程中,出現回表查詢。
?6.7、前綴索引
- 語法
create index idx_xxxx on table_name(column(n)) ;
示例 :
?為 tb_user 表的 email 字段,建立長度為5的前綴索引。create index idx_email_5 on tb_user(email(5));
? - 前綴長度
?可以根據索引的選擇性來決定,而選擇性是指不重復的索引值(基數)和數據表的記錄總數的比值,索引選擇性越高則查詢效率越高,唯一索引的選擇性是1 ,這是最好的索引選擇性,性能是最好的。
?select count(distinct email) / count(*) from tb_user ; select count(distinct substring(email,1,5)) / count(*) from tb_user ;
小貼士: 求取不重復的值, 在字段前加上distinct關鍵字來去重。
- 前綴索引的查詢流程
?
?6.8、單列索引與聯合索引
?
?

?在查詢出來的索引中,既有單列索引,又有聯合索引
接下來,我們來執行一條SQL語句,看看其執行計劃:
create unique index idx_user_phone_name on tb_user(phone,name);
在業務場景中,如果存在多個查詢條件,考慮針對于查詢字段建立索引時,建議建立聯合索引,而非單列索引。
?如果查詢使用的是聯合索引,具體的結構示意圖如下:
?
?7、索引設計原則
- 針對于數據量較大,且查詢比較頻繁的表建立索引。
?- 針對于常作為查詢條件(where)、排序(order by)、分組(group by)操作的字段建立索引。
?- 盡量選擇區分度高的列作為索引,盡量建立唯一索引,區分度越高,使用索引的效率越高。
?- 如果是字符串類型的字段,字段的長度較長,可以針對于字段的特點,建立前綴索引。
?- 盡量使用聯合索引,減少單列索引,查詢時,聯合索引很多時候可以覆蓋索引,節省存儲空間,避免回表,提高查詢效率。
?- 要控制索引的數量,索引并不是多多益善,索引越多,維護索引結構的代價也就越大,會影響增刪改的效率。