索引有不少種,hash索引,B樹索引,B+樹索引,全文索引等。Mysql支持多種存儲引擎,多種存儲引擎對索引的支持也各不相同。本文探究Mysql爲何使用B+樹來做爲索引的數據結構,索引的原理已經Sql中索引的優化。
Mysql官方對索引的定義是:索引(Index)是幫助Mysql高效獲取數據的數據結構。提取句子主幹就是:索引是數據結構。mysql
索引的目的在於提升查詢或檢索效率。例如咱們要在字典中查詢「mysql」這個單詞,是否是先要查詢m開頭的單詞表,而後在查詢第二個字母爲y的單詞,而後縮小範圍繼續找,知道找到「mysql」這個單詞爲止或者查無此詞。這就好像咱們沿着一個樹從樹根開始找,沿着主幹,樹幹,到最後的末梢,走了其中的一條路徑。這比一個查詢一個鏈表的結構,從頭找到尾,在大多數狀況下,效率要高得多。算法
爲何不用普通的二叉樹,這裏就沒必要多說了,由於對於大的數據量,二叉樹的高度過高,索引的效率低下。這裏主要說明爲何不用B樹(B-樹就是B樹),而是用B+樹。sql
咱們都知道二叉樹查詢的時間複雜度爲O(logN),查詢效率已經夠高了,但爲何還要有B樹和B+樹呢?答案是磁盤IO。咱們都知道,IO操做的效率很低,當有存儲的有很大的數據量,查詢的時候,咱們不可能把所有數據都加載到內存中,只能逐一加載磁盤頁,每一個磁盤頁對應樹的節點,形成大量的磁盤IO操做(最壞狀況下,磁盤IO操做次數是樹的高度),平衡二叉樹因爲樹的高度太大形成磁盤IO讀寫過於頻繁,從而致使效率低下,因此多路查找樹-B樹/B+樹應運而生。
下面是一個三階的B樹(實際中節點元素不少)數據庫
B+樹有如下特色:緩存
咱們知道Mysql有兩種經常使用的存儲引擎,MyISAM和InnoDB,這兩種存儲引擎對索引的實現方式是不一樣的。安全
MyISAM使用B+樹做爲索引的結構,葉子結點的data域存放的是數據記錄的地址。 bash
上圖中是以Col1做爲主鍵的MyISAM主索引的示意圖。能夠看到,組下面一層葉子結點的data域存放的是數據記錄的地址。若是咱們在字段Col2上建一個輔助索引,那麼索引的結構以下: MyISAM索引檢索算法是這樣的,首先按照B+樹的搜索算法查詢索引,若是指定的key存在,則取出data域的值,而後用data域的地址查詢數據記錄。MyISAM的索引方式也叫「非彙集的」,跟InnoDB的「彙集索引」相區分,由於數據記錄和索引不在一塊兒。InnoDB的索引實現方式與MyISAM的索引實現方式的區別有兩個:
第一,InnoDB的數據文件自己就是索引文件。在InnoDB中,數據文件自己就是按B+樹組織的一個索引結構,並且是主索引結構。數據和索引在一塊兒,葉子結點保存了完整的數據記錄,這種索引叫作彙集索引。由於InnoDB的數據文件自己要按主鍵彙集,因此InnoDB要求表必須有主鍵(MyISAM能夠沒有),若是沒有顯式指定,則MySQL系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段做爲主鍵,這個字段長度爲6個字節,類型爲長整形。服務器
在使用InnoDB做爲存儲引擎時,若是沒有特殊須要,請永遠是用一個與業務無關的自增字段做爲主鍵,並且這個字段長度不宜過大。爲何?InnoDB使用匯集索引,數據記錄自己存放在主索引(B+樹)的葉子結點上,這就要求同一個葉子結點(大小爲一個內存頁或磁盤頁)的數據記錄按主鍵順序存放,每當一條新的記錄插入時,mysql會根據其主鍵將其插入適當的節點和位置,若是頁面達到裝載因子(InnoDB默認爲15/16),則開闢一個新的頁(節點)。若是使用自增主鍵,那麼每次插入新的記錄,記錄就會順序插入到當前節點的下一個位置。這樣就會造成一個緊湊的索引結構,每次插入不須要移動已有數據,所以效率很高。以下圖:網絡
若是使用非自增主鍵(例如身份證號或學號這種無序字符串),每次插入主鍵近似隨機,每次記錄都要插入到現有索引頁的中間的某個位置,這時不得不移動元素來完成插入,增長了開銷。以下圖:數據結構
聯合索引:mysql能夠將多個列按照順序做爲一個索引,這種索引叫作聯合索引。
索引的最左匹配原則是:假如索引列分別爲A,B,C,順序也是A,B,C,那麼:
這個原則能夠結合索引的原理來理解:Mysql索引是B+樹這種複合結構,當索引是聯合索引,好比【name,age,sex】時,B+樹是按照從左到右的順序創建索引樹的。當(張三,20,M)這樣的數據來檢索時,B+樹會優先根據name來肯定下一步的搜索方向,若是name相同再比較name和sex,最後獲得檢索的數據。但當(20,M)這樣的數據來的時候,mysql就不知道該查哪一個節點,由於創建索引的時候,name就是第一個比較因子,必須先根據name去肯定下一步去哪裏搜索。當(張三,M)這樣的數據來時,能夠根據name是「張三」,來肯定下一步的搜索,而後再去匹配性別是「M」的數據,所以只能用到聯合索引中name這個索引。
一、儘可能選擇區分度高的列做爲索引,區分度公式:count(distinct col)/count(*),表示字段不重複的比例,比例越大,咱們掃描的記錄數就越少,惟一性的列的區分度爲1。這就是爲何不建議在狀態,性別這樣區分度很小的列上創建索引的緣由。
二、索引列在sql語句中不能參與運算,不然會致使索引失效。例如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,緣由很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,須要把全部元素都應用函數才能比較,顯然成本太大。應該改爲create_time = unix_timestamp(’2014-05-29’);
三、聯合索引比單個索引的性價比更高。例如,創建【A,B,C】這個聯合索引,至關於創建了【A】,【A,B】,【A,B,C】這三個索引。這就要求咱們儘可能的擴展索引而不是新建索引,具體狀況還需具體分析。
四、頻繁進行查詢的字段應該新建索引,與其餘表進行關聯的字段能夠考慮新建索引,查詢中排序的字段能夠考慮創建索引以提升排序的效率(這裏舉個例子,不少時候查詢記錄但願按照建立時間倒序返回,一般有人會這樣作order by create_time desc,可是若是create_time不是索引,而這個表有自增主鍵id,那麼order by id desc返回結果同樣,可是效率會提升)。
一、硬件問題:如網絡速度慢,內存不足,I/O吞吐量小,磁盤空間滿了等。
二、沒有使用索引或者索引失效。
三、數據過多(分庫分表)。
四、服務器或參數設置不當。
一、先觀察,開啓慢查詢日誌,設置相應的閾值(好比超過3秒就是慢sql),再生產環境跑個一天,看看哪些sql比較慢。
二、explain和慢sql分析,好比sql語句寫的很差,沒有使用索引或者索引失效,或者sql語句太過複雜,關聯查詢和嵌套子查詢太多等等。
三、Show Profile是比explain更近一步的執行細節,能夠查詢到執行每個SQL都幹了什麼事,這些事分別花了多少秒。
四、找DBA或者運維對Mysql進行服務器的參數調優。
CREATE TABLE `user_info` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL DEFAULT '',
`age` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name_index` (`name`)
)ENGINE = InnoDB DEFAULT CHARSET = utf8;
INSERT INTO user_info (name, age) VALUES ('xys', 20);
INSERT INTO user_info (name, age) VALUES ('a', 21);
INSERT INTO user_info (name, age) VALUES ('b', 23);
INSERT INTO user_info (name, age) VALUES ('c', 50);
INSERT INTO user_info (name, age) VALUES ('d', 15);
INSERT INTO user_info (name, age) VALUES ('e', 20);
INSERT INTO user_info (name, age) VALUES ('f', 21);
INSERT INTO user_info (name, age) VALUES ('g', 23);
INSERT INTO user_info (name, age) VALUES ('h', 50);
INSERT INTO user_info (name, age) VALUES ('i', 15);
CREATE TABLE `order_info` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`user_id` BIGINT(20) DEFAULT NULL,
`product_name` VARCHAR(50) NOT NULL DEFAULT '',
`productor` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_product_detail_index` (`user_id`, `product_name`, `productor`)
)ENGINE = InnoDB DEFAULT CHARSET = utf8;
INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p2', 'WL');
INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p1', 'DX');
INSERT INTO order_info (user_id, product_name, productor) VALUES (2, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (2, 'p5', 'WL');
INSERT INTO order_info (user_id, product_name, productor) VALUES (3, 'p3', 'MA');
INSERT INTO order_info (user_id, product_name, productor) VALUES (4, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (6, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (9, 'p8', 'TE');
複製代碼
執行explain看看,索引使用狀況在possible_keys、key和key_len這三列。
select_type總共有如下幾種類型:
一、SIMPLE:表示查詢不使用UNION或子查詢
二、PRIMARY:表示此查詢是最外層的查詢
三、SUBQUERY:表示此查詢是第一個查詢
四、UNION:表示此查詢是UNION第二或隨後的查詢 五、DEPENDENT UNION:UNION中的第二個或後面的查詢語句,取決於外面的查詢 六、UNION RESULT:UNION的結果 七、DEPENDENT SUBQUERY:子查詢中的第一個SELECT,取決於外面的查詢,即子查詢依賴於外面查詢的結果 八、DERIVED:衍生,表示導出表的SELECT
id=1的table derived2表示是由id=2的u和o衍生出來的
常見的索引原則: