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:mysql

1. mysql先去索引表裏根據b+樹的搜索原理很快搜索到id等於333333333的記錄不存在,IO大大下降,於是速度明顯提高sql

2. 咱們能夠去mysql的data目錄下找到該表,能夠看到佔用的硬盤空間多了ide

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、大數據

大於號、小於號spa

不等於!=

between ...and...

like

區分度問題

儘可能選擇區分度高的列做爲索引,區分度的公式是count(distinct col)/count(*),表示字段不重複的比例,比例越大咱們掃描的記錄數越少,惟一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不一樣,這個值也很難肯定,通常須要join的字段咱們都要求是0.1以上,即平均1條掃描10條記錄3d

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’)unix

 

 and/or

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

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

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

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

通過分析,在條件爲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;
相關文章
相關標籤/搜索