索引是SQL優化中最重要的手段之一,本文從基礎到原理,帶你深度掌握索引。html
MySQL官方對索引的定義爲:索引(Index)是幫助MySQL高效獲取數據的數據結構,索引對於良好的性能很是關鍵,尤爲是當表中的數據量愈來愈大時,索引對於性能的影響愈發重要。索引優化應該是對查詢性能優化最有效的手段了。索引可以輕易將查詢性能提升好幾個數量級。java
通俗來說,索引相似文章的目錄,用來提升查詢的效率。mysql
常見的索引類型有:主鍵索引、惟一索引、普通索引、全文索引、組合索引算法
當一張表,把某個列設爲主鍵的時候,則該列就是主鍵索引sql
create table a ( id int primary key auto_increment, name varchar(20) not null default '' );
這裏id就是表的主鍵,若是當建立表時沒有指定主鍵索引,也能夠在建立表以後添加:數據庫
alter table table_name add primary key (column_name);
用表中的普通列構建的索引,沒有任何限制數組
create index 索引名 on table_name(column1); alter table table_name add index 索引名(column1);
全文索引主要針對文本文件,好比文章,標題。在MySQL5.6以前,只有MyISAM存儲引擎支持全文索引,MySQL5.6以後InnoDB
存儲引擎也支持全文索引。性能優化
create table c( id int primary key auto_increment , title varchar(20), content text, fulltext(title,content) ) engine=myisam charset utf8;
insert into c(title,content) values ('MySQL Tutorial','DBMS stands for DataBase ...'), ('How To Use MySQL Well','After you went through a ...'), ('Optimizing MySQL','In this tutorial we will show ...'), ('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'), ('MySQL vs. YourSQL','In the following database comparison ...'), ('MySQL Security','When configured properly, MySQL ...');
見名知義,索引列中的值必須是惟一的,可是容許爲空值。d表中name就是惟一索引,相比主鍵索引,主鍵字段不能爲null,也不能重複bash
create table d( id int primary key auto_increment , name varchar(32) unique )
用多個列組合構建的索引,這多個列中的值不容許有空值。數據結構
ALTER TABLE 'table_name' ADD INDEX index_name('col1','col2','col3');
組合索引遵循「最左前綴」原則,使用時最好把最經常使用做爲檢索或排序的列放在最左,依次遞減。組合索引至關於創建了col1,col1col2,col1col2col3 三個索引,而col2或者col3是不能使用索引的。在使用組合索引的時候可能由於列名長度過長而致使索引的key太大,致使效率下降,在容許的狀況下,能夠只取col1和col2的前幾個字符做爲索引。
ALTER TABLE 'table_name' ADD INDEX index_name(col1(4),col2(3));
表示使用col1的前4個字符和col2的前3個字符做爲索引
咱們這裏先簡單剖析一下索引的機制,爲接下來的深刻作一些鋪墊。
傳統的查詢方法,是按照表的順序遍歷的,不論查詢幾條數據,MySQL須要將表的數據從頭至尾遍歷一遍。
在咱們添加完索引以後,MySQL通常經過BTREE算法生成一個索引文件,在查詢數據庫時,找到索引文件進行遍歷,使用可以大幅地查詢的效率的折半查找的方式,找到相應的鍵從而獲取數據。
建立索引是爲產生索引文件的,佔用磁盤空間。索引文件是一個二叉樹類型的文件,可想而知咱們的DML操做((數據操做語言,對錶記錄的(增、刪、改)操做)一樣也會對索引文件進行修改,因此性能會相應的有所降低。
上面已經說到,索引其實是數據庫中知足特定查找算法的數據結構
,這些數據結構以某種方式引用(指向)數據,這樣就能夠在這些數據結構上實現高級查找算法
。
可能咱們都知道,MySQL索引是B+樹
數據結構,固然,實際上索引還有哈希表
、有序數組
等常見的數據結構。
哈希表是一種以鍵-值(key-value)存儲數據的結構,咱們只要輸入待查找的值即key,就能夠找到其對應的值即Value。哈希的思路很簡單,把值放在數組裏,用一個哈希函數把key換算成一個肯定的位置,而後把value放在數組的這個位置。
不可避免地,多個key值通過哈希函數的換算,會出現同一個值的狀況。處理這種狀況的一種方法是,拉出一個鏈表。
因此,須要注意,哈希表後的鏈表並非有序的,區間查詢的話須要掃描鏈表,因此哈希表這種結構適用於只有等值查詢的場景,好比Memcached及其餘一些NoSQL引擎。
另一個你們比較熟悉的數組結構,有序數組在等值查詢和範圍查詢場景中的性能都很是優秀。
若是僅僅看查詢效率,有序數組是很是棒的數據結構。可是,在須要更新數據的時候就麻煩了,你往中間插入一個記錄就必須得挪動後面全部的記錄,成本過高。
因此,有序數組索引只適用於靜態存儲引擎,好比你要保存的是2017年某個城市的全部人口信息,這類不會再修改的數據。
這兩種都不是最主要的索引,常見的索引使用的數據結構是樹結構,樹是數據結構裏相對複雜一些的數據結構,咱們來一步步認識索引的樹結構。
二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。可是,折半查找要求線性表必須採用順序存儲結構,並且表中元素按關鍵字有序排列。
查找方法:首先,假設表中元素是按升序排列,將表中間位置記錄的關鍵字與查找關鍵字比較,若是二者相等,則查找成功;不然利用中間位置記錄將表分紅前、後兩個子表,若是中間位置記錄的關鍵字大於查找關鍵字,則進一步查找前一子表,不然進一步查找後一子表。重複以上過程,直到找到知足條件的記錄,使查找成功,或直到子表不存在爲止,此時查找不成功。
上面提到的有序數組的等值查詢和比較查詢效率很是高,可是更新數據存在問題。
爲了支持頻繁的修改,好比插入數據,咱們須要採用鏈表。鏈表的話,若是是單鏈表,它的查找效率仍是不夠高。
因此,有沒有可使用二分查找的鏈表呢?
爲了解決這個問題,BST(Binary Search Tree)也就是咱們所說的二叉查找樹誕生了。
二叉樹具備如下性質:左子樹的鍵值小於根的鍵值,右子樹的鍵值大於根的鍵值。
以下圖所示就是一棵二叉查找樹,
在這種比較平衡的狀態下查找時間複雜度是O(log(n))。
可是二叉查找樹存在一個問題:在某些極端狀況下會退化成鏈表。
一樣是2,3,4,6,7,8這六個數字,若是咱們插入的數據恰好是有序的,那它就變成這樣👇
這個時候,二叉查找樹查找的時間複雜度就和鏈表同樣,是O(n)。
形成它「叉劈」的緣由是什麼呢? 由於左右子樹深度差太大,這棵樹的左子樹根本沒有節點——也就是它不夠平衡。
因此,咱們有沒有左右子樹深度相差不是那麼大,更加平衡的樹呢? ——那就就是平衡二叉樹,叫作 Balanced binary search trees,或者 AVL 樹。
AVL Trees (Balanced binary search trees) 平衡二叉樹的定義:左右子樹深度差絕對值不能超過 1。
例如左子樹的深度是 2,右子樹的深度只能是 1 或者 3。 這個時候咱們再按順序插入 2,3,4,6,7,8,就不會「叉劈」👇
AVL樹的平衡是怎麼作到的呢?主要用到了兩個操做左旋
、右旋
。
插入 一、二、3。
當咱們插入了 一、2 以後,若是按照二叉查找樹的定義,3 確定是要在 2 的右邊的,這個時候根節點 1 的右節點深度會變成 2,可是左節點的深度是 0,由於它沒有子節點,因此就會違反平衡二叉樹的定義。
那應該怎麼辦呢?由於它是右節點下面接一個右節點,右–右型,因此這個時候咱們要把 2 提上去,這個操做叫作左旋
。
右旋
操做,把 2提上去。既然平衡二叉樹能保持平衡,不會退化,那麼咱們用平衡二叉樹存儲索引能夠嗎?——能夠的。
當咱們用樹的結構來存儲索引的時候,訪問一個節點就要跟磁盤之間發生一次 IO。 InnoDB 操做磁盤的最小的單位是一頁(或者叫一個磁盤塊)。與主存不一樣,磁盤I/O存在機械運動耗費,所以磁盤I/O的時間消耗是巨大的。
因此若是每一個節點存儲的數據太少,從索引中找到咱們須要的數據,就要訪問更多的節點,意味着跟磁盤交互次數就會過多。
那麼解決方案是什麼?
讓每一個節點存儲更多的數據。
讓節點上有更多的關鍵字。
節點上的關鍵字的數量越多,咱們的指針數也越多,也就是意味着能夠有更多的分叉(咱們把它叫作「路數」)。
由於分叉數越多,樹的深度就會減小(根節點是 0)。 這樣,樹就從瘦高變成了矮胖。
這個時候,咱們的樹就再也不是二叉了,而是多叉,或者叫作多路
。
接下來看一下多路平衡查找樹,也就是B樹。
B樹是一種多叉平衡查找樹,以下圖主要特色:
以上圖爲例,咱們來簡單看幾個查詢:
B樹看起來很完美,到這就結束了嗎?並無。
B樹不支持範圍查詢的快速查找,你想一想這麼一個狀況若是咱們想要查找10和35之間的數據,查找到15以後,須要回到根節點從新遍歷查找,須要從根節點進行屢次遍歷,查詢效率有待提升。
若是data存儲的是行記錄,行的大小隨着列數的增多,所佔空間會變大。這時,一個頁中可存儲的數據量就會變少,樹相應就會變高,磁盤IO次數就會變大
因此接下來就引入咱們的終極數據結構——B+樹。
B+樹,做爲B樹的升級版,在B樹基礎上,MySQL在B樹的基礎上繼續改造,使用B+樹構建索引。B+樹和B樹最主要的區別在於非葉子節點是否存儲數據的問題
- B樹:非葉子節點和葉子節點都會存儲數據。
- B+樹:只有葉子節點纔會存儲數據,非葉子節點至存儲鍵值。葉子節點之間使用雙向指針鏈接,最底層的葉子節點造成了一個雙向有序鏈表。
來看一下InnoDB裏的B+樹的具體存儲結構:
來講一下這張圖的重點:
舉個例子:假設一條記錄是 1K,一個葉子節點(一頁)能夠存儲 16 條記錄。非葉子節點能夠存儲多少個指針?
假設索引字段是 bigint 類型,長度爲 8 字節。指針大小在 InnoDB 源碼中設置爲 6 字節,這樣一共 14 字節。非葉子節點(一頁)能夠存儲 16384/14=1170 個這樣的 單元(鍵值+指針),表明有 1170 個指針。
樹深度爲 2 的時候,有 1170^2 個葉子節點,能夠存儲的數據爲 1170*1170*16=21902400。
在查找數據時一次頁的查找表明一次 IO,也就是說,一張 2000 萬左右的表,查詢數據最多須要訪問 3 次磁盤。
因此在 InnoDB 中 B+ 樹深度通常爲 1-3 層,它就能知足千萬級的數據存儲。
咱們來看一下 B+Tree 的數據搜尋過程:
3)添加了指向相鄰葉節點的指針,造成了帶有順序訪問指針的B+Tree,這樣作是爲了提升區間查找的效率,只要找到第一個值那麼就能夠順序的查找後面的值。
總結一下,InnoDB 中的 B+Tree 的特色:
2)掃庫、掃表能力更強(若是咱們要對錶進行全表掃描,只須要遍歷葉子節點就能夠 了,不須要遍歷整棵 B+Tree 拿到全部的數據)
MySQL中最多見的兩種存儲引擎分別是MyISAM和InnoDB,分別實現了非聚簇索引
和聚簇索引
。
首先要介紹幾個概念,在索引的分類中,咱們能夠按照索引的鍵是否爲主鍵來分爲「主鍵索引
」和「輔助索引
」,使用主鍵鍵值創建的索引稱爲「主鍵索引
」,其它的稱爲「輔助索引
」。所以主鍵索引
只能有一個,輔助索引能夠有不少個。
MyISAM存儲引擎採用的是非聚簇索引,非聚簇索引的主鍵索引和輔助索引基本上是相同的
,只是主鍵索引不容許重複,不容許空值,他們的葉子結點的key都存儲指向鍵值對應的數據的物理地址。
非聚簇索引的數據表和索引表是分開存儲的。
非聚簇索引中的數據是根據數據的插入順序保存。所以非聚簇索引更適合單個數據的查詢。插入順序不受鍵值影響。
思考:既然非聚簇索引的主鍵索引索引和輔助索引指向相同的內容,爲何還要輔助索引呢?索引不就是用來查詢的嗎,用在哪些地方呢?不就是WHERE和ORDER BY 語句後面嗎,那麼若是查詢的條件不是主鍵怎麼辦呢,這個時候就須要輔助索引了。
聚簇索引的主鍵索引的葉子結點存儲的是鍵值對應的數據自己,輔助索引的葉子結點存儲的是鍵值對應的數據的主鍵鍵值。所以主鍵的值長度越小越好,類型越簡單越好。
聚簇索引的數據和主鍵索引存儲在一塊兒。
從上圖中能夠看到輔助索引的葉子節點的data存儲的是主鍵的值,主鍵索引的葉子節點的data存儲的是數據自己,也就是說數據和索引存儲在一塊兒,而且索引查詢到的地方就是數據(data)自己,那麼索引的順序和數據自己的順序就是相同的。
由於聚簇輔助索引存儲的是主鍵的鍵值,所以能夠在數據行移動或者頁分裂的時候下降成本,由於這時不用維護輔助索引。可是因爲主鍵索引存儲的是數據自己,所以聚簇索引會佔用更多的空間。
聚簇索引在插入新數據的時候比非聚簇索引慢不少,由於插入新數據時須要檢測主鍵是否重複,這須要遍歷主索引的全部葉節點,而非聚簇索引的葉節點保存的是數據地址,佔用空間少,所以分佈集中,查詢的時候I/O更少,但聚簇索引的主索引中存儲的是數據自己,數據佔用空間大,分佈範圍更大,可能佔用好多的扇區,所以須要更屢次I/O才能遍歷完畢。
第一個叫作列的離散度,咱們先來看一下列的離散度的公式:
count(distinct(column_name)) : count(*)
列的所有不一樣值和全部數據行的比例。數據行數相同的狀況下,分子越大,列的離散度就越高。
mysql> SELECT * FROM `test`.`user` ORDER BY `id` LIMIT 10 OFFSET 0; +----+-----------+--------+-------------+ | id | name | gender | phone | +----+-----------+--------+-------------+ | 1 | 秦囀 | 0 | 13601722591 | | 2 | 李鎰榘 | 0 | 15204160836 | | 3 | 陳艮 | 0 | 13601994087 | | 4 | 沈夷旌 | 0 | 15507785988 | | 5 | 朱桐泰 | 1 | 13201268193 | | 6 | 周韜蕊 | 1 | 15705478612 | | 7 | 馮叻加 | 0 | 13705834063 | | 8 | 王焓 | 1 | 15006956358 | | 9 | 黃芪 | 0 | 15108012536 | | 10 | 吳笄遊 | 0 | 15301860708 | +----+-----------+--------+-------------+ 10 rows in set (0.00 sec)
簡單來講,若是列的重複值越多,離散度就越低,重複值越少,離散度就越高。
瞭解了離散度的概念以後,咱們再來思考一個問題,咱們在 name 上面創建索引和 在 gender 上面創建索引有什麼區別。
當咱們用在 gender 上創建的索引去檢索數據的時候,因爲重複值太多,須要掃描的行數就更多。例如,咱們如今在 gender 列上面建立一個索引,而後看一下執行計劃。
ALTER TABLE user ADD INDEX idx_user_gender (gender); -- 耗時比較久 EXPLAIN SELECT * FROM `user` WHERE gender = 0;
+----+-------------+-------------+------------+------+-----------------+-----------------+---------+-------+---------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+-----------------+-----------------+---------+-------+---------+----------+-------+ | 1 | SIMPLE | user | NULL | ref | idx_user_gender | idx_user_gender | 2 | const | 2492574 | 100.00 | NULL | +----+-------------+-------------+------------+------+-----------------+-----------------+---------+-------+---------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
而 name 的離散度更高,好比「陳艮」的這名字,只須要掃描一行。
ALTER TABLE user ADD INDEX idx_user_name (name); EXPLAIN SELECT * FROM `user` WHERE name = '陳艮';
+----+-------------+-------------+------------+------+----------------------------+----------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+----------------------------+----------+---------+-------+------+----------+-------+ | 1 | SIMPLE | user | NULL | ref | idx_name | idx_name | 1023 | const | 1 | 100.00 | NULL | +----+-------------+-------------+------------+------+----------------------------+----------+---------+-------+------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
查看錶上的索引,Cardinality [kɑ:dɪ’nælɪtɪ]表明基數,表明預估的不重複的值的數量。索引的基數與表總行數越接近,列的離散度就越高。
mysql> show indexes from user; +-------------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | +-------------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | user | 0 | PRIMARY | 1 | id | A | 4985145 | NULL | NULL | | BTREE | | | | user | 1 | idx_name | 1 | name | A | 2605146 | NULL | NULL | YES | BTREE | | | | user | 1 | idx_user_gender | 1 | gender | A | 1 | NULL | NULL | YES | BTREE | | | | user | 1 | comidx_name_phone | 1 | name | A | 2595718 | NULL | NULL | YES | BTREE | | | | user | 1 | comidx_name_phone | 2 | phone | A | 4972647 | NULL | NULL | YES | BTREE | | | +-------------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 5 rows in set (0.00 sec)
若是在索引 B+Tree 結構裏面的重複值太多,MySQL 的優化器發現走索引跟使用全表掃描差不了多少的時候,就算建了索引,也不必定會走索引。
前面咱們說的都是針對單列建立的索引,但有的時候咱們的多條件查詢的時候,也會創建組合索引。單列索引能夠當作是特殊的組合索引。
好比咱們在 user 表上面,給 name 和 phone 創建了一個組合索引。
ALTER TABLE user add INDEX comidx_name_phone (name,phone);
組合索引在 B+Tree 中是複合的數據結構,它是按照從左到右的順序來創建搜索樹的 (name 在左邊,phone 在右邊)。
從這張圖能夠看出來,name 是有序的,phone 是無序的。當 name 相等的時候, phone 纔是有序的。
這個時候咱們使用 where name= ‘wangwu‘ and phone = ‘139xx ‘去查詢數據的時候, B+Tree 會優先比較 name 來肯定下一步應該搜索的方向,往左仍是往右。若是 name 相同的時候再比較 phone。可是若是查詢條件沒有 name,就不知道第一步應該查哪一個 節點,由於創建搜索樹的時候 name 是第一個比較因子,因此用不到索引。
因此,咱們在創建組合索引的時候,必定要把最經常使用的列放在最左邊。 好比下面的三條語句,能用到組合索引嗎?
mysql> EXPLAIN SELECT * FROM user WHERE name= '陳艮' AND phone = '13601994087'; +----+-------------+-------------+------------+------+----------------------------+-------------------+---------+-------------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+----------------------------+-------------------+---------+-------------+------+----------+-------+ | 1 | SIMPLE | user_innodb | NULL | ref | comidx_name_phone | comidx_name_phone | 1070 | const,const | 1 | 100.00 | NULL | +----+-------------+-------------+------------+------+----------------------------+-------------------+---------+-------------+------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM user WHERE name= '陳艮'; +----+-------------+-------------+------------+------+----------------------------+----------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+----------------------------+----------+---------+-------+------+----------+-------+ | 1 | SIMPLE | user_innodb | NULL | ref | comidx_name_phone | idx_name | 1023 | const | 19 | 100.00 | NULL | +----+-------------+-------------+------------+------+----------------------------+----------+---------+-------+------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM user WHERE phone = '13601994087'; +----+-------------+-------------+------------+------+---------------+------+---------+------+---------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+---------------+------+---------+------+---------+----------+-------------+ | 1 | SIMPLE | user | NULL | ALL | NULL | NULL | NULL | NULL | 4985148 | 10.00 | Using where | +----+-------------+-------------+------------+------+---------------+------+---------+------+---------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
當建立(a,b,c)聯合索引時,至關於建立了(a)單列索引,(a,b)組合索引以及(a,b,c)組合索引,想要索引生效的話,只能使用 a和a,b和a,b,c三種組合;固然,b,a也是好使的,由於sql會對它優化。
用 where b=? 和 where b=? and c=? 和 where a=? and c=?是不能使用到索引。不能不用第一個字段,不能中斷。
這裏就是 MySQL 組合索引的最左匹配原則。
在聚簇索引裏,經過輔助索引查找數據,先經過索引找到主鍵索引的鍵值,再經過主鍵值查出索引裏面沒有的數據,它比基於主鍵索引的查詢多掃描了一棵索引樹,這個過程就叫回表。
例如:select * from user where name = ‘lisi’;
在輔助索引裏面,無論是單列索引仍是聯合索引,若是 select 的數據列只用從索引中就可以取得,沒必要從數據區中讀取,這時候使用的索引就叫作覆蓋索引,這樣就避免了回表。
咱們先來建立一個聯合索引:
-- 建立聯合索引 ALTER TABLE user add INDEX 'comixd_name_phone' ('name','phone');
這三個查詢語句都用到了覆蓋索引:
EXPLAIN SELECT name,phone FROM user WHERE name= '陳艮' AND phone = '13601994087'; EXPLAIN SELECT name FROM user WHERE name= '陳艮' AND phone = '13601994087'; EXPLAIN SELECT phone FROM user WHERE name= '陳艮' AND phone = '13601994087';
Extra 裏面值爲「Using index」表明使用了覆蓋索引。
mysql> EXPLAIN SELECT name FROM user_innodb WHERE name= '陳艮' AND phone = '13601994087'; +----+-------------+-------------+------------+------+----------------------------+-------------------+---------+-------------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+----------------------------+-------------------+---------+-------------+------+----------+-------------+ | 1 | SIMPLE | user_innodb | NULL | ref | idx_name,comidx_name_phone | comidx_name_phone | 1070 | const,const | 1 | 100.00 | Using index | +----+-------------+-------------+------------+------+----------------------------+-------------------+---------+-------------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
select * ,用不到覆蓋索引。
很明顯,由於覆蓋索引減小了 IO 次數,減小了數據的訪問量,能夠大大地提高查詢效率。
「索引條件下推」,稱爲 Index Condition Pushdown (ICP),這是MySQL提供的用某一個索引對一個特定的表從表中獲取元組」,注意咱們這裏特地強調了「一個」,這是由於這樣的索引優化不是用於多表鏈接而是用於單表掃描,確切地說,是單表利用索引進行掃描以獲取數據的一種方式。 它的做用以下
一是說明減小完整記錄(一條完整元組)讀取的個數;
二是說明對於InnoDB彙集索引無效,只能是對SECOND INDEX這樣的非聚簇索引有效。
關閉 ICP:
set optimizer_switch='index_condition_pushdown=off';
查看參數:
show variables like 'optimizer_switch';
如今咱們要查詢全部名字爲陳艮,而且手機號碼後四位爲4087這我的。查詢的 SQL:
SELECT * FROM user WHERE name= '陳艮' and phone LIKE '%4087' ;
這條 SQL 有兩種執行方式:
一、根據組合索引查出全部名字是’陳艮’的二級索引數據,而後回表,到主鍵索引上查詢所有符合條件的數據(19 條數據)。而後返回給 Server 層,在 Server 層過濾出手機號碼後四位爲4087這我的。
二、根據組合索引查出全部名字是’陳艮’的二級索引數據(19 個索引),而後從二級索引 中篩選出手機號碼後四位爲4087的索引(1 個索引),而後再回表,到主鍵索引上查詢所有符合條件的數據(1 條數據),返回給 Server 層。
很明顯,第二種方式到主鍵索引上查詢的數據更少。
注意,索引的比較是在存儲引擎進行的,數據記錄的比較,是在 Server 層進行的。 而當 phone 的條件不能用於索引過濾時,Server 層不會把 phone 的條件傳遞 給存儲引擎,因此讀取了兩條沒有必要的記錄。
這時候,若是知足 name=’陳艮’的記錄有 100000 條,就會有 99999 條沒有 必要讀取的記錄。
執行如下 SQL,Using where:
mysql> EXPLAIN SELECT * FROM user WHERE name= '陳艮' AND phone LIKE '%4087'; +----+-------------+-------------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | user_innodb | NULL | ref | comidx_name_phone | comidx_name_phone | 1023 | const | 19 | 11.11 | Using where | +----+-------------+-------------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
Using Where 表明從存儲引擎取回的數據不所有知足條件,須要在 Server 層過濾。
先用 name條件進行索引範圍掃描,讀取數據表記錄,而後進行比較,檢查是否符合 phone LIKE ‘%4087’ 的條件。此時 19 條中只有 1 條符合條件。
由於索引對於改善查詢性能的做用是巨大的,因此咱們的目標是儘可能使用索引。
根據上一節的分析,咱們總結出索引建立的一些注意點:
一、在用於 where 判斷 order 排序和 join 的(on)字段上建立索引
二、索引的個數不要過多。——浪費空間,更新變慢。
三、區分度低的字段,例如性別,不要建索引。——離散度過低,致使掃描行數過多。
四、頻繁更新的值,不要做爲主鍵或者索引。 ——頁分裂
五、組合索引把散列性高(區分度高)的值放在前面。——最左前綴匹配原則
六、建立複合索引,而不是修改單列索引。——組合索引代替多個單列索引(因爲MySQL中每次只能使用一個索引,因此常用多個條件查詢時更適合使用組合索引)
七、過長的字段,怎麼創建索引?——使用短索引。
當字段值比較長的時候,創建索引會消耗不少的空間,搜索起來也會很慢。咱們能夠經過截取字段的前面一部份內容創建索引,這個就叫前綴索引。
create table shop(address varchar(120) not null); alter table shop add key (address(12));
explain SELECT * FROM 't2' where id+1 = 4;
explain SELECT * FROM 'user' where name = 136; explain SELECT * FROM 'user' where name = '136';
where 條件中 like abc%,like %2673%,like %888 都用不到索引嗎?爲何?
explain select * from user where name like 'wang%'; explain select * from user where name like '%wang';
過濾的開銷太大,因此沒法使用索引。這個時候能夠用全文索引。
NOT LIKE 不能:
explain select *from employees where last_name not like 'wang'
!= (<>)和 NOT IN 在某些狀況下能夠:
explain select * from user where id not in (1) explain select * from user where id <> 1
只要列中包含有NULL值都將不會被包含在索引中,複合索引中只要有一列含有NULL值,那麼這一列對於此複合索引就是無效的。因此咱們在數據庫設計時不要讓字段的默認值爲NULL。
MySQL查詢只使用一個索引,所以若是where子句中已經使用了索引的話,那麼order by中的列是不會使用索引的。所以數據庫默認排序能夠符合要求的狀況下不要使用排序操做;儘可能不要包含多個列的排序,若是須要最好給這些列建立複合索引。
注意一個 SQL 語句是否使用索引,跟數據庫版本、數據量、數據選擇度都有關係。
其實,用不用索引,最終都是優化器說了算。
優化器是基於什麼的優化器?
基於 cost 開銷(Cost Base Optimizer),它不是基於規則(Rule-Based Optimizer),也不是基於語義。怎麼樣開銷小就怎麼來。
以上是我對索引相關知識的整理,但願你能有所收穫,參考以下!
參考:
【1】:《高性能MySQL》
【2】:MySQL索引原理及慢查詢優化
【3】:極客時間 《MySQL45講》
【6】:Mysql 四種常見的索引