MySQL創建索引


創建索引

#方法一:創建表時
      CREATE TABLE 表名 (
                字段名1  數據類型 [完整性約束條件…],
                字段名2  數據類型 [完整性約束條件…],
                [UNIQUE | FULLTEXT | SPATIAL ]   INDEX | KEY
                [索引名]  (字段名[(長度)]  [ASC |DESC]) 
                );


#方法二:CREATE在已存在的表上創建索引
        CREATE  [UNIQUE | FULLTEXT | SPATIAL ]  INDEX  索引名 
                     ON 表名 (字段名[(長度)]  [ASC |DESC]) ;


#方法三:ALTER TABLE在已存在的表上創建索引
        ALTER TABLE 表名 ADD  [UNIQUE | FULLTEXT | SPATIAL ] INDEX
                             索引名 (字段名[(長度)]  [ASC |DESC]) ;
                             
#刪除索引:DROP INDEX 索引名 ON 表名字;
#方式一
create table t1(
    id int,
    name char,
    age int,
    sex enum('male','female'),
    unique key uni_id(id),
    index ix_name(name) #index沒有key
);
create table t1(
    id int,
    name char,
    age int,
    sex enum('male','female'),
    unique key uni_id(id),
    index(name) #index沒有key
);


#方式二
create index ix_age on t1(age);


#方式三
alter table t1 add index ix_sex(sex);
alter table t1 add index(sex);

#查看
mysql> show create table t1;
| t1    | CREATE TABLE `t1` (
  `id` int(11) DEFAULT NULL,
  `name` char(1) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `sex` enum('male','female') DEFAULT NULL,
  UNIQUE KEY `uni_id` (`id`),
  KEY `ix_name` (`name`),
  KEY `ix_age` (`age`),
  KEY `ix_sex` (`sex`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
例子

測試索引

數據准備

#1. 准備表
create table s1(
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,'eva','female',concat('eva',i,'@oldboy'));
        set i=i+1;
    end while;
END$$ #$$結束
delimiter ; #重新聲明分號為結束符號

#3. 查看存儲過程
show create procedure auto_insert1\G 

#4. 調用存儲過程
call auto_insert1();
View Code

在沒有索引的前提下測試查詢速度

#無索引:mysql根本就不知道到底是否存在id等於333333333的記錄,只能把數據表從頭到尾掃描一遍,此時有多少個磁盤塊就需要進行多少IO操作,所以查詢速度很慢
mysql> select * from s1 where id=333333333;
Empty set (0.33 sec)

在表中已經存在大量數據的前提下,為某個字段段建立索引,建立速度會很慢

在索引建立完畢后,以該字段為查詢條件時,查詢速度提升明顯

PS:

1. mysql先去索引表里根據b+樹的搜索原理很快搜索到id等於333333333的記錄不存在,IO大大降低,因而速度明顯提升

2. 我們可以去mysql的data目錄下找到該表,可以看到占用的硬盤空間多了

3. 需要注意,如下圖

總結

#1. 一定是為搜索條件的字段創建索引,比如select * from s1 where id = 333;就需要為id加上索引

#2. 在表中已經有大量數據的情況下,建索引會很慢,且占用硬盤空間,建完后查詢速度加快
比如create index idx on s1(id);會掃描表中所有的數據,然后以id為數據項,創建索引結構,存放於硬盤的表中。
建完以后,再查詢就會很快了。

#3. 需要注意的是:innodb表的索引會存放於s1.ibd文件中,而myisam表的索引則會有單獨的索引文件table1.MYI

MySAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在innodb中,表數據文件本身就是按照B+Tree(BTree即Balance True)組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此innodb表數據文件本身就是主索引。
因為inndob的數據文件要按照主鍵聚集,所以innodb要求表必須要有主鍵(Myisam可以沒有),如果沒有顯式定義,則mysql系統會自動選擇一個可以唯一標識數據記錄的列作為主鍵,如果不存在這種列,則mysql會自動為innodb表生成一個隱含字段作為主鍵,這字段的長度為6個字節,類型為長整型.

如果才能正確命中索引

索引未命中

並不是說我們創建了索引就一定會加快查詢速度,若想利用索引達到預想的提高查詢速度的效果,我們在添加索引時,必須遵循以下問題

范圍問題,或者說條件不明確

條件中出現這些符號或關鍵字:>、>=、<、<=、!= 、between...and...、like、

大於號、小於號

不等於!=

between ...and...

like

區分度問題

盡量選擇區分度高的列作為索引,區分度的公式是count(distinct col)/count(*),表示字段不重復的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什么經驗值嗎?使用場景不同,這個值也很難確定,一般需要join的字段我們都要求是0.1以上,即平均1條掃描10條記錄

mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | YES  | MUL | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(5)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  | MUL | NULL    |       |
+--------+-------------+------+-----+---------+-------+
rows in set (0.00 sec)

mysql> drop index a on s1;
Query OK, 0 rows affected (0.20 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> drop index d on s1;
Query OK, 0 rows affected (0.18 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | YES  |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(5)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
rows in set (0.00 sec)
先把表中的索引都刪除,讓我們專心研究區分度的問題

我們編寫存儲過程為表s1批量添加記錄,name字段的值均為egon,也就是說name這個字段的區分度很低(gender字段也是一樣的,我們稍后再搭理它)

回憶b+樹的結構,查詢的速度與樹的高度成反比,要想將樹的高低控制的很低,需要保證:在某一層內數據項均是按照從左到右,從小到大的順序依次排開,即左1<左2<左3<...

而對於區分度低的字段,無法找到大小關系,因為值都是相等的,毫無疑問,還想要用b+樹存放這些等值的數據,只能增加樹的高度,字段的區分度越低,則樹的高度越高。極端的情況,索引字段的值都一樣,那么b+樹幾乎成了一根棍。本例中就是這種極端的情況,name字段所有的值均為'egon'

#現在我們得出一個結論:為區分度低的字段建立索引,索引樹的高度會很高,然而這具體會帶來什么影響呢???

#1:如果條件是name='xxxx',那么肯定是可以第一時間判斷出'xxxx'是不在索引樹中的(因為樹中所有的值均為'egon’),所以查詢速度很快

#2:如果條件正好是name='egon',查詢時,我們永遠無
分析原因

索引列不能在條件中參與計算

索引列不能在條件中參與計算,保持列“干凈”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,需要把所有元素都應用函數才能比較,顯然成本太大。所以語句應該寫成create_time = unix_timestamp(’2014-05-29’)

 

 and/or

#1、and與or的邏輯
    條件1 and 條件2:所有條件都成立才算成立,但凡要有一個條件不成立則最終結果不成立
    條件1 or 條件2:只要有一個條件成立則最終結果就成立

#2、and的工作原理
    條件:
        a = 10 and b = 'xxx' and c > 3 and d =4
    索引:
        制作聯合索引(d,a,b,c)
    工作原理:
        對於連續多個and:mysql會按照聯合索引,從左到右的順序找一個區分度高的索引字段(這樣便可以快速鎖定很小的范圍),加速查詢,即按照d—>a->b->c的順序

#3、or的工作原理
    條件:
        a = 10 or b = 'xxx' or c > 3 or d =4
    索引:
        制作聯合索引(d,a,b,c)
        
    工作原理:
        對於連續多個or:mysql會按照條件的順序,從左到右依次判斷,即a->b->c->d

在左邊條件成立但是索引字段的區分度低的情況下(name與gender均屬於這種情況),會依次往右找到一個區分度高的索引字段,加速查詢

經過分析,在條件為name='egon' and gender='male' and id>333 and email='xxx'的情況下,我們完全沒必要為前三個條件的字段加索引,因為只能用上email字段的索引,前三個字段的索引反而會降低我們的查詢效率

最左前綴匹配原則

非常重要的原則,對於組合索引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的順序可以任意調整。

其他情況

使用函數
    select * from tb1 where reverse(email) = 'egon';
            
- 類型不一致
    如果列是字符串類型,傳入條件是必須用引號引起來,不然...
    select * from tb1 where email = 999;
    
#排序條件為索引,則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(*)
- 創建表時盡量使用 char 代替 varchar
- 表的字段順序固定長度的字段優先
- 組合索引代替多個單列索引(由於mysql中每次只能使用一個索引,所以經常使用多個條件查詢時更適合使用組合索引)
- 盡量使用短索引
- 使用連接(JOIN)來代替子查詢(Sub-Queries)
- 連表時注意條件類型需一致
- 索引散列值(重復少)不適合建索引,例:性別不適合

聯合索引

聯合索引

聯合索引是指對表上的多個列合起來做一個索引。聯合索引的創建方法與單個索引的創建方法一樣,不同之處僅在於有多個索引列,如:

 
mysql> create table t(
    -> a int,
    -> b int,
    -> primary key(a),
    -> key idx_a_b(a,b)
    -> );
Query OK, 0 rows affected (0.11 sec)
 

覆蓋索引

InnoDB存儲引擎支持覆蓋索引(covering index,或稱索引覆蓋),即從輔助索引中就可以得到查詢記錄,而不需要查詢聚集索引中的記錄。

覆蓋索引 查一個數據不需要回表
    select name from 表 where age = 20   不是覆蓋索引
    select age from 表 where age =20  是覆蓋索引
    select count(age) from 表 where age =20  是覆蓋索引

合並索引

 
當我們為單獨的一列創建索引的時候
    如果條件是這一列,且使用正確就可以命中索引
當我們為兩列分別創建單獨的索引的時候
    如果這兩列都是條件,那么可能只能命中期中一個條件
    如果這兩列都是條件,那么可能會命中兩個索引  - 合並索引
我們為多列直接創建聯合所以
    條件命中聯合索引
 

索引總結:

 
1.條件一定是建立了索引的字段,如果條件使用的字段根本就沒有創建索引,那么索引不生效
2.如果條件是一個范圍,隨着范圍的值逐漸增大,那么索引能發揮的作用也越小
3.如果使用like進行模糊查詢,那么使用a%的形式能命中索引,%a形式不能命中索引
4.盡量選擇區分度高的字段作為索引列
5.索引列不能在條件中參與計算,也不能使用函數
6.在多個條件以and相連的時候,會優點選擇區分度高的索引列來進行查詢
  在多個條件以or相連的時候,就是從左到右依次判斷
7.制作聯合索引
    1.最左前綴原則 a,b,c,d 條件是a的能命中索引,條件是a,b能命中索引,a,b,c能命中,a,c.... 只要沒有a就不能命中索引
        如果在聯合查詢中,總是涉及到同一個字段,那么就在建立聯合索引的時候將這個字段放在最左側
    2.聯合索引 如果按照定義順序,從左到右遇到的第一個在條件中以范圍為條件的字段,索引失效
        盡量將帶着范圍查詢的字段,定義在聯合索引的最后面
    drop index
    如果我們查詢的條件總是多個列合在一起查,那么就建立聯合索引
        create index ind_mix on s1(id,email)

        select * from s1 where id = 1000000    命中索引
        select * from s1 where email = 'eva1000000@oldboy'  未命中索引
        但凡是創建了聯合索引,那么在查詢的時候,再創建順序中從左到右的第一列必須出現在條件中
        select count(*) from s1 where id = 1000000 and email = 'eva10%';  命中索引

        select count(*) from s1 where id = 1000000 and email like 'eva10%'; 可以命中索引
        范圍 :
        select * from s1 where id >3000 and email = 'eva300000@oldboy';  不能命中索引
8.條件中涉及的字段的值必須和定義表中字段的數據類型一致,否則不能命中索引
 

執行計划

執行計划
看看mysql准備怎么執行這條語句 可以看到是否命中索引,計划能命中哪些,實際命中了哪些,執行的順序,是否發生了索引合並,覆蓋索引
explain select * from s1;


免責聲明!

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



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