一步一步帶你入門MySQL中的索引和鎖

索引

索引常見的幾種類型

索引常見的類型有哈希索引,有序數組索引,二叉樹索引,跳錶等等。本文主要探討 MySQL 的默認存儲引擎 InnoDB 的索引結構。sql

InnoDB的索引結構

在InnoDB中是經過一種多路搜索樹——B+樹實現索引結構的。在B+樹中是只有葉子結點會存儲數據,並且全部葉子結點會造成一個鏈表。而在InnoDB中維護的是一個雙向鏈表。數據庫

你可能會有一個疑問,爲何使用 B+樹 而不使用二叉樹或者B樹?數組

首先,咱們知道訪問磁盤須要訪問到指定塊中,而訪問指定塊是須要 盤片旋轉磁臂移動 的,這是一個比較耗時的過程,若是增長樹高那麼就意味着你須要進行更屢次的磁盤訪問,因此會採用n叉樹。而使用B+樹是由於若是使用B樹在進行一個範圍查找的時候每次都會進行從新檢索,而在B+樹中能夠充分利用葉子結點的鏈表併發

在建表的時候你可能會添加多個索引,而 InnDB 會爲每一個索引創建一個 B+樹 進行存儲索引函數

好比這個時候咱們創建了一個簡單的測試表post

create table test(
  id int primary key,
  a int not null,
  name varchar,
  index(a)
)engine = InnoDB;
複製代碼

這個時候 InnDB 就會爲咱們創建兩個 B+索引樹性能

一個是 主鍵聚簇索引,另外一個是 普通索引輔助索引,這裏我直接貼上 MySQL淺談(索引、鎖) 這篇文章上面的貼圖(由於我懶不想畫圖了。。。) 測試

能夠看到在輔助索引上面的葉子節點的值只是存了主鍵的值,而在主鍵的聚簇索引上的葉子節點纔是存上了整條記錄的值優化

回表

因此這裏就會引伸出一個概念叫回表,好比這個時候咱們進行一個查詢操做ui

select name from test where a = 30;
複製代碼

咱們知道由於條件 MySQL 是會走 a 的索引的,可是 a 索引上並無存儲 name 的值,此時咱們就須要拿到相應 a 上的主鍵值,而後經過這個主鍵值去走 聚簇索引 最終拿到其中的name值,這個過程就叫回表。

咱們來總結一下回表是什麼?MySQL在輔助索引上找到對應的主鍵值並經過主鍵值在聚簇索引上查找所要的數據就叫回表

索引維護

咱們知道索引是須要佔用空間的,索引雖能提高咱們的查詢速度可是也是不能濫用。

好比咱們在用戶表裏用身份證號作主鍵,那麼每一個二級索引的葉子節點佔用約20個字節,而若是用整型作主鍵,則只要4個字節,若是是長整型(bigint)則是8個字節。也就是說若是我用整型後面維護了4個g的索引列表,那麼用身份證將會是20個g。

因此咱們能夠經過縮減索引的大小來減小索引所佔空間

固然B+樹爲了維護索引的有序性會在刪除,插入的時候進行一些必要的維護(在InnoDB中刪除會將節點標記爲「可複用」以減小對結構的變更)。

好比在增長一個節點的時候可能會遇到數據頁滿了的狀況,這個時候就須要作頁的分裂,這是一個比較耗時的工做,並且頁的分裂還會致使數據頁的利用率變低,好比原來存放三個數據的數據頁再次添加一個數據的時候須要作頁分裂,這個時候就會將現有的四個數據分配到兩個數據頁中,這樣就減小了數據頁利用率。

覆蓋索引

上面提到了 回表,而有時候咱們查輔助索引的時候就已經知足了咱們須要查的數據,這個時候 InnoDB 就會進行一個叫 覆蓋索引 的操做來提高效率,減小回表。

好比這個時候咱們進行一個 select 操做

select id from test where a = 1;
複製代碼

這個時候很明顯咱們走了 a 的索引直接能獲取到 id 的值,這個時候就不須要進行回表,咱們這個時候就使用了 覆蓋索引

簡單來講 覆蓋索引 就是當咱們走輔助索引的時候能獲取到咱們所須要的數據的時候不須要再次進行回表操做的操做

聯合索引

這個時候咱們新建一個學生表

CREATE TABLE `stu` (
  `id` int(11) NOT NULL,
  `class` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `class_name` (`class`,`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 
複製代碼

咱們使用 class(班級號) 和 name 作一個 聯合索引,你可能會問這個聯合索引有什麼用呢?咱們能夠結合着上面的 覆蓋索引 去理解,好比這個時候咱們有一個需求,咱們須要經過班級號去找對應的學生姓名

select name from stu where class = 102;
複製代碼

這個時候咱們就能夠直接在 輔助索引 上查找到學生姓名而不須要再次回表。

總的來講,設計好索引,充分利用覆蓋索引能很大提高檢索速度

最左前綴原則

這個是以 聯合索引 做爲基礎的,是一種聯合索引的匹配規則。

這個時候,咱們將上面的需求稍微變更一下,這時咱們有個學生遲到,可是他在門衛記錄信息的時候只寫了本身的名字張三而沒有寫班級,因此咱們須要經過學生姓名去查找相應的班級號。

select class from stu where name = '張三';
複製代碼

這個時候咱們就不會走咱們的聯合索引了,而是進行了全表掃描

爲何?由於 最左匹配原則。咱們能夠畫一張簡單的圖來理解一下。

咱們能夠看到整個索引設計就是這麼設計的,因此咱們須要查找的時候也須要遵循着這個規則,若是咱們直接使用name,那麼InnoDB是不知道咱們須要幹什麼的。

固然最左匹配原則還有這些規則

  • 全值匹配的時候優化器會改變順序,也就是說你全值匹配時的順序和原先的聯合索引順序不一致沒有關係,優化器會幫你調好。
  • 索引匹配從最左邊的地方開始,若是沒有則會進行全表掃描,好比你設計了一個(a,b,c)的聯合索引,而後你可使用(a),(a,b),(a,b,c) 而你使用 (b),(b,c),(c)就用不到索引了。
  • 遇到範圍匹配會取消索引。好比這個時候你進行一個這樣的 select 操做
select * from stu where class > 100 and name = '張三';
複製代碼

這個時候 InnoDB 就會放棄索引而進行全表掃描,由於這個時候 InnoDB 會不知道怎麼進行遍歷索引,因此進行全表掃描。

索引下推

我給你挖了個坑。剛剛的操做在 MySQL5.6 版本之前是須要進行回表的,可是5.6以後的版本作了一個叫 索引下推 的優化。

select * from stu where class > 100 and name = '張三';
複製代碼

如何優化的呢?由於剛剛的最左匹配原則咱們放棄了索引,後面咱們緊接着會經過回表進行判斷 name,這個時候咱們所要作的操做應該是這樣的

可是有了索引下推以後就變成這樣了,此時 "李四" 和 "小明" 這兩個不會再進行回表。

由於這裏匹配了後面的name = 張三,也就是說,若是最左匹配原則由於範圍查詢終止了,InnoDB仍是會索引下推來優化性能。

一些最佳實踐

哪些狀況須要建立索引?

  • 頻繁做爲查詢條件的字段應建立索引。
  • 多表關聯查詢的時候,關聯字段應該建立索引。
  • 查詢中的排序字段,應該建立索引。
  • 統計或者分組字段須要建立索引。

哪些狀況不須要建立索引

  • 表記錄少。
  • 常常增刪改查的表。
  • 頻繁更新的字段。
  • where 條件使用不高的字段。
  • 字段很大的時候。

其餘

  • 儘可能選擇區分度高的列做爲索引。
  • 不要對索引進行一些函數操做,還應注意隱式的類型轉換和字符編碼轉換。
  • 儘量的擴展索引,不要新創建索引。好比表中已經有了a的索引,如今要加(a,b)的索引,那麼只須要修改原來的索引便可。
  • 多考慮覆蓋索引,索引下推,最左匹配。

全局鎖

MySQL提供了一個加全局讀鎖的方法,命令是 Flush tables with read lock (FTWRL)。當你須要讓整個庫處於只讀狀態的時候,可使用這個命令,以後其餘線程的如下語句會被阻塞:數據更新語句(數據的增刪改)、數據定義語句(包括建表、修改表結構等)和更新類事務的提交語句。

通常會在進行 全庫邏輯備份 的時候使用,這樣就能確保 其餘線程不能對該數據庫作更新操做

在 MVCC 中提供了獲取 一致性視圖 的操做使得備份變得很是簡單,若是想了解 MVCC 能夠參考個人另外一篇文章 你真的懂MVCC嗎?來手動實踐一下?

表鎖

MDL(Meta Data Lock)元數據鎖

MDL鎖用來保證只有一個線程能對該表進行表結構更改

怎麼說呢?MDL分爲 MDL寫鎖MDL讀鎖,加鎖規則是這樣的

  • 當線程對一個表進行 CRUD 操做的時候會加 MDL讀鎖
  • 當線程對一個表進行 表結構更改 操做的時候會加 MDL寫鎖
  • 寫鎖和讀鎖,寫鎖和寫鎖互斥,讀鎖之間不互斥

lock tables xxx read/write;

這是給一個表設置讀鎖和寫鎖的命令,若是在某個線程A中執行lock tables t1 read, t2 write; 這個語句,則其餘線程寫t一、讀寫t2的語句都會被阻塞。同時,線程A在執行unlock tables以前,也只能執行讀t一、讀寫t2的操做。連寫t1都不容許,天然也不能訪問其餘表。

這種表鎖是一種處理併發的方式,可是在InnoDB中經常使用的是行鎖

行鎖

咱們知道在5.5版本之前 MySQL 的默認存儲引擎是 MyISAM,而 MyISAM 和 InnoDB 最大的區別就是兩個

  • 事務
  • 行鎖

其中行鎖是咱們今天的主題,若是不瞭解事務能夠去補習一下。

其實行鎖就是兩個鎖,你能夠理解爲 寫鎖(排他鎖 X鎖)和讀鎖(共享鎖 S鎖)

  • 共享鎖(S鎖):容許一個事務去讀一行,阻止其餘事務得到相同數據集的排他鎖。 也叫作讀鎖:讀鎖是共享的,多個客戶能夠同時讀取同一個資源,但不容許其餘客戶修改。

  • 排他鎖(X鎖):容許得到排他鎖的事務更新數據,阻止其餘事務取得相同數據集的共享讀鎖和排他寫鎖。也叫作寫鎖:寫鎖是排他的,寫鎖會阻塞其餘的寫鎖和讀鎖。

而行鎖還會引發一個一個很頭疼的問題,那就是死鎖

若是事務A對行100加了寫鎖,事務B對行101加了寫鎖,此時事務A想要修改行101而事務B又想修改行100,這樣佔有且等待就致使了死鎖問題,而面對死鎖問題就只有檢測和預防了。

next-key鎖

MVCC 和行鎖是沒法解決 幻讀 問題的,這個時候 InnoDB 使用了 一個叫 GAP鎖(間隙鎖) 的東西,它配合 行鎖 造成了 next-key鎖,解決了幻讀的問題。

可是由於它的加鎖規則,又致使了擴大了一些加鎖範圍從而減小數據庫併發能力。具體的加鎖規則以下:

  • 加鎖的基本單位是next-key lock 就是行鎖和GAP鎖結合。
  • 查找過程當中訪問到的對象就會加鎖。
  • 索引上的等值查詢,給惟一索引加鎖的時候,next-key lock退化爲行鎖。
  • 索引上的等值查詢,向右遍歷時且最後一個值不知足等值條件的時候,next-key lock退化爲間隙鎖。
  • 惟一索引上的範圍查詢會訪問到不知足條件的第一個值爲止。

MVCC 解決幻讀的思路比較複雜,這裏就不作過多的驗證。

總結

對於 MySQL 的索引來講,我給了不少最佳實踐,其實這些最佳實踐都是從原理來的,而 InnoDB 其實就是一個改進版的 B+樹,還有存儲索引的結構。弄懂了這些你就會駕輕就熟起來。

而對於 MySQL 的鎖,主要就是在行鎖方面,InnoDB 其實就是使用了 行鎖,MVCC還有next-key鎖來實現事務併發控制的

而對於MySQL中最重要的其實就是 鎖和索引 了,由於內容太多這篇文章僅僅作一些介紹和簡單的分析,若是想深刻了解能夠查看相應的文章。

相關文章
相關標籤/搜索