索引的目的在于提高查詢效率
一 索引分類
1、普通索引 index
加速查詢
2、唯一索引
2.1、主鍵索引 primary key
加速查詢+約束(不為空且唯一)
2.2、唯一索引 unique
加速查詢+約束(唯一)
3、聯合索引
-- index(id,name) 聯合普通索引
-- primary key(id,name) 聯合主鍵索引
-- unique(id,name) 聯合唯一索引
4、全文索引 fulltext
用于搜索很長文章的時候效果最好,
5、空間索引 spatial
二 索引型別
# 我們可以在創建索引的時候,為其指定索引型別,分兩類
1、hash型別
查詢單條快,范圍查詢慢
2、btree型別 B+樹
b+樹,層級越多,資料量指數級增長
#不同的存盤引擎支持的索引型別也不一樣
InnoDB 支持事務,支持行級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
MyISAM 不支持事務,支持表級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
Memory 不支持事務,支持表級別鎖定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;
NDB 支持事務,支持行級別鎖定,支持 Hash 索引,不支持 B-tree、Full-text 等索引;
Archive 不支持事務,支持表級別鎖定,不支持 B-tree、Hash、Full-text 等索引;
三 創建\洗掉索引的語法
1、創建索引
# 在創建表時就添加索引 及 注意事項
create table TABLE_NAME(
id int, # 可以添加primary key
# id int index, # 不可以這么添加索引,因為index是普通索引,沒有約束一說,所以不能像主鍵索引和唯一索引那樣在定義欄位的時候加索引
name char(20),
age int,
email varchar(30)
# primary key(id) # 也可以為主鍵這樣添加索引
# index(id) # 雖然不能在定義欄位的同時添加普通索引,但是通過這種方式為欄位添加普通索引
);
# 在創建表之后添加索引
create index name on TABLE_NAME(name); # 添加普通索引
create unique age on TABLE_NAME(age); # 添加唯一索引
alter table TABLE_NAME add primary key(id); # 添加主鍵索引,也就是給id欄位增減一個主鍵約束
create index name on TABLE_NAME(id,name); # 添加普通聯合索引
2、洗掉索引
drop index name on TABLE_NAME; # 洗掉普通索引
drop index age on TABLE_NAME; # 洗掉唯一索引,就和普通索引一樣,不用在index前加unique就可以洗掉
alter table TABLE_NAME drop promary key; # 洗掉主鍵(因為它添加的時候是按照alter來增加的,那么我們也用alter來刪)
四 測驗索引
1、準備表
create table TABLE_NAME(
id int,
name varchar(20),
gender char(6),
email varchar(50)
);
2、創建存盤程序,實作批量插入記錄
delimiter $$ #宣告存盤程序的結束符號為$$
create procedure auto_insert1()
BEGIN
declare i int default 1;
while(i<3000000)do
insert into s1 values(i,concat('egon',i),'male',concat('egon',i,'@oldboy'));
set i=i+1;
end while;
END$$ #$$結束
delimiter ; #重新宣告分號為結束符號
3、查看存盤程序
show create procedure auto_insert1\G
4、呼叫存盤程序
call auto_insert1();
# 準備測驗表 create table TEST_TABLE_SUOYIN( id int, name varchar(20), gender char(6), email varchar(50) ); # 創建存盤程序,實作批量插入記錄 delimiter $$ #宣告存盤程序的結束符號為$$ create procedure auto_insert1() BEGIN declare i int default 1; while(i<3000000)do insert into TEST_TABLE_SUOYIN values(i,concat('egon',i),'male',concat('egon',i,'@oldboy')); set i=i+1; end while; END$$ #$$結束 delimiter ; #重新宣告分號為結束符號 # 查看存盤程序 show create procedure auto_insert1; # 呼叫存盤程序 call auto_insert1(); # 未添加索引 select * from TEST_TABLE_SUOYIN where id = 1000; # 1.287s select * from TEST_TABLE_SUOYIN where id BETWEEN 1000 and 100000; # 1.306s select * from TEST_TABLE_SUOYIN where id>= 1000 and id <= 10000; # 1.337s select * from TEST_TABLE_SUOYIN where name = 'egon1000'; # 1.392s select * from TEST_TABLE_SUOYIN where id = 12000 and name = 'egon12000'; # 1.292s select * from TEST_TABLE_SUOYIN where email = 'egon10002@oldboy'; # 1.424s select * from TEST_TABLE_SUOYIN where gender = 'male' and email = 'egon10002@oldboy'; # 1.628s # 添加主鍵索引 alter table TEST_TABLE_SUOYIN add primary key(id); # 添加普通索引 的兩種方式 create index name on TEST_TABLE_SUOYIN(name); alter table TEST_TABLE_SUOYIN add index(gender); # 添加組合索引 的兩種方式 create index i_id_name on TEST_TABLE_SUOYIN(id, name); alter table TEST_TABLE_SUOYIN add index(name, gender, email); # 添加唯一索引 alter table TEST_TABLE_SUOYIN add unique(email); # 已添加索引 select id, name, gender, email from TEST_TABLE_SUOYIN where id = 1000; # 0.008s select id, name, gender, email from TEST_TABLE_SUOYIN where id BETWEEN 1000 and 100000; # 0.021s select id, name, gender, email from TEST_TABLE_SUOYIN where id>= 1000 and id <= 10000; # 0.007s select id, name, gender, email from TEST_TABLE_SUOYIN where name = 'egon1000'; # 0.008s select id, name, gender, email from TEST_TABLE_SUOYIN where id = 12000 and name = 'egon12000'; # 0.007s select id, name, gender, email from TEST_TABLE_SUOYIN where email = 'egon10002@oldboy'; # 0.007s # index(name, gender, email) 僅添加這個組合索引 select id, name, gender, email from TEST_TABLE_SUOYIN where gender = 'male' and email = 'egon10002@oldboy'; # 0.000s 索引失效 未遵守最左前綴匹配原則 select id, name, gender, email from TEST_TABLE_SUOYIN where gender = 'male' and email = 'egon10002@oldboy' and name = 'egon10002'; # 0.001s 索引有效 遵守最左前綴匹配原則 # 索引無法命中的情況 %模糊查詢 # 所以得出結論 %加在前面所以無法命中 只能加在后面才能夠命中 select * from TEST_TABLE_SUOYIN where email like '%on10000@oldboy'; # 1.925s select * from TEST_TABLE_SUOYIN where email like '%on10000@%'; # 1.940s select * from TEST_TABLE_SUOYIN where email like 'egon10000@%'; # 0.008s # 索引無法命中的情況 使用函式 select * from TEST_TABLE_SUOYIN where REVERSE(email) = 'yobdlo@00001noge'; # 1.840s # 索引無法命中的情況 or # 在測驗這個情況時 我洗掉了 gender 的索引;特別在于 or 前后的列有未添加索引的 索引才會失效;如果前后兩個列都有索引,則索引生效 select * from TEST_TABLE_SUOYIN where id = 1000000 or gender = 'sss'; # 索引未命中 select * from TEST_TABLE_SUOYIN where id = 1000000 or email = 'egon1000000@oldboy'; # 索引命中 # 索引無法命中的情況 型別不一致 # email的型別是字串,如果查詢的值型別不是字串則不會命中索引 select * from TEST_TABLE_SUOYIN where email = 123; # 2.133s select * from TEST_TABLE_SUOYIN where email = 'egon1000000@oldboy'; # 0.008s # 索引無法命中的情況 != # 因為資料量太大 所以無法直觀的測出來 select * from TEST_TABLE_SUOYIN where id != 1; # 文章上說:主鍵的 != 會走索引 select * from TEST_TABLE_SUOYIN where email != 'egon1@oldboy'; # 反之則不會 # 索引無法命中的情況 > < # 文章上說:主鍵或索引是整數型別的可以命中索引,但是實測字符也是可以命中的 select * from TEST_TABLE_SUOYIN where id > 2999999; # 0.002s select * from TEST_TABLE_SUOYIN where id < 2; # 0.006s select * from TEST_TABLE_SUOYIN where email > 'xxxx'; # 0.001s # 索引無法命中的情況 ORDER BY # 從第一次查詢出來的結果看 查詢 gender 的時間明顯比 id 要久,所以 order by的條件列有索引 查詢結果列 也需要有索引 才會命中索引 # 但是如果對主鍵排序 則不看 結果列 是否有索引 也會命中索引 select gender from TEST_TABLE_SUOYIN where id BETWEEN 1000 and 10000 order by name DESC; # 0.020s select id from TEST_TABLE_SUOYIN where id BETWEEN 1000 and 10000 order by name DESC; # 0.005s # 索引無法命中的情況 最左前綴原則(組合索引) # 比如 添加 index(a,b,c,d)這個組合索引 以下例子是否命中索引 select * from dual where c = 1 and d = 1 and b = 1 and a = 1; # 命中索引 select * from dual where c = 1 and d = 1 and b = 1; # 未命中索引 因為(a不在查詢條件中,且a在組合索引種的第一位,所以之后的索引都不會命中) SELECT * from dual where a = 1 and c = 1 and d = 1; # 僅只有a命中索引(b不在查詢條件中,且b在組合索引種的第二位,所以之后的索引都不會命中) # 索引無法命中的情況 count(?) # 文章中說count(1) | count(列名) 代替 count(*) 這樣會命中索引 但是實測下來 沒有區別; select count(*) from TEST_TABLE_SUOYIN; # 1.732s select count(1) from TEST_TABLE_SUOYIN; # 2.060s select count(id) from TEST_TABLE_SUOYIN; # 1.778s # 索引無法命中的情況 添加索引時 如果列型別時text型別的,必須制定長度 create index index_name on table_name(title(19)) #text型別,必須制定長度 # 洗掉主鍵索引 alter table TEST_TABLE_SUOYIN drop PRIMARY key; # 洗掉普通索引 drop index name on TEST_TABLE_SUOYIN; drop index gender on TEST_TABLE_SUOYIN; # 洗掉唯一索引 drop index email on TEST_TABLE_SUOYIN; # 洗掉組合索引 drop index i_id_name on TEST_TABLE_SUOYIN; drop index name_2 on TEST_TABLE_SUOYIN;
五 正確使用索引
1、覆寫索引
#分析
select * from TABLE_NAME where id=123;
該sql命中了索引,但未覆寫索引,
利用id=123到索引的資料結構中定位到該id在硬碟中的位置,或者說再資料表中的位置,
但是我們select的欄位為*,除了id以外還需要其他欄位,這就意味著,我們通過索引結構取到id還不夠,
還需要利用該id再去找到該id所在行的其他欄位值,這是需要時間的,很明顯,如果我們只select id,
就減去了這份苦惱,如下
select id from TABLE_NAME where id=123;
這條就是覆寫索引了,命中索引,且從索引的資料結構直接就取到了id在硬碟的地址,速度很快
2、聯合索引
create index ne on s1(name,email);#組合索引
3、索引合并
#索引合并:把多個單列索引合并使用
#分析:
組合索引能做到的事情,我們都可以用索引合并去解決,比如
create index ne on s1(name,email);#組合索引
我們完全可以單獨為name和email創建索引
組合索引可以命中:
select * from s1 where name='egon' ;
select * from s1 where name='egon' and email='adf';
索引合并可以命中:
select * from s1 where name='egon' ;
select * from s1 where email='adf';
select * from s1 where name='egon' and email='adf';
乍一看好像索引合并更好了:可以命中更多的情況,但其實要分情況去看,如果是name='egon' and email='adf',
那么組合索引的效率要高于索引合并,如果是單條件查,那么還是用索引合并比較合理
4、添加索引遵循原則
#1、最左前綴匹配原則,非常重要的原則,
create index ix_name_email on s1(name,email,)
- 最左前綴匹配:必須按照從左到右的順序匹配
select * from s1 where name='egon'; #可以
select * from s1 where name='egon' and email='asdf'; #可以
select * from s1 where email='[email protected]'; #不可以
mysql會一直向右匹配直到遇到范圍查詢(>、<、between、like)就停止匹配,
比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)順序的索引,
d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調整,
#2、=和in可以亂序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優化器會幫你優化成索引可以識別的形式
#3、盡量選擇區分度高的列作為索引,區分度的公式是count(distinct col)/count(*),
表示欄位不重復的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別欄位可能在大資料面前區分度就是0,那可能有人會問,這個比例有什么經驗值嗎?使用場景不同,
這個值也很難確定,一般需要join的欄位我們都要求是0.1以上,即平均1條掃描10條記錄
#4、索引列不能參與計算,保持列“干凈”,比如from_unixtime(create_time) = ’2014-05-29’
就不能使用到索引,原因很簡單,b+樹中存的都是資料表中的欄位值,但進行檢索時,需要把所有元素都應用函式才能比較,顯然成本太大,所以陳述句應該寫成create_time = unix_timestamp(’2014-05-29’);
最左前綴示范
select * from s1 where id>3 and name='egon' and email='[email protected]' and gender='male'; create index idx on s1(id,name,email,gender); #未遵循最左前綴 select * from s1 where id>3 and name='egon' and email='[email protected]' and gender='male'; drop index idx on s1; create index idx on s1(name,email,gender,id); #遵循最左前綴 select * from s1 where id>3 and name='egon' and email='[email protected]' and gender='male';
1 最左前綴匹配 2 index(id,age,email,name); 3 #條件中一定要出現id(只要出現id就會提升速度) 4 id 5 id age 6 id email 7 id name 8 9 email #不行 如果單獨這個開頭就不能提升速度了 10 mysql> select count(*) from s1 where id=3000; 11 +----------+ 12 | count(*) | 13 +----------+ 14 | 1 | 15 +----------+ 16 1 row in set (0.11 sec) 17 18 mysql> create index xxx on s1(id,name,age,email); 19 Query OK, 0 rows affected (6.44 sec) 20 Records: 0 Duplicates: 0 Warnings: 0 21 22 mysql> select count(*) from s1 where id=3000; 23 +----------+ 24 | count(*) | 25 +----------+ 26 | 1 | 27 +----------+ 28 1 row in set (0.00 sec) 29 30 mysql> select count(*) from s1 where name='egon'; 31 +----------+ 32 | count(*) | 33 +----------+ 34 | 299999 | 35 +----------+ 36 1 row in set (0.16 sec) 37 38 mysql> select count(*) from s1 where email='[email protected]'; 39 +----------+ 40 | count(*) | 41 +----------+ 42 | 1 | 43 +----------+ 44 1 row in set (0.15 sec) 45 46 mysql> select count(*) from s1 where id=1000 and email='[email protected]'; 47 +----------+ 48 | count(*) | 49 +----------+ 50 | 0 | 51 +----------+ 52 1 row in set (0.00 sec) 53 54 mysql> select count(*) from s1 where email='[email protected]' and id=3000; 55 +----------+ 56 | count(*) | 57 +----------+ 58 | 0 | 59 +----------+ 60 1 row in set (0.00 sec) 建聯合索引,最左匹配
索引無法命中的情況需要注意:
- like '%xx' select * from tb1 where email like '%cn'; - 使用函式 select * from tb1 where reverse(email) = 'wupeiqi'; - or select * from tb1 where nid = 1 or name = '[email protected]'; 特別的:當or條件中有未建立索引的列才失效,以下會走索引 select * from tb1 where nid = 1 or name = 'seven'; select * from tb1 where nid = 1 or name = '[email protected]' and email = 'alex' - 型別不一致 如果列是字串型別,傳入條件是必須用引號引起來,不然... select * from tb1 where email = 999; 普通索引的不等于不會走索引 - != select * from tb1 where email != 'alex' 特別的:如果是主鍵,則還是會走索引 select * from tb1 where nid != 123 - > select * from tb1 where email > 'alex' 特別的:如果是主鍵或索引是整數型別,則還是會走索引 select * from tb1 where nid > 123 select * from tb1 where num > 123 #排序條件為索引,則select欄位必須也是索引欄位,否則無法命中 - order by select name from s1 order by email desc; 當根據索引排序時候,select查詢的欄位如果不是索引,則不走索引 select email from s1 order by email desc; 特別的:如果對主鍵排序,則還是走索引: select * from tb1 order by nid desc; - 組合索引最左前綴 如果組合索引為:(name,email) name and email -- 使用索引 name -- 使用索引 email -- 不使用索引 - count(1)或count(列)代替count(*)在mysql中沒有差別了 - create index xxxx on tb(title(19)) #text型別,必須制定長度
- 避免使用select * - count(1)或count(列) 代替 count(*) - 創建表時盡量時 char 代替 varchar - 表的欄位順序固定長度的欄位優先 - 組合索引代替多個單列索引(經常使用多個條件查詢時) - 盡量使用短索引 - 使用連接(JOIN)來代替子查詢(Sub-Queries) - 連表時注意條件型別需一致 - 索引散列值(重復少)不適合建索引,例:性別不適合
六 慢查詢優化的基本步驟
0、先運行看看是否真的很慢,注意設定SQL_NO_CACHE
1、where條件單表查,鎖定最小回傳記錄表,這句話的意思是把查詢陳述句的where都應用到表中回傳的記錄數最小的表開始查起,單表每個欄位分別查詢,看哪個欄位的區分度最高
2、explain查看執行計劃,是否與1預期一致(從鎖定記錄較少的表開始查詢)
3、order by limit 形式的sql陳述句讓排序的表優先查
4、了解業務方使用場景
5、加索引時參照建索引的幾大原則
6、觀察結果,不符合預期繼續從0分析
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/413165.html
標籤:MySQL
上一篇:工具 | 如何對 MySQL 進行 TPC-C 測驗?
下一篇:canal
