一、MySQL中常見索引類型
- 普通索引:僅加速查詢
- 主鍵索引:加速查詢、列值唯一、表中只有一個(不可有null)
- 唯一索引:加速查詢、列值唯一(可以有null)
- 組合索引:多列值組成一個索引,專門用於組合搜索,其效率大於索引合並
索引合並:使用多個單列索引組合搜索。
覆蓋索引:select的數據列只用從索引中就能夠取得,不必讀取數據行;換句話說,查詢列要被所建的索引覆蓋。
普通索引

-- 創建表同時添加name字段為普通索引 create table tb( id int not null auto_increment primary key, name char(32) not null, index idx_name(name) ); -- 單獨為表指定普通索引 create index idx_name on tb(name); -- 刪除索引 drop index idx_name on tb; -- 查看索引 show index from tb;

Table # 表的名稱 Non_unique # 如果索引為唯一索引,則為0,如果可以則為1 Key_name # 索引的名稱 Seq_in_index # 索引中的列序列號,從1開始 Column_name # 列名稱 Collation # 列以什么方式存儲在索引中。在MySQL中,有值‘A’(升序)或NULL(無分類) Cardinality # 索引中唯一值的數目的估計值 Sub_part # 如果列只是被部分地編入索引,則為被編入索引的字符的數目。如果整列被編入索引,則為NULL Packed #指示關鍵字如何被壓縮。如果沒有被壓縮,則為NULL Null # 如果列含有NULL,則含有YES。如果沒有,則該列含有NO Index_type # 用過的索引方法(BTREE, FULLTEXT, HASH, RTREE) Comment # 多種評注
主鍵索引
主鍵有兩個功能:加速查詢 和 唯一約束(不可含null)
注意:一個表中最多只能有一個主鍵索引

-- 創建表同時添加id字段為主鍵索引 -- 方式一 create table tb( id int not null auto_increment primary key, name char(4) not null ); -- 方式二 create table tb( id int not null auto_increment, name char(4) not null, primary key(id) ); -- 給某個已經存在的表增加主鍵 alter table tb add primary key(id); -- 刪除主鍵 -- 方式一 alter table tb drop primary key; -- 方式二 -- 如果當前主鍵為自增主鍵,則不能直接刪除,需要先修改自增屬性,再刪除 alter table tb modify id int,drop primary key;
唯一索引
唯一索引有兩個功能:加速查找 和 唯一約束(可含一個null 值)

create table tb( id int not null auto_increment primary key, name char(4) not null, age int not null, unique index idx_age (age) ); -- 給某個已經存在的表創建唯一索引 create unique index idx_age on tb(age);
組合索引
組合索引是將n個列組合成一個索引
應用場景:頻繁的同時使用n列來進行查詢,如:select * from tb where name="pd" and id=888;

create table tb( id int not null, name char(4) not null, age int not null, index idx_name_age (name,age) ); -- 給某個已經存在的表創建組合索引 create index idx_name_age on tb(name,age);
二、聚集索引和非聚集索引(輔助索引)
數據庫中的 B+tree 索引可以分為:聚集索引和非聚集索引
聚集索引:innodb表/索引組織表,即表中數據按主鍵B+樹存放,葉子節點直接存放整條數據,每張表只能有一個聚集索引。
①當你定義一個主鍵時,innodb 存儲引擎則把它當做聚集索引;
②如果你沒有定義一個主鍵,則 innodb 定位到第一個唯一索引,且該索引的所有列值均飛空的,則將其當做聚集索引;
③如果表沒有主鍵或合適的唯一索引,innodb 會產生一個隱藏的行ID值6字節的行ID聚集索引。
補充:由於實際的數據頁只能按照一顆B+tree進行排序,因此每張表只能有一個聚集索引,聚集索引對於主鍵的排序和范圍查找非常有利。
非聚集索引(輔助索引):指葉節點不包含行的全部數據,葉節點除了包含鍵值之外,還包含一個書簽連接,通過該書簽再去找相應的行數據。
innodb存儲引擎輔助索引獲得數據的查找方式如下:
從上圖中可以看出,輔助索引葉節點存放的是主鍵值,獲得主鍵值后,再從聚集索引中查找整行數據。
舉個例子,如果在一顆高度為3的輔助索引中查找數據,首先從輔助索引中獲得主鍵值(3次IO),接着從高度為3的聚集索引中查找以獲得整行數據(3次IO),總共需6次IO。一個表上可以存在多個輔助索引。
聚集索引與輔助索引區別:
- 相同的是:不管是聚集索引還是輔助索引,其內部都是 B+tree 形式,即高度是平衡的,葉子結點存放着所有的數據。
- 不同的是:聚集索引葉子結點存放的是一整行的信息,而輔助索引葉子結點存放的是單個索引字段信息。
何時使用聚集索引或非聚集索引(重要!!!):
三、測試索引
1、創建表

create table userinfo( id int not null, name varchar(16) default null, age int, gender char(1) not null, email varchar(32) default null )engine=myisam default charset=utf8;
注意:MYISAM存儲引擎不產生引擎事務,數據插入速度極快,為方便快速插入測試數據,等我們插完數據,再把存儲類型修改為InnoDB。
2、創建存儲過程

create procedure insert_userinfo(in num int) begin declare i int default 0; declare n int default 1; -- 循環進行數據插入 while n<=num do set i=rand()*50; insert into userinfo(id,name,age,gender,email) values(n,concat("pink",i),rand()*50,if(i%2=0,"女","男"),concat("pink",n,"@qq.com")); set n=n+1; end while; end;
3、調用存儲過程,插入500萬條數據
call insert_userinfo(5000000);
4、修改引擎為INNODB
alter table userinfo engine=innodb;
5、測試索引
①在沒有索引的前提下測試查詢速度
select * from userinfo where id=4567890;
注意:無索引情況,mysql根本就不知道id等於4567890的記錄在哪里,只能把數據表從頭到尾掃描一遍,此時有多少個磁盤塊就需要進行多少IO操作,所以查詢速度很慢。
②在表中已經存在大量數據的前提下,為某個字段段建立索引,建立速度會很慢
create index idx_id on userinfo(id);
③在索引建立完畢后,以該字段為查詢條件時,查詢速度提升明顯
select * from userinfo where id=4567890;
注意:
- mysql先去索引表里根據 b+樹 的搜索原理很快搜索到id為4567890的數據,IO大大降低,因而速度明顯提升
- 我們可以去mysql的data目錄下找到該表,可以看到添加索引后該表占用的硬盤空間多了
- 如果使用沒有添加索引的字段進行條件查詢,速度依舊會很慢(如下圖)
四、正確使用索引
數據庫表中添加索引后確實會讓查詢速度起飛,但前提必須是正確的使用索引來查詢,如果以錯誤的方式使用,則即使建立索引也會不奏效。
即使建立索引,索引也不會生效,例如:
-- 范圍查詢(>、>=、<、<=、!= 、between...and) -- = 等號 select count(*) from userinfo where id=1000; -- 執行索引,索引效率高 -- >、>=、<、<=、between...and 區間查詢 select count(*) from userinfo where id<100; -- 執行索引,區間范圍越小,索引效率越高 select count(*) from userinfo where id>100; -- 執行索引,區間范圍越大,索引效率越低 select count(*) from userinfo where id between 10 and 500000; -- 執行索引,區間范圍越大,索引效率越低 -- != 不等於 select count(*) from userinfo where id!=1000; -- 索引范圍大,索引效率低 -- like "%xx%" -- 為name字段添加索引 create index idx_name on userinfo(name); select count(*) from userinfo where name like "%xxxx%"; -- 全模糊查詢,索引效率低 select count(*) from userinfo where name like "%xxxx"; -- 以什么結尾模糊查詢,索引效率低 -- 例外:當like使用以什么開頭,索引效率高 select * from userinfo where name like "xxxx%";
-- or select count(*) from userinfo where id=1000 or email="xx"; -- email不是索引字段,索引此查詢全表掃描 -- 例外:當or條件中有未建立索引的列才失效,以下會走索引 select count(*) from userinfo where id=1000 or name="pink3"; -- id和name都為索引字段時,or條件也會執行索引
-- 使用函數 select count(*) from userinfo where reverse(name)="1knip"; -- name索引字段,使用函數時,索引失效 -- 例外:索引字段對應的值可以使用函數,我們可以改為以下形式 select count(*) from userinfo where name=reverse("1knip");
-- 類型不一致 -- 如果列是字符串類型,傳入條件是必須用引號引起來 select count(*) from userinfo where name=123; -- 慢 -- 類型一致 select count(*) from userinfo where name="123"; -- 快
-- order by -- 排序條件為索引,則select字段必須也是索引字段,否則無法命中 select email from userinfo order by name desc; -- 無法命中索引 select name from userinfo order by name desc; -- 命中索引
五、組合索引
組合索引:是指對表上的多個列組合起來做一個索引。
組合索引好處,簡單的說有兩個主要原因:
- 一個頂三個;建了一個(a,b,c)的組合索引,那么實際等於建了(a),(a,b),(a,b,c)三個索引,因為每多一個索引,都會增加寫操作的開銷和磁盤空間的開銷。對於大量數據的表,這可是不小的開銷!
- 索引列越多,通過索引篩選出的數據越少。有1000W條數據的表,有如下sql:select * from table where a=1 and b=2 and c=3,假設每個條件可以篩選出10%的數據,如果只有單值索引,那么通過該索引能篩選出1000W*10%=100w條數據,然后再回表從100w條數據中找到符合 b=2 and c= 3 的數據,然后再排序,再分頁;如果是組合索引,通過索引篩選出1000w*10%*10%*10%=1w,然后再排序、分頁,哪個更高效,一眼便知 。
最左匹配原則:從左往右依次使用生效,如果中間某個索引沒有使用,那么斷點前面的索引部分起作用,斷點后面的索引沒有起作用。
select * from tb where a=1 and b=2 and c=3; -- abc三個索引都在where條件里面用到了,而且都發揮了作用 select * from tb where c=3 and b=2 and a=1; -- 這條語句列出來只想說明mysql沒有那么笨,where里面的條件順序在查詢之前會被mysql自動優化,效果跟上一句一樣 select * from tb where a=1 and c=3; -- a用到索引,b沒有用,所以c是沒有用到索引效果的 select * from tb where a=1 and b>2 and c=3; -- a用到了,b也用到了,c沒有用到,這個地方b是范圍值,也算斷點,只不過自身用到了索引 select * from tb where a>1 and b=2 and c=3; -- a用到了,b沒有使用,c沒有使用 select * from tb where a=1 order by b; -- a用到了索引,b在結果排序中也用到了索引的效果 select * from tb where a=1 order by c; -- a用到了索引,但是c沒有發揮排序效果,因為中間斷點了 select * from tb where b=2 order by a; -- b沒有用到索引,排序中a也沒有發揮索引效果
六、注意事項(重要)
1、避免使用select *
2、其他數據庫中使用count(1)或count(列)代替count(*),而mysql數據庫中count(*)經過優化后,效率與前兩種基本一樣
3、創建表時盡量時 char 代替 varchar
4、表的字段順序固定長度的字段優先
5、組合索引代替多個單列索引(經常使用多個條件查詢時)
6、使用連接(JOIN)來代替子查詢(Sub-Queries)
7、不要有超過4個以上的表連接(JOIN)
8、優先執行那些能夠大量減少結果的連接
9、連表時注意條件類型需一致
10、索引散列值不適合建索引,例:性別不適合
七、慢查詢日志
慢查詢日志:將mysql服務器中影響數據庫性能的相關SQL語句記錄到日志文件,通過對這些特殊的SQL語句分析,改進以達到提高數據庫性能的目的。
慢查詢日志參數:
long_query_time # 設定慢查詢的閥值,超出設定值的SQL即被記錄到慢查詢日志,缺省值為10s slow_query_log # 指定是否開啟慢查詢日志 log_slow_queries # 指定是否開啟慢查詢日志(該參數已經被slow_query_log取代,做兼容性保留) slow_query_log_file # 指定慢日志文件存放位置,可以為空,系統會給一個缺省的文件host_name-slow.log log_queries_not_using_indexes # 如果值設置為ON,則會記錄所有沒有利用索引的查詢
查看MySQL慢日志信息:
-- 查詢慢日志配置信息 show variables like "%query%"; -- 修改配置信息 set global slow_query_log = on;
查看不使用索引參數狀態:
-- 顯示參數 show variables like "%log_queries_not_using_indexes"; -- 開啟狀態 set global log_queries_not_using_indexes = on;
查看慢日志顯示的方式:
-- 查看慢日志記錄的方式 show variables like "%log_output%"; -- 設置慢日志在文件和表中同時記錄 set global log_output="FILE,TABLE";
測試慢查詢日志:
-- 查詢時間超過10秒就會記錄到慢查詢日志中 select sleep(3) from userinfo; -- 查看表中的日志 select * from mysql.slow_log;
八、執行計划
explain + 查詢SQL:用於顯示SQL執行信息參數,根據參考信息可以進行SQL優化。
explain select count(*) from userinfo where id=1;
執行計划:讓mysql預估執行操作(一般正確)。 type:查詢計划的連接類型, 有多個參數,先從最佳類型到最差類型介紹 性能:null > system/const > eq_ref > ref > ref_or_null > index_merge > range > index > all 慢: explain select * from userinfo where email="pink"; type: ALL(全表掃描) 特別的: select * from userinfo limit 1; 快: explain select * from userinfo where name="alex"; type: ref(走索引)
九、大數據量分頁優化
執行此段代碼:
select * from userinfo limit 3000000,10;
優化方案:
1、簡單粗暴,不允許查看這么靠后的數據。比如百度就是這樣的,最多翻到76頁就不讓你翻了,這種方式就是從業務上解決。
2、在查詢下一頁時把上一頁的行id作為參數傳遞給客戶端程序,然后sql就改成了
select * from userinfo where id>3000000 limit 10;
這條語句執行也是在毫秒級完成的,id>300w 其實就是讓mysql直接跳到這里了,不用依次在掃描全面所有的行。
3、延遲關聯
分析一下這條語句為什么慢,慢在哪里:
select * from userinfo limit 3000000,10;
慢就慢在這個 * 里面,這個表除了id主鍵肯定還有其他字段,比如 name、age 之類的,因為select * 所以mysql在沿着id主鍵走的時候要回行拿數據,走一下拿一下數據;如果把語句改成:
select id from userinfo limit 3000000,10;
你會發現時間縮短了很多;然后我們在拿id分別去取10條數據就行了。語句就改成了這樣:
select userinfo.* from userinfo inner join (select id from userinfo limit 3000000,10) as tmp on tmp.id=userinfo.id;
PS:三種方法最先考慮第一種,其次第二種,第三種是別無選擇。