InnoDB雜談

目錄

 

一、索引算法

1、B+樹

1.1B+樹的插入

1.2B+樹的刪除

2、B+樹索引

2.1聚集索引

2.2輔助索引

2.3創建索引

2.4Online DDL

2.5Cardinality

2.6MRR

2.7哈希索引

二、鎖

1、行級鎖

2、樂觀鎖

3、意向鎖

4、一致性非鎖定讀

5、鎖問題

三、事務

1、原子性

2、一致性

3、隔離性

4、持久性

5、事務的隔離級別

6、分佈式事務

四、InnoDB的一些特性

1、後臺線程

2、LRU

3、Insert Buffer


一、索引算法

       這塊分爲B+樹索引,全文索引,哈希索引。

       B+樹索引並不能找到一個給定鍵值的具體行,B+樹索引能找到的只是被查找數據行所在的頁,然後數據庫通過把頁讀入到內存,再在內存中進行查找,最後得到要查找的數據。

1、B+樹

       B+樹是爲磁盤或其它直接存儲輔助設備設計的一種平衡查找樹。在B+樹中,所有記錄節點都是按鍵值的大小順序存放在同一層的葉子節點上。

1.1B+樹的插入

       葉子節點page未滿,索引page未滿:

(1)直接插入記錄到葉子節點。

       葉子節點page滿了,索引page未滿:

(1)拆分葉子節點page

(2)將中間的節點放入到索引page中

(3)原page數據按大小放入兩個葉子節點page,並插入元素

       葉子節點page滿了,索引page滿了:

(1)拆分葉子節點page

(2)拆分索引節點page

(3)將各中間節點放入上層page

       旋轉操作:

       當要插入的元素所要在的頁滿了,但是其左兄弟頁沒有滿,那麼此時並不會進行拆頁,而是會將當前滿了的頁的一些數據移到左兄弟頁,然後再將元素插入當前頁。

       其實這裏介紹的分裂是最簡單的情況,數據庫中會有所不同,一是分裂不一定是從中間分裂的,二是涉及到併發。

 

1.2B+樹的刪除

       葉子節點page大於填充因子,索引節點page大於填充因子:

  • 直接刪除節點。

       葉子節點page小於填充因子,索引節點page大於填充因子:

  • 合併葉子節點和它的兄弟節點,更新索引節點page。

       葉子節點page小於填充因子,索引節點page小於填充因子:

  • 合併葉子節點和它的兄弟節點,更新索引節點page,合併索引節點和它的兄弟節點。

 

2、B+樹索引

2.1聚集索引

       Innodb存儲引擎表是索引組織表,即表中數據按主鍵順序存放;而MyISAM存儲引擎表是堆表,即數據按插入順序存儲。而聚集索引就是按照每張表的主鍵構造一棵B+樹,同時葉子節點中存放的即爲整張表的行記錄信息,也將聚集索引的葉子節點稱爲數據頁。

       聚集索引的存儲並不是物理上連續的,而是邏輯上連續的。這裏有兩點:一是前面說過的頁通過雙向鏈表鏈接,頁按照主鍵的順序排序;二是每個頁中的記錄也是通過雙向鏈表進行維護的,物理存儲上可以同樣不按照主鍵存儲。

 

2.2輔助索引

       輔助索引的葉子節點並不包含行記錄的全部數據。葉子節點除了包含鍵值以外,每個葉子節點中的索引行中還包含了一個書籤,用於找到與索引對應的行數據。

 

2.3創建索引

       Mysql5.5之前添加刪除索引都需要對錶加排他鎖,新建臨時表,然後把原表中的數據導入到臨時表中,接着刪除原表。

       個人對這種做法是很不解的,之後他們就出來了一種稱爲FIC(Fast Index Creation)的技術,簡單來說就是聚集索引修改和原來一樣,輔助索引只加S鎖,不加X鎖了,並且也不拷貝一份數據了。

 

2.4Online DDL

       FIC讓索引創建時的讀操作不再阻塞,但是會阻塞DML操作和DDL操作,所以mysql5.6之後開始支持Online DDL,它允許輔助索引創建的時候,INSERT,UPDATE等DML操作都能執行,部分如列的重命名等DDL操作也能執行,原理就是輔助索引創建刪除的同時,將INSERT這類操作日誌寫到一個緩存中,完成索引創建後再將這些操作重做到表上,這個做法的味道感覺就像redis中的AOF重寫過程中存在一個AOF重寫緩衝區,記錄這期間的命令一樣。

 

2.5Cardinality

       Cardinality是判斷是否作爲索引的一個指標,當一個字段的重複率很高如性別,地區等,它是不適合作爲索引的,因爲這種情況很有可能還是要掃描大部分的頁,效率並沒有提升,還增加了索引維護的成本。

       那麼怎麼知道一個字段的不重複率是不是很高呢,可以看cardinality,它表示的是字段不重複記錄數量的預估值,cardinality/n_rows_in_table就是不重複率了,不重複率高則該字段是適合作爲索引的。

       Innodb中cardinality的計算是隨機採樣B+樹中的八個葉子節點頁,統計每個頁不同記錄的個數,然後等比例擴大到整個表的頁數即爲預估的不重複記錄數量。這個數會在表中1/16的數據發生過變化或超過20億條數據發生了變化後重新計算。

 

2.6MRR

       MRR(Multi-Range Read):簡單來說就是對於範圍查詢會先將讀到的索引鍵值對按頁的id在緩存中排好序,然後按順序讀磁盤中數據。這樣做可以將隨機訪問轉化爲較爲順序的數據訪問,性能提升極大。

 

2.7哈希索引

       根據行的查詢情況會自適應地建立哈希鍵值對,用於等值查詢。

 

二、鎖

       Innodb中,鎖分爲latch和lock,latch指的是互斥量讀寫鎖這些,lock指的是對事物的鎖,鎖的是表,頁,行。

1、行級鎖

       行級鎖分爲:共享(S)鎖和排他(X)鎖。

       排他鎖的算法又有三種:

  • Record Lock:單個行記錄上的鎖
  • Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄本身
  • Next-Key Lock:前兩者加起來,鎖定一個範圍,並鎖定記錄本身

       這裏有以下幾點需要理解:

  • 所謂鎖算法,鎖的都是B+樹中的一段物理範圍,而不是抽象的數值範圍。
  • record lock鎖的是一個記錄實例,而不是索引等於某個值的所有記錄。對應(1)中的描述,它鎖的就是某條具體的記錄在B+樹中的一段物理數據。
  • gap lock鎖定一個範圍,它鎖的並不是索引在一個數值上的範圍,而是當前這個數值範圍在B+樹中的那段物理數據。

理解了上面幾點,下面兩個機制就很好懂了:

  • Next-Key Lock能夠防止幻讀,比如1(gap 3 gap 3 gap)6這塊數據,括號內是被鎖住的,這時想要插入3是插不進去的,這就防止了3的數量增加從而防止了幻讀,但是同時2,4,5也插不進去了,算是一點犧牲把,如果它們能插進去,也就是鎖的1 gap(3 gap 3)gap 6,那麼3也是有可能能夠插進去的。
  • 當索引唯一時,Next-Key Lock降級爲Record Lock,因爲該索引已經唯一了所以沒必要對兩邊加鎖了。

       這樣子看來,有了Next-Key Lock,RR隔離級別下就不存在幻讀了?不然,如果不顯示地使用FOR UPDATE語句進行鎖定讀取,還是會出現幻讀,可以參考:https://www.cnblogs.com/13579net/p/11429923.html 和 https://www.jianshu.com/p/cef49aeff36b 。

 

2、樂觀鎖

       所謂樂觀鎖,也叫樂觀併發控制,本質上就是CAS,它假設多用戶併發的事務在處理時不會彼此互相影響,各事務能夠在不產生鎖的情況下處理各自影響的那部分數據。在提交數據更新之前,每個事務會先檢查在該事務讀取數據後,有沒有其他事務又修改了該數據。如果其他事務有更新的話,那麼當前正在提交的事務會進行回滾。
樂觀鎖一般依靠的是記錄數據版本來實現,即通過在表中添加版本號字段來作爲是否可以成功提交的關鍵因素。

 

3、意向鎖

       意向鎖分爲:意向共享(IS)鎖和意向排他(IX)鎖。意向鎖指事務希望在更細粒度上進行加鎖,要對某行加排他鎖,首先需要對錶加意向排他鎖,然後對頁加意向排他鎖,然後對行加排他鎖,這樣做目的是將鎖定的對象分爲多個層次,提高效率。

       試想一下如果沒有意向鎖,那此時有一個事務想對錶加共享鎖,它需要對整個表每行都判斷一下有沒有被加排他鎖,這樣效率極低。

       兼容性:

       要注意的是,這裏的兼容性都是表鎖級別的,意向鎖並不會與行級共享/排他鎖互斥。

 

4、一致性非鎖定讀

       一致性非鎖定讀是指innodb通過行多版本控制的方式來讀取當前執行時間數據庫中行的數據。如果此時讀取的行正在執行delete或update操作,並不會等待行鎖的釋放,而是會讀取行的一個快照數據。

       快照數據是指該行的之前版本的數據,該實現是通過undo段來完成。而undo用來在事務中回滾數據,因此快照數據本身是沒有額外的開銷。

       在READ COMMITTED事務隔離級別下,對於快照數據,非一致性讀總是讀取被鎖定行的最新一份快照數據。而在REPEATABLE READ事務隔離級別下,對於快照數據,非一致性讀總是讀取事務開始時的行數據版本。

 

5、鎖問題

  • 髒讀:當前事務可以讀到另外事務未提交的數據,簡單來說就是可以讀到髒數據。髒讀發生的條件是需要事務的隔離級別爲READ UNCOMMITTED。
  • 死鎖

 

三、事務

       數據庫事務是指單個工作邏輯的一系列操作,其要麼完全成功執行,要麼完全沒執行,滿足ACID屬性。

       ACID是以下四點的縮寫:

  • 原子性(Atomicity):事務的一系列操作要麼全部成功,要麼失敗就全部回滾。
  • 一致性(Consistency):事務執行前後是從一個正確的狀態轉移到另一個正確的狀態,也叫內部一致性,與CAP中的一致性不同。
  • 隔離性(Isolation):一個事務在對某一數據操作時,其它事務不能對該數據進行操作。
  • 持久性(Durability):事務一旦提交,對數據庫的修改就是永久性的了,任一時刻即使數據庫崩潰,數據也不丟失。

 

1、原子性

       Innodb的原子性是通過undo實現的,undo存放在數據庫內部的一個特殊段中,位於共享表空間內。如果用戶執行的事務或語句由於某種原因失敗了,就可以利用這些undo信息將數據回滾到修改之前的樣子。

       當回滾時,innodb實際上做的是與先前相反的工作。對於INSERT,回滾時執行一個DELETE;對於UPDATE,執行一個相反的UPDATE;所以並不會讓表空間的大小收縮。

       當然,undo log的操作也會產生redo log,做持久性的保護。

       當事務提交後並不能馬上刪除undo log,因爲可能有其它事務需要通過undo log得到記錄之前的版本,如RR隔離級別下的重複讀。具體刪除undo log的操作是交給purge線程,purge線程做的事情還有真正地更新記錄行。對於一個delete操作,innodb只是將那行記錄的delete flag設爲1,記錄並沒有刪除,還存在於B+樹中,甚至沒有產生undo log,真正的delete操作是在該行記錄已不被任何其它事務引用時才進行的。

 

2、一致性

       innodb的鎖機制,redo日誌,undo日誌共同保證了事務的一致性。鎖機制和undo日誌在前面已經有講到了,在持久性裏面會着重介紹。

 

3、隔離性

       隔離性是通過鎖來實現的,Innodb的鎖家族很豐富,第二章已經詳細介紹了,這裏不再贅述。

 

4、持久性

       事務的持久性是用重做日誌保證的,即當事務提交時,必須先將該事務的所有日誌寫入到重做日誌文件進行持久化,事務的commit操作纔算完成。

       之前我就在想一個問題,這個redo日誌到底有什麼用呢,我每次對實際更新或者操作的數據做持久化不是也一樣就夠了嗎?實際仔細想想就能知道,這樣做主要是因爲redo日誌是順序寫,效率更高,每次直接對數據做持久化是隨機寫,效率會很低,所以實際數據的持久化都是待緩存積攢到一定量再一次寫進磁盤以提高效率。

 

5、事務的隔離級別

       在說隔離級別之前,先理解兩個概念:不可重複讀和幻讀。Innodb技術內幕中寫到,mysql官方文檔將不可重複讀定義爲幻象問題。但是我並沒有檢索到這條定義,所以這裏說一下我自己的理解。不可重複讀指的是事務A兩次讀取同一行記錄,因爲事務B的UPDATE操作導致其兩次讀取的數據不一樣。幻讀指的是事務A兩次讀取一定範圍的記錄,由於事務B的INSERT操作導致其兩次讀取的數據不一樣。當然,這兩個問題都可以通過前面介紹的顯示添加Next-Key Lock解決。

       有了這兩個概念,從mysql官網整理以下四個事務的隔離級別:

  • READ UNCOMMITTED:以非鎖定方式讀,並可能產生髒讀。
  • READ COMMITED:同一個事務,每次讀都讀取最新快照;對於鎖定讀,只鎖定記錄,即只使用Record Lock,所以可能發生幻讀。
  • REPEATABLE READ:非鎖定讀則讀取第一次讀取建立的快照,這樣彷彿非鎖定讀可以解決幻讀問題,但實際上並沒有完全解決,例子參考行級鎖小節給出的例子;對於鎖定讀,每次鎖定一個範圍,即使用Next-Key Lock,可以解決不可重複讀和幻讀問題。
  • SERIALIZABLE:類似於REPEATABLE READ,如果沒有啓用autocommit則會對非鎖定讀隱式轉換爲SELECT ... FROM FOR SHARE;

 

6、分佈式事務

       關於分佈式事務事務,在網上看到一個很好的總結:“分佈式事務本質上是對多個數據庫的事務進行統一控制,按照控制力度可以分爲:不控制、部分控制和完全控制。不控制就是不引入分佈式事務,部分控制就是各種變種的兩階段提交,包括上面提到的消息事務+最終一致性、TCC模式,而完全控制就是完全實現兩階段提交。部分控制的好處是併發量和性能很好,缺點是數據一致性減弱了,完全控制則是犧牲了性能,保障了一致性,具體用哪種方式,最終還是取決於業務場景。”

       InnoDB提供對XA事務的支持,實現兩階段提交。

       XA事務由一或多個RM(Resource Managers,一般爲數據庫),一個TM(Transaction Manager,協調全局事務的各個事務,mysql中爲連接mysql的客戶端)以及一個AP(Application Program,定義事務邊界,指定全局事務中的操作)組成。

       在第一階段,所有節點開始準備(PREPARE),告訴事務管理器它們準備好提交了。第二階段,事務管理器告訴資源管理器執行ROLLBAKC還是COMMIT。

 

四、InnoDB的一些特性

1、後臺線程

  • Master Thread:將緩存池中的數據異步刷新到磁盤。
  • IO Thread:四個read thread,四個write thread,一個insert buffer thread,一個log thread。
  • Purge Thread:回收已經使用並分配的undo頁。

 

2、LRU

       緩存池通過LRU算法管理,但不同的是,新訪問的頁並不是直接放到首部,而是放到midpoint位置,因爲數據庫經常會全表掃描。

 

3、Insert Buffer

       對於非聚集索引的插入或者更新操作,不是每一次直接插入到索引頁中,而是先判斷插入的非聚集索引頁是否在緩存池中,若在,則直接插入;若不在,則先放入一個Insert Buffer對象中,然後再以一定的頻率和情況進行InsertBuffer和輔助索引葉子節點的merge操作,這樣做的目的主要是減少離散寫,可以將多個寫操作合併到一次。

       但是Insert Buffer的使用需要同時滿足兩個條件:

  • 索引是輔助索引。
  • 索引不是唯一的。

       如果索引是唯一的,那每次插入或更新都要去查找索引頁來判斷記錄的唯一性,這樣肯定又會有離散讀的情況發生,讓Insert Buffer失去意義。同樣這也是索引必須是輔助索引的原因。

       Insert Buffer的數據結構是一棵B+樹,全局只有一棵Insert Buffer B+樹,負責所有的表的輔助索引。

 

參考:

https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html

https://www.cnblogs.com/cxxjohnson/p/9145548.html

《MySQL技術內幕--InnoDB存儲引擎》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章