MySQL之索引以及正確使用索引


一、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;
View Code
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;
View Code

唯一索引

 唯一索引有兩個功能:加速查找 唯一約束(可含一個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);
View Code

組合索引

組合索引是將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);
View Code

 二、聚集索引和非聚集索引(輔助索引)

 數據庫中的 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;
View Code

注意: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;
View Code

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;

注意:

  1. mysql先去索引表里根據 b+樹 的搜索原理很快搜索到id為4567890的數據,IO大大降低,因而速度明顯提升
  2. 我們可以去mysql的data目錄下找到該表,可以看到添加索引后該表占用的硬盤空間多了
  3. 如果使用沒有添加索引的字段進行條件查詢,速度依舊會很慢(如下圖)

四、正確使用索引

數據庫表中添加索引后確實會讓查詢速度起飛,但前提必須是正確的使用索引來查詢,如果以錯誤的方式使用,則即使建立索引也會不奏效。
即使建立索引,索引也不會生效,例如:

-- 范圍查詢(>、>=、<、<=、!= 、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(走索引)

EXPLAIN 參數詳解

九、大數據量分頁優化

執行此段代碼:

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:三種方法最先考慮第一種,其次第二種,第三種是別無選擇。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM