索引 與 索引數據結構


索引

初始索引

  • 為什么要有索引

"""
一般的應用系統,讀寫比例在10:1左右,而且插入操作和一般的更新操作很少出現性能問題,在生產環境中,我們遇到最多的,
也是最容易出問題的,還是一些復雜的查詢操作,因此對查詢語句的優化顯然是重中之重。說起加速查詢,就不得不提到索引了。
"""
  • 什么是索引

"""
索引在MySQL中也叫是一種“鍵”,是存儲引擎用於快速找到記錄的一種數據結構,索引對於良好的性能
非常關鍵,尤其是當表中的數據量越來越大時,索引對於性能的影響愈發重要,
索引優化應該是對查詢性能優化最有效的手段了,索引能夠輕易將查詢性能提高好幾個數量級,
索引相當於字典的音序表,如果要查某個字,如果不使用音序表,則需要從幾百頁中逐頁去查
"""
  • 對索引存在的誤解

"""
索引是應用程序設計和開發的一個重要方面。若索引太多,應用程序的性能可能會受到影響。而索引太少,
對查詢性能又會產生影響,要找到一個平衡點,這對應用程序的性能至關重要。一些開發人員總是在事后才想起添加索引----我一直認為,
這源於一種錯誤的開發模式。如果知道數據的使用,從一開始就應該在需要處添加索引。開發人員往往對數據庫的使用停留在應用的層面,
比如編寫SQL語句、存儲過程之類,他們甚至可能不知道索引的存在,或認為事后讓相關DBA加上即可。DBA往往不夠了解業務的數據流,
而添加索引需要通過監控大量的SQL語句進而從中找到問題,這個步驟所需的時間肯定是遠大於初始添加索引所需的時間,
並且可能會遺漏一部分的索引。當然索引也並不是越多越好,我曾經遇到過這樣一個問題:某台MySQL服務器iostat顯示磁盤使用率一直處於100%,
經過分析后發現是由於開發人員添加了太多的索引,在刪除一些不必要的索引之后,磁盤使用率馬上下降為20%。可見索引的添加也是非常有技術含量的。
"""

索引的原理

"""
索引的目的在於提高查詢效率,與我們查閱圖書所用的目錄是一個道理:先定位到章,然后定位到該章下的一個小節,然后找到頁數。

本質都是:通過不斷地縮小想要獲取數據的范圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,
有了這種索引機制,我們可以總是用同一種查找方式來鎖定數據。

數據庫也是一樣,但顯然要復雜的多,因為不僅面臨着等值查詢,還有范圍查詢(>、<、between、in)、模糊查詢(like)、並集查詢(or)等等。
數據庫應該選擇怎么樣的方式來應對所有的問題呢?我們回想字典的例子,能不能把數據分成段,然后分段查詢呢?最簡單的如果1000條數據,
1到100分成第一段,101到200分成第二段,201到300分成第三段......這樣查第250條數據,只要找第三段就可以了,一下子去除了90%的無效數據。
但如果是1千萬的記錄呢,分成幾段比較好?稍有算法基礎的同學會想到搜索樹,其平均復雜度是lgN,具有不錯的查詢性能。
但這里我們忽略了一個關鍵的問題,復雜度模型是基於每次相同的操作成本來考慮的。而數據庫實現比較復雜,一方面數據是保存在磁盤上的,
另外一方面為了提高性能,每次又可以把部分數據讀入內存來計算,因為我們知道訪問磁盤的成本大概是訪問內存的十萬倍左右,
所以簡單的搜索樹難以滿足復雜的應用場景
"""

磁盤IO與預讀

"""
前面提到了訪問磁盤,那么這里先簡單介紹一下磁盤IO和預讀,磁盤讀取數據靠的是機械運動,每次讀取數據花費的時間可以分為尋道時間、
旋轉延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁道所需要的時間,主流磁盤一般在5ms以下;旋轉延遲就是我們經常聽說的磁盤轉速,
比如一個磁盤7200轉,表示每分鍾能轉7200次,也就是說1秒鍾能轉120次,旋轉延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,
一般在零點幾毫秒,相對於前兩個時間可以忽略不計。那么訪問一次磁盤的時間,即一次磁盤IO的時間約等於5+4.17 = 9ms左右,聽起來還挺不錯的,
但要知道一台500 -MIPS(Million Instructions Per Second)的機器每秒可以執行5億條指令,因為指令依靠的是電的性質,換句話說執行一次IO的時間可以執行約450萬條指令,
數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難。

考慮到磁盤IO是非常高昂的操作,計算機操作系統做了一些優化,當一次IO時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩沖區內,
因為局部預讀性原理告訴我們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。每一次IO讀取的數據我們稱之為一頁(page)。
具體一頁有多大數據跟操作系統有關,一般為4k或8k,也就是我們讀取一頁內的數據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計非常有幫助。
"""

索引的數據結構 樹

"""
樹狀圖是一種[數據結構],它是由n(n>=1)個有限結點組成一個具有層次關系的[集合],把它叫做“樹”是因為它看起來像一棵倒掛的樹,
也就是說它是根朝上,而葉朝下的

樹(Tree)是由多個節點(Node)的集合組成,每個節點又有多個與其關聯的子節點(Child Node)。子節點就是直接處於節點之下的節點,
而父節點(Parent Node)則位於節點直接關聯的上方。樹的根(Root)指的是一個沒有父節點的單獨的節點。

所有的樹都呈現了一些共有的性質:
只有一個根節點;
除了根節點,所有節點都有且只有一個父節點;
無環。將任意一個節點作為起始節點,都不存在任何回到該起始節點的路徑。(正是前兩個性質保證了無環的成立。)
"""
#下圖
根結點 : A   

父節點 : A是B,C的父節點

葉子節點:D,E是葉子節點

樹的深度/樹的高度:高度為3

類別 樹名稱
二叉查找樹(Binary Search Tree) 二叉查找樹,笛卡爾樹,T 樹
自平衡二叉查找樹(Self-balancing Binary Search Tree) AA 樹,AVL 樹, 紅黑樹(Red-Black Tree), 伸展樹(Splay Tree)
B 樹(B-Tree) 2-3 樹,2-3-4 樹, B 樹,B+ 樹,B* 樹
字典樹(Trie-Tree) 后綴樹,基數樹,三叉查找樹,快速前綴樹
空間數據分割樹(Spatial Data Partitioning Tree) R 樹,R+ 樹,R* 樹, 線段樹,優先 R 樹
  • 樹中的術語

根(Root):樹中最頂端的節點,根沒有父節點。
子節點(Child):節點所擁有子樹的根節點稱為該節點的子節點。
父節點(Parent):如果節點擁有子節點,則該節點為子節點的父節點。
兄弟節點(Sibling):與節點擁有相同父節點的節點。
子孫節點(Descendant):節點向下路徑上可達的節點。
葉節點(Leaf):沒有子節點的節點。
內節點(Internal Node):至少有一個子節點的節點。
度(Degree):節點擁有子樹的數量。
邊(Edge):兩個節點中間的鏈接。
路徑(Path):從節點到子孫節點過程中的邊和節點所組成的序列。
層級(Level):根為 Level 0 層,根的子節點為 Level 1 層,以此類推。
高度(Height)/深度(Depth):樹中層的數量。比如只有 Level 0,Level 1,Level 2 則高度為 3。

二叉樹

二叉樹是一種很經典的數據結構,二叉樹是每個結點最多有兩個子樹的樹結構。通常子樹被稱作“左子樹”(left subtree)和“右子樹”(right subtree)。
二叉樹常被用於實現二叉查找樹和二叉堆。二叉樹有如下特性:
1、每個結點都包含一個元素以及n個子樹,這里0≤n≤2。
2、左子樹和右子樹是有順序的,次序不能任意顛倒。左子樹的值要小於父結點,右子樹的值要大於父結點。
  • 光看概念有點枯燥,假設我們現在有這樣一組數[35 27 48 12 29 38 55],順序的插入到一個數的結構中,步驟如下:

這就是一棵二叉樹啦!我們能看到,經通過一系列的插入操作之后,原本無序的一組數已經變成一個有序的結構了,並且這個樹滿足了上面提到的兩個二叉樹的特性!

# 但是如果同樣是上面那一組數,我們自己升序排列后再插入,也就是說按照[12 27 29 35 38 48 55]的順序插入,會怎么樣呢?

平衡二叉樹

平衡二叉樹是一種特殊的二叉樹,所以他也滿足前面說到的二叉樹的兩個特性,同時還有一個特性:
它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。

前面[35 27 48 12 29 38 55]插入完成后的圖,其實就已經是一顆平衡二叉樹啦。

那如果按照[12 27 29 35 38 48 55]的順序插入一顆平衡二叉樹,會怎么樣呢?我們看看插入以及平衡的過程:

"""
這棵樹始終滿足平衡二叉樹的幾個特性而保持平衡!這樣我們的樹也不會退化為線性鏈表了!
我們需要查找一個數的時候就能沿着樹根一直往下找,這樣的查找效率和二分法查找是一樣的呢!

一顆平衡二叉樹能容納多少的結點呢?這跟樹的高度是有關系的,假設樹的高度為h,那每一層最多容納的結點數量為2^(n-1),
整棵樹最多容納節點數為2^0+2^1+2^2+...+2^(h-1)。這樣計算,100w數據樹的高度大概在20左右,那也就是說從有着100w條數據的平衡二叉樹中找一個數據,
最壞的情況下需要20次查找。如果是內存操作,效率也是很高的!但是我們數據庫中的數據基本都是放在磁盤中的,每讀取一個二叉樹的結點就是一次磁盤IO,
這樣我們找一條數據如果要經過20次磁盤的IO?那性能就成了一個很大的問題了!那我們是不是可以把這棵樹壓縮一下,讓每一層能夠容納更多的節點呢?雖然我矮,但是我胖啊...
"""

B樹

這顆矮胖的樹就是B-Tree,注意中間是杠精的杠而不是減,所以也不要讀成B減Tree了~
那B-Tree有哪些特性呢?一棵m階的B-Tree有如下特性:

1、每個結點最多m個子結點。
2、除了根結點和葉子結點外,每個結點最少有m/2(向上取整)個子結點。
3、如果根結點不是葉子結點,那根結點至少包含兩個子結點。
4、所有的葉子結點都位於同一層。
5、每個結點都包含k個元素(關鍵字),這里m/2≤k<m,這里m/2向下取整。
6、每個節點中的元素(關鍵字)從小到大排列。
7、每個元素(關鍵字)字左結點的值,都小於或等於該元素(關鍵字)。右結點的值都大於或等於該元素(關鍵字)。

下面我們以一個[0,1,2,3,4,5,6,7]的數組插入一顆3階的B-Tree為例,將所有的條件都串起來,就明白了.

那么,你是否對B-Tree的幾點特性都清晰了呢?在二叉樹中,每個結點只有一個元素。但是在B-Tree中,
每個結點都可能包含多個元素,並且非葉子結點在元素的左右都有指向子結點的指針。

如果需要查找一個元素,那流程是怎么樣的呢?我們看下圖,如果我們要在下面的B-Tree中找到關鍵字24,那流程如下:

從這個流程我們能看出,B-Tree的查詢效率好像也並不比平衡二叉樹高。但是查詢所經過的結點數量要少很多,
也就意味着要少很多次的磁盤IO,這對性能的提升是很大的。

前面對B-Tree操作的圖我們能看出來,元素就是類似1、2、3這樣的數值,但是數據庫的數據都是一條條的數據,
如果某個數據庫以B-Tree的數據結構存儲數據,那數據怎么存放的呢?
#我們看下一張圖

普通的B-Tree的結點中,元素就是一個個的數字。但是上圖中,我們把元素部分拆分成了key-data的形式,key就是數據的主鍵,
data就是具體的數據。這樣我們在找一條數的時候,就沿着根結點往下找就ok了,效率是比較高的。

B+樹

B+Tree是在B-Tree基礎上的一種優化,使其更適合實現外存儲索引結構。B+Tree與B-Tree的結構很像,但是也有幾個自己的特性:

1、所有的非葉子節點只存儲關鍵字信息。
2、所有衛星數據(具體數據)都存在葉子結點中。
3、所有的葉子結點中包含了全部元素的信息。
4、所有葉子節點之間都有一個鏈指針。

如果上面B-Tree的圖變成B+Tree,那應該如下:

#大家仔細對比於B-Tree的圖能發現什么不同?
  1、非葉子結點上已經只有key信息了,滿足上面第1點特性!
  2、所有葉子結點下面都有一個data區域,滿足上面第2點特性!
  3、非葉子結點的數據在葉子結點上都能找到,如根結點的元素4、8在最底層的葉子結點上也能找到,滿足上面第3點特性!
  4、注意圖中葉子結點之間的箭頭,滿足滿足上面第4點特性!

"""
我們現在總結一下,我們需要這種數據結構能夠做些什么,其實很簡單,那就是:每次查找數據時把磁盤IO次數控制在一個很小的數量級,
最好是常數數量級。那么我們就想到如果一個高度可控的多路搜索樹是否能滿足需求呢?就這樣,b+樹應運而生(B+樹是通過二叉查找樹,再由平衡二叉樹,B樹演化而來)。
"""
  • b+樹性質

1.索引字段要盡量的小:通過上面的分析,我們知道IO次數取決於b+數的高度h,假設當前數據表的數據為N,
每個磁盤塊的數據項的數量是m,則有h=㏒(m+1)N,當數據量N一定的情況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,
磁盤塊的大小也就是一個數據頁的大小,是固定的,如果數據項占的空間越小,數據項的數量越多,樹的高度越低。這就是為什么每個數據項,
即索引字段要盡量的小,比如int占4字節,要比bigint8字節少一半。這也是為什么b+樹要求把真實的數據放到葉子節點而不是內層節點,
一旦放到內層節點,磁盤塊的數據項會大幅度下降,導致樹增高。當數據項等於1時將會退化成線性表。

2.索引的最左匹配特性:當b+樹的數據項是復合的數據結構,比如(name,age,sex)的時候,b+數是按照從左到右的順序來建立搜索樹的,
比如當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,
最后得到檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪個節點,因為建立搜索樹的時候name就是第一個比較因子,
必須要先根據name來搜索才能知道下一步去哪里查詢。比如當(張三,F)這樣的數據來檢索時,b+樹可以用name來指定搜索方向,但下一個字段age的缺失,
所以只能把名字等於張三的數據都找到,然后再匹配性別是F的數據了, 這個是非常重要的性質,即索引的最左匹配特性。

聚集索引與輔助索引

在數據庫中,B+樹的高度一般都在2~4層,這也就是說查找某一個鍵值的行記錄時最多只需要2到4次IO,這倒不錯。
因為當前一般的機械硬盤每秒至少可以做100次IO,2~4次的IO意味着查詢時間只需要0.02~0.04秒。

數據庫中的B+樹索引可以分為聚集索引(clustered index)和輔助索引(secondary index),

聚集索引與輔助索引相同的是:不管是聚集索引還是輔助索引,其內部都是B+樹的形式,即高度是平衡的,葉子結點存放着所有的數據。

聚集索引與輔助索引不同的是:葉子結點存放的是否是一整行的信息
  • 聚集索引

#InnoDB存儲引擎表是索引組織表,即表中數據按照主鍵順序存放。
而聚集索引(clustered index)就是按照每張表的主鍵構造一棵B+樹,同時葉子結點存放的即為整張表的行記錄數據,也將聚集索引的葉子結點稱為數據頁。
聚集索引的這個特性決定了索引組織表中數據也是索引的一部分。同B+樹數據結構一樣,每個數據頁都通過一個雙向鏈表來進行鏈接。
    
#如果未定義主鍵,MySQL取第一個唯一索引(unique)而且只含非空列(NOT NULL)作為主鍵,InnoDB使用它作為聚簇索引。
    
#如果沒有這樣的列,InnoDB就自己產生一個這樣的ID值,它有六個字節,而且是隱藏的,使其作為聚簇索引。

#由於實際的數據頁只能按照一棵B+樹進行排序,因此每張表只能擁有一個聚集索引。
在多數情況下,查詢優化器傾向於采用聚集索引。因為聚集索引能夠在B+樹索引的葉子節點上直接找到數據。
此外由於定義了數據的邏輯順序,聚集索引能夠特別快地訪問針對范圍值得查詢。
  • 聚集索引的好處

聚集索引的好處之一:它對主鍵的排序查找和范圍查找速度非常快,葉子節點的數據就是用戶所要查詢的數據。如用戶需要查找一張表,
查詢最后的10位用戶信息,由於B+樹索引是雙向鏈表,所以用戶可以快速找到最后一個數據頁,並取出10條記錄

#參照第六小結測試索引的准備階段來創建出表s1
mysql> desc s1; #最開始沒有主鍵
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(6)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
rows in set (0.00 sec)

mysql> explain select * from s1 order by id desc limit 10; #Using filesort,需要二次排序
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra          |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
|  1 | SIMPLE      | s1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 2633472 |   100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
row in set, 1 warning (0.11 sec)

mysql> alter table s1 add primary key(id); #添加主鍵
Query OK, 0 rows affected (13.37 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from s1 order by id desc limit 10; #基於主鍵的聚集索引在創建完畢后就已經完成了排序,無需二次排序
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
|  1 | SIMPLE      | s1    | NULL       | index | NULL          | PRIMARY | 4       | NULL |   10 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
row in set, 1 warning (0.04 sec)
聚集索引的好處之二:范圍查詢(range query),即如果要查找主鍵某一范圍內的數據,通過葉子節點的上層中間節點就可以得到頁的范圍,之后直接讀取數據頁即可
mysql> alter table s1 drop primary key;
Query OK, 2699998 rows affected (24.23 sec)
Records: 2699998  Duplicates: 0  Warnings: 0

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

mysql> explain select * from s1 where id > 1 and id < 1000000; #沒有聚集索引,預估需要檢索的rows數如下
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | s1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 2690100 |    11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
row in set, 1 warning (0.00 sec)

mysql> alter table s1 add primary key(id);
Query OK, 0 rows affected (16.25 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from s1 where id > 1 and id < 1000000; #有聚集索引,預估需要檢索的rows數如下
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | s1    | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL | 1343355 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
row in set, 1 warning (0.09 sec)
  • 輔助索引

表中除了聚集索引外其他索引都是輔助索引(Secondary Index,也稱為非聚集索引),與聚集索引的區別是:輔助索引的葉子節點不包含行記錄的全部數據。

葉子節點除了包含鍵值以外,每個葉子節點中的索引行中還包含一個書簽(bookmark)。該書簽用來告訴InnoDB存儲引擎去哪里可以找到與索引相對應的行數據。
由於InnoDB存儲引擎是索引組織表,因此InnoDB存儲引擎的輔助索引的書簽就是相應行數據的聚集索引鍵。

輔助索引的存在並不影響數據在聚集索引中的組織,因此每張表上可以有多個輔助索引,但只能有一個聚集索引。當通過輔助索引來尋找數據時,
InnoDB存儲引擎會遍歷輔助索引並通過葉子級別的指針獲得只想主鍵索引的主鍵,然后再通過主鍵索引來找到一個完整的行記錄。

舉例來說,如果在一棵高度為3的輔助索引樹種查找數據,那需要對這個輔助索引樹遍歷3次找到指定主鍵,如果聚集索引樹的高度同樣為3,
那么還需要對聚集索引樹進行3次查找,最終找到一個完整的行數據所在的頁,因此一共需要6次邏輯IO訪問才能得到最終的一個數據頁。

聚集索引
1.紀錄的索引順序與無力順序相同
   因此更適合between and和order by操作
2.葉子結點直接對應數據
 從中間級的索引頁的索引行直接對應數據頁
3.每張表只能創建一個聚集索引

非聚集索引
1.索引順序和物理順序無關
2.葉子結點不直接指向數據頁
3.每張表可以有多個非聚集索引,需要更多磁盤和內容
   多個索引會影響insert和update的速度

mysql 常用的索引

  • 索引功能

#1. 索引的功能就是加速查找
#2. mysql中的primary key,unique,聯合唯一也都是索引,這些索引除了加速查找以外,還有約束的功能  
  • mysql 常用的索引

普通索引INDEX:加速查找

唯一索引:
    -主鍵索引PRIMARY KEY:加速查找+約束(不為空、不能重復)
    -唯一索引UNIQUE:加速查找+約束(不能重復)

聯合索引:
    -PRIMARY KEY(id,name):聯合主鍵索引
    -UNIQUE(id,name):聯合唯一索引
    -INDEX(id,name):聯合普通索引
        
舉個例子來說,比如你在為某商場做一個會員卡的系統。

這個系統有一個會員表
有下列字段:
會員編號 INT
會員姓名 VARCHAR(10)
會員身份證號碼 VARCHAR(18)
會員電話 VARCHAR(10)
會員住址 VARCHAR(50)
會員備注信息 TEXT

那么這個 會員編號,作為主鍵,使用 PRIMARY
會員姓名 如果要建索引的話,那么就是普通的 INDEX
會員身份證號碼 如果要建索引的話,那么可以選擇 UNIQUE (唯一的,不允許重復)

#除此之外還有全文索引,即FULLTEXT
會員備注信息 , 如果需要建索引的話,可以選擇全文搜索。
用於搜索很長一篇文章的時候,效果最好。
用在比較短的文本,如果就一兩行字的,普通的 INDEX 也可以。
但其實對於全文搜索,我們並不會使用MySQL自帶的該索引,而是會選擇第三方軟件如Sphinx,專門來做全文搜索。

#其他的如空間索引SPATIAL,了解即可,幾乎不用
  • 索引的兩大類型hash與btree

#我們可以在創建上述索引的時候,為其指定索引類型,分兩類
hash類型的索引:查詢單條快,范圍查詢慢
btree類型的索引:b+樹,層數越多,數據量指數級增長(我們就用它,因為innodb默認支持它)

#不同的存儲引擎支持的索引類型也不一樣
InnoDB 支持事務,支持行級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
MyISAM 不支持事務,支持表級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
Memory 不支持事務,支持表級別鎖定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;
NDB 支持事務,支持行級別鎖定,支持 Hash 索引,不支持 B-tree、Full-text 等索引;
Archive 不支持事務,支持表級別鎖定,不支持 B-tree、Hash、Full-text 等索引;
  • 創建/刪除索引的語法

#方法一:創建表時
      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();
  • 在沒有索引的前提下測試查詢速度

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

  • 總結

#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個字節,類型為長整型.

正確的使用索引

  • 索引未命中

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

1 范圍問題,或者說條件不明確,條件中出現這些符號或關鍵字:>、>=、<、<=、!= 、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',查詢時,我們永遠無法從樹的某個位置得到一個明確的范圍,只能往下找,往下找,往下找。。。
這與全表掃描的IO次數沒有多大區別,所以速度很慢
索引列不能在條件中參與計算,保持列“干凈”,比如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)
- 連表時注意條件類型需一致
- 索引散列值(重復少)不適合建索引,例:性別不適合

滿查詢優化的基本步驟

0.先運行看看是否真的很慢,注意設置SQL_NO_CACHE
1.where條件單表查,鎖定最小返回記錄表。這句話的意思是把查詢語句的where都應用到表中返回的記錄數最小的表開始查起,
單表每個字段分別查詢,看哪個字段的區分度最高
2.explain查看執行計划,是否與1預期一致(從鎖定記錄較少的表開始查詢)
3.order by limit 形式的sql語句讓排序的表優先查
4.了解業務方使用場景
5.加索引時參照建索引的幾大原則
6.觀察結果,不符合預期繼續從0分析

參考文章(B樹,B+樹)

索引原理參考博客

作 者: 郭楷豐
聲援博主:如果您覺得文章對您有幫助,可以點擊文章右下角 推薦一下。您的鼓勵是博主的最大動力!
自 勉:生活,需要追求;夢想,需要堅持;生命,需要珍惜;但人生的路上,更需要堅強。 帶着感恩的心啟程,學會愛,愛父母,愛自己,愛朋友,愛他人。


免責聲明!

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



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