前置文章:
一、MySQL-Explain了解查詢語句執(zhí)行計(jì)劃
零、本文綱要
- 一、索引概述
- 二、索引分類
- 三、索引語法
- 四、SQL性能分析
- 五、索引使用
- 六、索引設(shè)計(jì)原則
tips:Ctrl + F快速定位到所需內(nèi)容閱讀吧。
一、索引概述
索引(index)是幫助MySQL高效獲取數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)(有序)。
1、索引特點(diǎn)
- 優(yōu)勢
① 提高檢索效率,降低IO成本;
② 降低排序成本,減少CPU消耗。 - 劣勢
① 索引占用空間,增加空間消耗;
② 增加SELECT語句效率,但是INSERT\DELETE\UPDATE效率降低。
2、索引結(jié)構(gòu)
數(shù)據(jù)結(jié)構(gòu)演示網(wǎng)站:Data Structure Visualization (usfca.edu)
- ① 二叉樹
二叉樹在理想情況下確實(shí)是不錯(cuò)的數(shù)據(jù)結(jié)構(gòu),但是插入順序影響下,可能轉(zhuǎn)化為鏈表。
二叉樹.png - ② 紅黑樹
紅黑樹相比于二叉搜索樹雖然解決了轉(zhuǎn)化為鏈表的問題,但是隨著數(shù)據(jù)量的提升,二叉樹層數(shù)不斷增加也會降低搜索效率。
紅黑樹.png - ③ B-Tree
B-Tree,B樹是一種多叉路衡查找樹,相對于二叉樹,B樹每個(gè)節(jié)點(diǎn)可以有多個(gè)分支,即多叉。
B-Tree.png - ④ B+Tree(InnoDB默認(rèn))
B+Tree與B-Tree相比:
Ⅰ 所有的數(shù)據(jù)都會出現(xiàn)在葉子節(jié)點(diǎn);
Ⅱ 葉子節(jié)點(diǎn)形成一個(gè)單向鏈表;
Ⅲ 非葉子節(jié)點(diǎn)僅僅起到索引數(shù)據(jù)作用。
B+Tree.png - MySQL優(yōu)化的B+Tree
在原B+Tree的基礎(chǔ)上,增加一個(gè)指向相鄰葉子節(jié)點(diǎn)的鏈表指針(環(huán)形鏈表),就形成了帶有順序指針的B+Tree,提高區(qū)間訪問的性能,利于排序。
image.png - ⑤ Hash
哈希索引就是采用一定的hash算法,將鍵值換算成新的hash值,映射到對應(yīng)的槽位上,然后存儲在hash表中。
如果兩個(gè)(或多個(gè))鍵值,映射到一個(gè)相同的槽位上,他們就產(chǎn)生了hash沖突(也稱為hash碰撞),可以通過鏈表來解決。
Hash.png - 特點(diǎn)
① Hash索引只能用于對等比較(=,in),不支持范圍查詢(between,>,< ,...);
② 無法利用索引完成排序操作;
③ 查詢效率高,通常(不存在hash沖突的情況)只需要一次檢索就可以了,效率通常要高于B+tree索引。
二、索引分類
- 1、按類型分類
① 主鍵索引
PRIMARY:針對于表中主鍵創(chuàng)建的索引,默認(rèn)自動創(chuàng)建,只能有一個(gè);
② 唯一索引
UNIQUE:避免同一個(gè)表中某數(shù)據(jù)列中的值重復(fù),同一個(gè)表可以有多個(gè);
③ 常規(guī)索引
快速定位特定數(shù)據(jù),同一個(gè)表可以有多個(gè);
④ 全文索引
FULLTEXT:全文索引查找的是文本中的關(guān)鍵詞,而不是比較索引中的值,同一個(gè)表可以有多個(gè)。 - 2、按存儲形式分類
① 聚集索引
Clustered Index:將數(shù)據(jù)存儲與索引放到了一塊,索引結(jié)構(gòu)的葉子節(jié)點(diǎn)保存了行數(shù)據(jù)(必須有,而且只有一個(gè));
② 二級索引
Secondary Index:(又稱:非聚集索引、輔助索引),將數(shù)據(jù)與索引分開存儲,索引結(jié)構(gòu)的葉子節(jié)點(diǎn)關(guān)聯(lián)的是對應(yīng)的主鍵(可以存在多個(gè))。

- 聚集索引選取規(guī)則
① 如果存在主鍵,主鍵索引就是聚集索引;
② 如果不存在主鍵,將使用第一個(gè)唯一(UNIQUE)索引作為聚集索引;
③ 如果表沒有主鍵,或沒有合適的唯一索引,則InnoDB會自動生成一個(gè)rowid作為隱藏的聚集索引。
三、索引語法
- 1、創(chuàng)建索引
MySQL官方關(guān)于創(chuàng)建索引的文檔介紹:CREATE INDEX Statement
CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name,... ) ;
- 2、查看索引
SHOW INDEX FROM table_name ;
- 3、刪除索引
DROP INDEX index_name ON table_name ;
簡單練習(xí)
- ① 創(chuàng)建測試表
create table tb_user
(
id int auto_increment comment '主鍵'
primary key,
name varchar(50) not null comment '用戶名',
phone varchar(11) not null comment '手機(jī)號',
email varchar(100) null comment '郵箱',
profession varchar(11) null comment '專業(yè)',
age tinyint unsigned null comment '年齡',
gender char null comment '性別 , 1: 男, 2: 女',
status char null comment '狀態(tài)',
createtime datetime null comment '創(chuàng)建時(shí)間',
)
comment '系統(tǒng)用戶表';
- ② 插入數(shù)據(jù)
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('妲己', '17799990015', '2783238293@qq.com', '軟件工程', 31,'2', '0', '2001-01-30 00:00:00');
- ③ 操作索引
索引命名:idx(index)_user(table_name)_name(column_name)。
Ⅰ 創(chuàng)建常規(guī)索引
此處name因?yàn)閷?shí)際場景中不是唯一的,可以重復(fù),所以我們創(chuàng)建常規(guī)索引;
create index idx_user_name on tb_user(name);
Ⅱ 查看索引
show index from tb_user;

Ⅲ 創(chuàng)建唯一索引
此處phone實(shí)際場景中是唯一的,所以我們創(chuàng)建唯一索引;
create unique index idx_user_phone on tb_user(phone);
Ⅳ 創(chuàng)建聯(lián)合索引
create index idx_user_pro_age_sta on tb_user(profession,age,status);
四、SQL性能分析
- 1、SQL執(zhí)行頻率
使用SQL來顯示狀態(tài):SHOW STATUS Statement。
SHOW [GLOBAL | SESSION] STATUS提供服務(wù)器狀態(tài)信息,該語句有一個(gè)變量Com_xxx是專門用來指示每個(gè) xxx 語句的執(zhí)行次數(shù)。
show global status like 'com_______';
-- 此處com后面有7個(gè)“_”下劃線
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Com_binlog | 0 |
| Com_commit | 0 |
| Com_delete | 25 |
| Com_import | 0 |
| Com_insert | 38 |
| Com_repair | 0 |
| Com_revoke | 0 |
| Com_select | 355 |
| Com_signal | 0 |
| Com_update | 26 |
| Com_xa_end | 0 |
+---------------+-------+
- 2、慢查詢?nèi)罩?/li>
mysql> show variables like "slow_query_log";
+----------------+-------+
| Variable_name | Value |
+----------------+-------+
| slow_query_log | OFF |
+----------------+-------+
-- 默認(rèn)是關(guān)閉的
我們可以通過修改對應(yīng)配置文件開啟,位置/etc/my.cnf。
vim /etc/my.cnf
# 插入下方數(shù)據(jù)
# 1表示開啟,0表示關(guān)閉
slow_query_log=1
# 慢查詢的設(shè)定時(shí)間10s,可以根據(jù)實(shí)際需求調(diào)整
long_query_time=10
查看慢SQL日志,位置/var/lib/mysql/localhost-slow.log。
cat /var/lib/mysql/localhost-slow.log
- 3、profile
官方文檔:SHOW PROFILE Statement
SELECT @@profiling;查看profiling屬性,默認(rèn)關(guān)閉;
SET profiling = 1;開啟profiling;
mysql> SHOW PROFILES;查看PROFILES詳情,就是幾個(gè)語句執(zhí)行情況;
SHOW PROFILE FOR QUERY 1;查看具體某一個(gè)語句的執(zhí)行情況;
SHOW PROFILE CPU FOR QUERY 2;帶CPU使用情況的查詢。
# 查看profiling屬性,默認(rèn)關(guān)閉
mysql> SELECT @@profiling;
+-------------+
| @@profiling |
+-------------+
| 0 |
+-------------+
mysql> SET profiling = 1;
mysql> DROP TABLE IF EXISTS t1;
mysql> CREATE TABLE T1 (id INT);
mysql> SHOW PROFILES;
+----------+----------+--------------------------+
| Query_ID | Duration | Query |
+----------+----------+--------------------------+
| 0 | 0.000088 | SET PROFILING = 1 |
| 1 | 0.000136 | DROP TABLE IF EXISTS t1 |
| 2 | 0.011947 | CREATE TABLE t1 (id INT) |
+----------+----------+--------------------------+
mysql> SHOW PROFILE FOR QUERY 1;
+--------------------+----------+
| Status | Duration |
+--------------------+----------+
| query end | 0.000107 |
| freeing items | 0.000008 |
| logging slow query | 0.000015 |
| cleaning up | 0.000006 |
+--------------------+----------+
mysql> SHOW PROFILE CPU FOR QUERY 2;
+----------------------+----------+----------+------------+
| Status | Duration | CPU_user | CPU_system |
+----------------------+----------+----------+------------+
| checking permissions | 0.000040 | 0.000038 | 0.000002 |
| creating table | 0.000056 | 0.000028 | 0.000028 |
| After create | 0.011363 | 0.000217 | 0.001571 |
| query end | 0.000375 | 0.000013 | 0.000028 |
| freeing items | 0.000089 | 0.000010 | 0.000014 |
| logging slow query | 0.000019 | 0.000009 | 0.000010 |
| cleaning up | 0.000005 | 0.000003 | 0.000002 |
+----------------------+----------+----------+------------+
- 4、Explain執(zhí)行計(jì)劃
可以查看前置文章MySQL-Explain了解查詢語句執(zhí)行計(jì)劃。
五、索引使用
- 1、最左前綴法則
最左前綴法則指的是查詢從聯(lián)合索引的最左列開始,并且不跳過索引中的列。如果跳躍某一列,索引將會部分失效(后面的字段索引失效)。
mysql> explain select * from tb_user where profession = '軟件工程';
mysql> explain select * from tb_user where profession = '軟件工程' and status = 0;

- 2、范圍查詢
以下場景由于使用了(>,<)范圍查詢,導(dǎo)致該索引后面的索引失效了。實(shí)際使用中像age>30等價(jià)于age>=31,所以我們可以替換一下。
mysql> explain select * from tb_user where profession = '軟件工程' and age > 30 and status = '0';
mysql> explain select * from tb_user where profession = '軟件工程' and age >= 31 and status = '0';

- 3、索引失效場景
以下案例關(guān)注| possible_keys | key |兩列,possible_keys列為可能使用到的索引,key列是實(shí)際使用到的索引。
- ① 索引列運(yùn)算
索引列上有計(jì)算或者使用函數(shù),導(dǎo)致索引失效。
mysql> explain select * from tb_user where substring(phone,10,2) = '15';
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_user | NULL | ALL | NULL | NULL | NULL | NULL | 24 | 100.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
mysql> explain select * from tb_user where id + 5 = 25;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_user | NULL | ALL | NULL | NULL | NULL | NULL | 24 | 100.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
- ② 字符串不加引號
字符串不加引號,可以理解為MySQL底層做了數(shù)據(jù)類型轉(zhuǎn)換,導(dǎo)致索引失效。
mysql> explain select * from tb_user where phone = '17799990015';
+----+-------------+---------+------------+-------+----------------+----------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+----------------+----------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | tb_user | NULL | const | idx_user_phone | idx_user_phone | 46 | const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+-------+----------------+----------------+---------+-------+------+----------+-------+
mysql> explain select * from tb_user where phone = 17799990015;
+----+-------------+---------+------------+------+----------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+----------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_user | NULL | ALL | idx_user_phone | NULL | NULL | NULL | 24 | 10.00 | Using where |
+----+-------------+---------+------------+------+----------------+------+---------+------+------+----------+-------------+
- ③ 模糊查詢
在like模糊查詢中,在關(guān)鍵字后面加%,索引可以生效。而如果在關(guān)鍵字
前面加了%,索引將會失效。
mysql> explain select * from tb_user where profession like '%工程';
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_user | NULL | ALL | NULL | NULL | NULL | NULL | 24 | 11.11 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
mysql> explain select * from tb_user where profession like '軟件%';
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | tb_user | NULL | range | idx_user_pro_age_sta | idx_user_pro_age_sta | 47 | NULL | 4 | 100.00 | Using index condition |
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
- ④ or條件連接
用or分割開的條件, 如果or前的條件中的列有索引,而后面的列中沒有索引,那么涉及的索引都不會被用到。
mysql> explain select * from tb_user where id = 10 or age = 23;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_user | NULL | ALL | PRIMARY | NULL | NULL | NULL | 24 | 13.75 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
- ⑤ 數(shù)據(jù)分布影響
MySQL在查詢時(shí),會評估使用索引的效率與走全表掃描的效率,如果走全表掃描更快,則放棄索引,走全表掃描。
mysql> explain select * from tb_user where phone >= '17799990005';
+----+-------------+---------+------------+------+----------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+----------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_user | NULL | ALL | idx_user_phone | NULL | NULL | NULL | 24 | 79.17 | Using where |
+----+-------------+---------+------------+------+----------------+------+---------+------+------+----------+-------------+
mysql> explain select * from tb_user where phone >= '17799990015';
+----+-------------+---------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | tb_user | NULL | range | idx_user_phone | idx_user_phone | 46 | NULL | 9 | 100.00 | Using index condition |
+----+-------------+---------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
注意:is null、is not null是否走索引,也是根據(jù)實(shí)際效率來決定的。
- 4、SQL提示
mysql> explain select * from tb_user where profession = '軟件工程';
mysql> create index idx_user_pro on tb_user(profession);
mysql> explain select * from tb_user where profession = '軟件工程';

SQL提示,是優(yōu)化數(shù)據(jù)庫的一個(gè)重要手段,就是在SQL語句中加入一些人為的提示來達(dá)到優(yōu)化操作的目的。
- ① use index : 建議MySQL使用哪一個(gè)索引完成此次查詢(僅僅是建議,mysql內(nèi)部還會再次進(jìn)行評估);
- ② ignore index : 忽略指定的索引;
- ③ force index : 強(qiáng)制使用索引。
mysql> explain select * from tb_user use index(idx_user_pro) where profession = '軟件工程';
mysql> explain select * from tb_user ignore index(idx_user_pro) where profession = '軟件工程';
mysql> explain select * from tb_user force index(idx_user_pro_age_sta) where profession = '軟件工程';

- 5、覆蓋索引
覆蓋索引是指查詢使用了索引,并且需要返回的列,在該索引中已經(jīng)全部能夠找到。
mysql> explain select id, profession from tb_user where profession = '軟件工程' and age = 31 and status = '0' ;
mysql> explain select id,profession,age, status from tb_user where profession = '軟件工程' and age = 31 and status = '0';
mysql> explain select id,profession,age, status, name from tb_user where profession = '軟 件工程' and age = 31 and status = '0';
mysql> explain select * from tb_user where profession = '軟件工程' and age = 31 and status = '0';

① Extra
| Using where; Using index |:查找使用了索引,但是需要的數(shù)據(jù)都在索引列中能找到,所以不需要回表查詢數(shù)據(jù);
| Using index condition |:查找使用了索引,但是需要回表查詢數(shù)據(jù)。
- 6、前綴索引
當(dāng)字段類型為字符串(varchar,text,longtext等)時(shí),有時(shí)候需要索引很長的字符串,這會讓索引變得很大,查詢時(shí),浪費(fèi)大量的磁盤IO, 影響查詢效率。此時(shí)可以只將字符串的一部分前綴,建立索引,這樣可以大大節(jié)約索引空間,從而提高索引效率。
語法:create index idx_xxxx on table_name(column(n)) ;
① 前綴長度
全列選擇性:
SELECT COUNT(DISTINCT column_name) / COUNT(*) FROM table_name;
某一長度前綴的選擇性:
SELECT COUNT(DISTINCT LEFT(column_name, prefix_length)) / COUNT(*) FROM table_name;
使用如下:
select count(distinct email) / count(*) from tb_user ;
select count(distinct substring(email,1,5)) / count(*) from tb_user ;

② 前綴索引的查詢流程

Ⅰ 根據(jù)sql語句的搜索項(xiàng)截取前綴;
Ⅱ 拿著前綴在輔助索引內(nèi)查找匹配項(xiàng);
Ⅲ 從輔助索引拿到主鍵id;
Ⅳ 根據(jù)主鍵id在聚集索引內(nèi)找到對應(yīng)row行數(shù)據(jù);
Ⅴ 輔助索引鏈表位置處繼續(xù)向后確認(rèn),有則重復(fù)Ⅱ-Ⅴ;
Ⅵ 返回結(jié)果集。
- 7、單列索引與聯(lián)合索引
在業(yè)務(wù)場景中,如果存在多個(gè)查詢條件,考慮針對于查詢字段建立索引時(shí),建議建立聯(lián)合索引,而非單列索引。
注意:如果MySQL未按預(yù)期執(zhí)行,參考SQL提示,做適當(dāng)調(diào)整。

六、索引設(shè)計(jì)原則
- 1、針對于數(shù)據(jù)量較大,且查詢比較頻繁的表建立索引;
- 2、針對于常作為查詢條件(where)、排序(order by)、分組(group by)操作的字段建立索引;
- 3、盡量選擇區(qū)分度高的列作為索引,盡量建立唯一索引,區(qū)分度越高,使用索引的效率越高;
- 4、如果是字符串類型的字段,字段的長度較長,可以針對于字段的特點(diǎn),建立前綴索引;
- 5、盡量使用聯(lián)合索引,減少單列索引,查詢時(shí),聯(lián)合索引很多時(shí)候可以覆蓋索引,節(jié)省存儲空間,避免回表,提高查詢效率;
- 6、要控制索引的數(shù)量,索引并不是多多益善,索引越多,維護(hù)索引結(jié)構(gòu)的代價(jià)也就越大,會影響增刪改的效率;
- 7、如果索引列不能存儲NULL值,請?jiān)趧?chuàng)建表時(shí)使用NOT NULL約束它。當(dāng)優(yōu)化器知道每列是否包含NULL值時(shí),它可以更好地確定哪個(gè)索引最有效地用于查詢。
七、結(jié)尾
以上即為MySQL索引的部分內(nèi)容,感謝閱讀。





