mysql索引B+Tree以及優化

今天看到別人寫的一些關於mysql索引的文章,有一些小收穫,就以此開啓個人隨筆記錄簡單摘了一些重點html

轉載文章:http://www.cnblogs.com/tgycoder/p/5410057.htmlmysql

mysql索引實現原理面試

    1. MyISAM引擎使用B+Tree做爲索引結構,葉結點的data域存放的是數據記錄的地址,MyISAM的索引方式也叫作「非彙集」的,之因此這麼稱呼是爲了與InnoDB的彙集索引區分。sql

  

 2. InnoDB也使用B+Tree做爲索引結構,第一個重大區別是InnoDB的數據文件自己就是索引文件,第一個重大區別是InnoDB的數據文件自己就是索引文件。從上文知道,MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在InnoDB中,表數據文件自己就是按B+Tree組織的一個索引結構,這棵樹的葉結點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,所以InnoDB表數據文件自己就是主索引數據庫

  

InnoDB主索引(同時也是數據文件)的示意圖,能夠看到葉結點包含了完整的數據記錄。這種索引叫作彙集索引。由於InnoDB的數據文件自己要按主鍵彙集,因此InnoDB要求表必須有主鍵(MyISAM能夠沒有),若是沒有顯式指定,則MySQL系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段做爲主鍵,這個字段長度爲6個字節,類型爲長整形。函數

3. 最左前綴與相關優化mysql索引

    以前我理解的最左前綴覺得索引的順序是跟where條件查詢的一致不一致就使用不到索引,這是錯誤的優化

  Ps:最左前綴原則中where字句有or出現仍是會遍歷全表spa

   (1) 其實where條件的順序不影響使用索引,好比三個字段添加聯合索引t_user表聯合索引(name, mobile, create_date)code

     select * from t_user where mobile = '13256767876' and create_date= '2017-07-31' and name = 'corner';

      理論上索引對順序是敏感的,可是因爲MySQL的查詢優化器會自動調整where子句的條件順序以使用適合的索引,因此這樣也是能夠用到索引的

    (2)查詢條件沒有指定索引第一列

   若是where條件中沒有name條件,只有另外兩個不管順序是什麼都是沒法用到索引的,若是where條件只有name,status而沒有mobile這時候只能用到一列索引,status這一列的索引是用不到的

     (3)範圍查詢

             範圍列能夠用到索引(必須是最左前綴),可是範圍列後面的列沒法用到索引。同時,索引最多用於一個範圍列,所以若是查詢條件中有兩個範圍列則沒法全用到索引

             表t_title聯合索引(emp_no,title,from_date)

EXPLAIN SELECT * FROM employees.titles WHERE emp_no < '10010' AND title='Senior Engineer' AND from_date BETWEEN '1986-01-01' AND '1986-12-31';
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table  | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 4 | NULL | 16 | Using where | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  只能用到第一列索引,這裏特別要說明MySQL一個有意思的地方,那就是僅用explain可能沒法區分範圍索引和多值匹配,由於在type中這二者都顯示爲range。同時,用了「between」並不意味着就是範圍查詢,例以下面的查詢:
所有索引都用到了
EXPLAIN SELECT * FROM employees.titles WHERE emp_no BETWEEN '10001' AND '10010' AND title='Senior Engineer' AND from_date BETWEEN '1986-01-01' AND '1986-12-31';
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table  | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 59 | NULL | 16 | Using where | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
(4)查詢條件中含有函數或表達式
若是查詢條件中含有函數或表達式,則MySQL不會爲這列使用索引
like若是通配符%不出如今開頭,則能夠用到索引,但根據具體狀況不一樣可能只會用其中一個前綴
EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title LIKE 'Senior%'; +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 56 | NULL | 1 | Using where | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

4.索引選擇性與前綴索引
 (1)什麼狀況下判斷字段是否應該創建索引,今天剛看到這個"選擇性"的概念,除了表數據不多的狀況不用建索引由於索引文件自己要消耗存儲空間會加劇數據庫操做的負擔,另一種狀況就是索引的選擇性比較低:
所謂索引的選擇性(Selectivity),是指不重複的索引值(也叫基數,Cardinality)與表記錄數(#T)的比值:Index Selectivity = Cardinality / #T
顯然選擇性的取值範圍爲(0, 1],選擇性越高的索引價值越大,這是由B+Tree的性質決定的。
 

           這個問題就像是面試時提問個人一個問題:性別列適不適合創建索引?(答案是否認的)

 

           例如,上文用到的employees.titles表,若是title字段常常被單獨查詢,是否須要建索引,咱們看一下它的選擇性:

 
SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles; +-------------+ | Selectivity | +-------------+ | 0.0000 | +-------------+
 

title的選擇性不足0.0001(精確值爲0.00001579),因此實在沒有什麼必要爲其單獨建索引。

 

  (2)有一種與索引選擇性有關的索引優化策略叫作前綴索引,就是用列的前綴代替整個列做爲索引key,當前綴長度合適時,能夠作到既使得前綴索引的選擇性接近全列索引,同時由於索引key變短而減小了索引文件的大小和維護開銷。下面以employees.employees表爲例介紹前綴索引的選擇和使用。

從圖12能夠看到employees表只有一個索引<emp_no>,那麼若是咱們想按名字搜索一我的,就只能全表掃描了:

EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido'; +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+ | 1 | SIMPLE | employees | ALL | NULL | NULL | NULL | NULL | 300024 | Using where | +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+

若是頻繁按名字搜索員工,這樣顯然效率很低,所以咱們能夠考慮建索引。有兩種選擇,建<first_name>或<first_name, last_name>,看下兩個索引的選擇性:

SELECT count(DISTINCT(first_name))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.0042 | +-------------+ SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.9313 | +-------------+

<first_name>顯然選擇性過低,<first_name, last_name>選擇性很好,可是first_name和last_name加起來長度爲30,有沒有兼顧長度和選擇性的辦法?能夠考慮用first_name和last_name的前幾個字符創建索引,例如<first_name, left(last_name, 3)>,看看其選擇性:

SELECT count(DISTINCT(concat(first_name, left(last_name, 3))))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.7879 | +-------------+

選擇性還不錯,但離0.9313仍是有點距離,那麼把last_name前綴加到4:

SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.9007 | +-------------+

這時選擇性已經很理想了,而這個索引的長度只有18,比<first_name, last_name>短了接近一半,咱們把這個前綴索引 建上:

ALTER TABLE employees.employees ADD INDEX first_name_last_name4 (first_name, last_name(4));

此時再執行一遍按名字查詢,比較分析一下與建索引前的結果:

SHOW PROFILES;
+----------+------------+---------------------------------------------------------------------------------+ | Query_ID | Duration | Query | +----------+------------+---------------------------------------------------------------------------------+ | 87 | 0.11941700 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' | | 90 | 0.00092400 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' | +----------+------------+---------------------------------------------------------------------------------+
相關文章
相關標籤/搜索