該文爲《 MySQL 實戰 45 講》的學習筆記,感謝查看,若有錯誤,歡迎指正
mysql
索引就相似書本的目錄,做用就是方便咱們更加快速的查找到想要的數據。sql
索引的實現方式比較多,常見的有哈希表
,有序數組
,搜索樹
。數據庫
哈希表
是將數據以key-value
的形式存儲起來,簡單來講就是將key
經過哈希函數換算成數組中的一個肯定的位置,將value
存到這個位置去。當key
比較多時,有可能換算出相同的位置,此時能夠經過鏈表來解決。在查詢時先找到位置,再對該位置的多個value
進行遍歷。數組
哈希表適合用於等值查詢,因爲是無序的,不適合用來作區間查詢。
微信
有序數組
在等值查詢和區間查詢上效率都很高。因爲是有序的,能夠經過二分法快速獲得結果。也支持範圍查詢。可是也有一個缺點,若是要在中間插入一個數據,那麼後面的全部記錄都要向後挪一位,成本過高了。函數
所以,有序數組只適用於靜態存儲引擎。 例如咱們要保存2019年的出生人口信息,就適合用有序數組。
性能
常見的搜索樹有二叉
,也有多叉
。學習
二叉樹
的特色是:日誌
多叉樹
的特色是:code
因爲索引不止存在內存中,還會寫到磁盤上,而讀磁盤越多,查詢效率越慢。要下降讀磁盤的次數的話,就要儘可能訪問儘可能少的數據塊。
假設數據塊大小是N
,樹高爲M
,最多能夠存的數據行數爲 N^(M-1)
(N
的 M-1
次方)。最多訪問磁盤數爲 M-1
。
要使樹高比較小,訪問次數就少,N叉樹
的樹高就小於二叉樹
。以 InnoDB 的一個整數字段索引爲例,這個 N 差很少是 1200,這棵樹高是 4 的時候,就能夠存 1200 的 3 次方個值,這已經 17 億行記錄了。一個 10 億行的表上一個整數字段的索引,查找一個值最多隻須要訪問 3 次磁盤。
數據庫底層存儲的核心就是基於這些數據模型的。每碰到一個新數據庫,咱們須要先關注它的數據模型,這樣才能從理論上分析出這個數據庫的適用場景。
索引組織表
。所以,每個索引在 InnoDB 裏面對應一棵 B+ 樹。
根據字段約束,分爲主鍵索引
和普通索引
;根據字段內容是否可重複,分爲惟一索引
和非惟一索引
。
主鍵索引
主鍵是一種約束,一個表中只能有一個主鍵;
主鍵能夠是多個列;
主鍵能夠被其它表引用爲外鍵使用;
主鍵索引能夠理解爲非空字段
+惟一索引
;
主鍵索引的葉子節點存的是整行數據。
普通索引(二級索引)
一個表中能夠有多個普通索引;索引能夠有多列;
普通索引的葉子節點內容是主鍵的值;
惟一索引
字段內容不能重複,可是能夠爲空;
一個表中能夠有多個惟一索引;
不能作外鍵使用;
非惟一索引
字段內容容許重複;
下面以表爲例,建表語句:
mysql> create table T( id int primary key, k int not null, name varchar(16), index (k))engine=InnoDB;
表中 R1~R5 的 (ID,k) 值分別爲 (100,1)、(200,2)、(300,3)、(500,5) 和 (600,6),兩棵樹的示例示意圖以下:
id
字段爲主鍵索引
,主鍵索引
的字段是不會重複的,一定是惟一索引
;
k
字段爲普通索引
,k
的值容許重複,所以是非惟一索引
。
分析下面 2 條 SQL 語句:
select * from T where ID=500
。此時用到的是主鍵索引,所以直接從索引中返回了整行記錄,只須要搜索ID
這棵 B+ 樹。select * from T where k=5
。此時用到的是普通索引,須要先搜索 k
索引樹,獲得ID = 500
,再根據500
到ID
索引樹搜索一次。這種須要返回主鍵索引樹搜索的過程,叫作回表。以上兩條 SQL 語句返回的結果是同樣的,可是效率卻不同,由於第 2 條 SQL 語句有一次回表操做,效率會慢不少,所以,要儘可能避免回表操做,多使用主鍵查詢。
仍是以上表爲例,若是咱們要插入一個數據,ID 值爲 700,則只須要在 R5 後面新增長 1 條記錄便可。若是插入的值 ID 爲 400,那就須要邏輯上挪動後面的數據,空出位置。
若是剛好 R5 所在的數據頁已經滿了,那麼就須要申請一個新的數據頁,而且將 R5 挪過去,這個狀況就叫作頁分裂
。
數據頁中並非要利用率達到 100% 纔會申請新的數據頁。也不是說只要有數據刪除,那麼後一頁的數據就會順補到前一頁,這樣太浪費性能了。數據頁有一個利用率,假設分裂是80%,合併是 50%。只要利用率達到了 80%,就會申請一個新的數據頁。若是刪除數據比較多,利用率低於 50% 了,就會把後一頁的數據合併過來。
如何避免頁分裂形成的性能消耗?常見作法是在表中,設置一個自增加的 id 主鍵,這個字段不能和業務相關。自增主鍵的定義:NOT NULL PRIMARY KEY AUTO_INCREMENT
。
這樣每次插入數據,若是不指定 id 值,就會自增加到最後,由於和業務無關,因此不必去指定 id 值。這樣能夠避免出現頁分裂。
仍是以上表爲例,執行如下 SQL 語句,分析執行過程:
mysql> select * from T where k between 3 and 5;
k
上遍歷,獲得k=3
對應的 ID
值 300
;ID=300
去主鍵索引上取得整行記錄R3
;k
,獲得k=5
對應的 ID
值 500
;ID=500
去主鍵索引上取得整行記錄R5
;k
,發現k=6
,不知足between
條件,循環結束。能夠看到,這個過程讀了k
索引樹的 3
條記錄(步驟 1,3,5), 回表了2
次(步驟2,4)。
若是咱們換成如下 SQL 語句:
mysql> select ID from T where k between 3 and 5;
因爲 ID
已經在k
索引樹上了,所以能夠直接返回結果,不用回表。這種索引中已經覆蓋了咱們要查詢的數據,叫作覆蓋索引
。
覆蓋索引
能夠減小樹的搜索次數(沒有回表過程),顯著提升查詢性能。
MySQL 認爲上述操做掃描的行數是 2 行,由於在索引中查數據,是在引擎層的操做。而 Server 層最後只拿到了 2 條記錄,所以 MySQL 認爲只掃描了 2 行。
那麼如何看掃描函數呢?有 2 種方法:
explain
查看預計掃描行數mysql> explain select * from t where a between 1000 and 2000; +----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------+ | 1 | SIMPLE | t | range | a | a | 5 | NULL | 1000 | Using index condition | +----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------+ 1 row in set (0.01 sec) mysql>
能夠看到使用了索引 key=a
,預計掃描行數rows=1000
。
# Time: 191228 13:03:16 # User@Host: federated[federated] @ [60.191.76.22] Id: 177 # Query_time: 31.211439 Lock_time: 0.000059 Rows_sent: 0 Rows_examined: 95324 SET timestamp=1577509396; CALL Z10004();
能夠看到,掃描行數爲Rows_examined: 95324
舉一個例子來理解最左前綴原則,假設有一個聯合索引(name,age)以下:
能夠看到,索引順序先按照第一個字段排序,再按照第二個字段。
假設咱們要查詢全部名爲張三
的數據。能夠快速定位到ID4
,再依次向後遍歷。若是要查詢全部姓張
的(where name like '張%')
,也能用到索引,先定位到ID3
,再依次向後遍歷,直到不知足條件爲止。
不僅是索引的所有定義,只要知足最左前綴,就能夠利用索引來加速檢索。這個最左前綴能夠是聯合索引的最左 N 個字段,也能夠是字符串索引的最左 M 個字符。
在創建聯合索引時,如何肯定字段的先後順序呢?
第一原則,若是經過調整順序,能夠少維護一個索引,那麼這個順序每每就是須要優先考慮採用的。
好比,已經有了一個(a, b)索引,就沒必要再創建一個 a 索引了。
考慮磁盤空間佔用大小。
好比,(name, age) 索引加上 age 索引,和 (age, name) 索引加上 name 索引。這兩種狀況,咱們就要考慮佔用空間了。選擇佔用空間小的。
因爲name 字段比 age 字段大,所以咱們選擇(name, age) 索引加上 age 索引。
索引下推功能是在 MySQL 5.6 引入的,目的是減小回表次數。
仍是以市民表的聯合索引(name, age)爲例。若是如今有一個需求:檢索出表中「名字第一個字是張,並且年齡是 10 歲的全部男孩」。那麼,SQL 語句是這麼寫的:
mysql> select * from tuser where name like '張%' and age=10 and ismale=1;
ID3
,而後回表到主鍵索引,找出對應的數據行,判斷是否符合and age=10 and ismale=1
。最終要回表 4 次(ID3,ID4,ID5,ID6),返回的結果只有 ID4,ID5。感謝閱讀,有興趣的小夥伴能夠關注個人微信公衆號DevOps探索之旅
,你們一塊兒學習進步