InnoDB索引機制

MySQL- InnoDB鎖機制

InnoDB與MyISAM的最大不一樣有兩點:html

一是支持事務(TRANSACTION);二是採用了行級鎖。mysql

MyISAM不支持事務、行鎖和外鍵,訪問速度快(表級鎖,可同時讀,不可寫),多使用於多讀寫少或對事務完整性沒有要求的狀況;算法

MEMORY:將全部的數據保存在RAM中,對錶的大小有限制;sql

MERGE:用於將一系列等同的MyISAM表以邏輯方式組合在一塊兒,並做爲一個對象引用它們;數據庫

1、事務

事務是由一組SQL語句組成的邏輯處理單元,事務具備如下4個屬性,一般簡稱爲事務的ACID屬性。session

  • 原子性(Atomicity):事務是一個原子操做單元,其對數據的修改,要麼全都執行,要麼全都不執行。
  • 一致性(Consistent):在事務開始和完成時,數據都必須保持一致狀態。這意味着全部相關的數據規則都必須應用於事務的修改,以保持數據的完整性;事務結束時,全部的內部數據結構(如B樹索引或雙向鏈表)也都必須是正確的。
  • 隔離性(Isolation):數據庫系統提供必定的隔離機制,保證事務在不受外部併發操做影響的「獨立」環境執行。這意味着事務處理過程當中的中間狀態對外部是不可見的,反之亦然。
  • 持久性(Durable):事務完成以後,它對於數據的修改是永久性的,即便出現系統故障也可以保持。

2、併發事務處理帶來的問題

  • 髒讀(Dirty Reads):一個事務正在對一條記錄作修改,在這個事務提交前,另外一個事務也來讀取同一條記錄,若是不加控制,第二個事務讀取了這些「髒」數據,並據此作進一步的處理,就會產生未提交的數據依賴關係。這種現象被形象地叫作"髒讀"。
  • 不可重複讀(Non-Repeatable Reads):一個事務在讀取某些數據後的某個時間,再次讀取之前讀過的數據,卻發現其讀出的數據已經發生了改變、或某些記錄已經被刪除了!這種現象就叫作「不可重複讀」。 
  • 幻讀(Phantom Reads):一個事務按相同的查詢條件從新讀取之前檢索過的數據,卻發現其餘事務插入了知足其查詢條件的新數據,這種現象就稱爲「幻讀」。

3、事務隔離級別

4種隔離級別比較數據結構

隔離級別併發

讀數據一致性post

髒讀優化

不可重複讀

幻讀

未提交讀(Read uncommitted)

最低級別,只能保證不讀取物理上損壞的數據

已提交度(Read committed)

語句級

可重複讀(Repeatable read)

事務級

可序列化(Serializable)

最高級別,事務級

 數據庫的事務隔離越嚴格,併發反作用越小,但付出的代價也就越大,由於事務隔離實質上就是使事務在必定程度上 「串行化」進行,這顯然與「併發」是矛盾的。同時,不一樣的應用對讀一致性和事務隔離程度的要求也是不一樣的,好比許多應用對「不可重複讀」和「幻讀」並不敏感,可能更關心數據併發訪問的能力。

 

4、事務傳播行爲

1PROPAGATION_REQUIRES_NEW:建立新事務,不管當前存不存在事務,都建立新事務。

2PROPAGATION_REQUIRED:支持當前事務,若是當前存在事務,就加入該事務,若是當前沒有事務,就建立一個新事務。

3PROPAGATION_SUPPORTS:支持當前事務,若是當前存在事務,就加入該事務,若是當前不存在事務,就以非事務執行。‘

4PROPAGATION_MANDATORY:支持當前事務,若是當前存在事務,就加入該事務,若是當前不存在事務,就拋出異常。

5PROPAGATION_NOT_SUPPORTED:以非事務方式執行操做,若是當前存在事務,就把當前事務掛起。

6PROPAGATION_NEVER:以非事務方式執行,若是當前存在事務,則拋出異常。

7PROPAGATION_NESTED:若是當前存在事務,則在嵌套事務內執行。若是當前沒有事務,則執行與PROPAGATION_REQUIRED相似的操做。

5、InnoDB中的鎖

InnoDB存儲引擎既支持行級鎖,也支持表級鎖,默認狀況下采用行級鎖。

InnoDB的鎖類型有:

  • 共享鎖(S):容許一個事務去讀一行,阻止其餘事務得到相同數據集的排他鎖。
  • 排他鎖(X):容許得到排他鎖的事務更新數據,阻止其餘事務取得相同數據集的共享讀鎖和排他寫鎖。

  另外,爲了容許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖。

  • 意向共享鎖(IS):事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖。
  • 意向排他鎖(IX):事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。

 InnoDB行鎖模式兼容性列表

X

IX

S

IS

X

衝突

衝突

衝突

衝突

IX

衝突

兼容

衝突

兼容

S

衝突

衝突

兼容

兼容

IS

衝突

兼容

兼容

兼容

   意向鎖是InnoDB自動加的,不需用戶干預。對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及數據集加排他鎖(X);對於普通SELECT語句,InnoDB不會加任何鎖;

若將上鎖的對象當作一棵樹,那麼對最下層的對象上鎖,也就是對最細粒度的對象進行上鎖,那麼首先須要對粗粒度的對象上鎖。以下圖,當咱們要對記錄加上排他鎖時,那麼咱們先要對數據庫A、表、頁都加上意向排他鎖,最後再對記錄加上排他鎖,若其中任何一部分致使等待,那麼該操做須要等待粗粒度鎖的完成。

6、InnoDB中鎖的實現算法

InnoDB存儲引擎有3種行鎖的算法設計,分別是:

  • Record Lock:單個行記錄上的鎖。
  • Gap Lock:間隙鎖,鎖定一個範圍,但不包括記錄自己。GAP鎖的目的,是爲了幻讀的問題。
  • Next-Key Lock:Gap Lock + Record Lock。鎖定一個範圍,而且鎖定記錄自己。

Record Lock老是會去鎖住索引記錄。若是InnoDB存儲引擎表創建的時候沒有設置任何一個索引,這時InnoDB存儲引擎會使用隱式的主鍵來進行鎖定。

InnoDB中對於行的查詢都是採用Next-Key Lock鎖定算法。對於不一樣SQL查詢語句,可能設置共享的(Share) Next-Key Lock和排他的(exlusive) Next-Key Lock,其主要目的也是爲了解決幻行問題。

但當查詢的索引含有所有惟一屬性(主鍵或惟一索引)時InnoDB存儲引擎會對Next-Key Lock進行優化,將其降級爲Record Lock,即僅鎖住索引自己,而不是範圍,從而提升應用的併發性。

可是若是查詢的條件中包含的是輔助索引,則狀況會徹底不一樣,會產生以下鎖狀況:

  1. 首先會對輔助索引對應的主鍵索引加上Record Lock。
  2. 對輔助索引加上Next-Key Lock,鎖定輔助索引的前一個區間。
  3. 對輔助索引的下一個區間加上gap Lock。

若是查詢條件沒有索引,則全表掃描,且每條記錄之間加上了gap鎖,爲了防止幻讀。

下面是一個示例:

咱們知道InnoDB默認的事務隔離級別爲REPEATABLE READ模式,而REPEATABLE READ模式下,Next-Key Lock算法又是默認的行記錄鎖定算法,因此就能夠避免幻讀的現象。

7、InnoDB行鎖實現方式

 InnoDB行鎖是經過給索引上的索引項加鎖來實現的,這一點MySQL與Oracle不一樣,後者是經過在數據塊中對相應數據行加鎖來實現的。

InnoDB這種行鎖實現特色意味着:只有經過索引條件檢索數據,InnoDB才使用行級鎖,不然,InnoDB將使用表鎖!

1)在不經過索引條件查詢的時候,InnoDB確實使用的是表鎖,而不是行鎖。

 InnoDB存儲引擎的表在不使用索引時使用表鎖例子

session_1

session_2

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_no_index where id = 1 ;

+------+------+

| id   | name |

+------+------+

| 1    | 1    |

+------+------+

1 row in set (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_no_index where id = 2 ;

+------+------+

| id   | name |

+------+------+

| 2    | 2    |

+------+------+

1 row in set (0.00 sec)

mysql> select * from tab_no_index where id = 1 for update;

+------+------+

| id   | name |

+------+------+

| 1    | 1    |

+------+------+

1 row in set (0.00 sec)

 
 

mysql> select * from tab_no_index where id = 2 for update;

等待

session_1只給一行加了排他鎖,但session_2在請求其餘行的排他鎖時,卻出現了鎖等待!緣由就是在沒有索引的狀況下,InnoDB只能使用表鎖。當咱們給其增長一個索引後,InnoDB就只鎖定了符合條件的行。

                                                                                                                            InnoDB存儲引擎的表在使用索引時使用行鎖例子

session_1

session_2

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_with_index where id = 1 ;

+------+------+

| id   | name |

+------+------+

| 1    | 1    |

+------+------+

1 row in set (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_with_index where id = 2 ;

+------+------+

| id   | name |

+------+------+

| 2    | 2    |

+------+------+

1 row in set (0.00 sec)

mysql> select * from tab_with_index where id = 1 for update;

+------+------+

| id   | name |

+------+------+

| 1    | 1    |

+------+------+

1 row in set (0.00 sec)

 
 

mysql> select * from tab_with_index where id = 2 for update;

+------+------+

| id   | name |

+------+------+

| 2    | 2    |

+------+------+

1 row in set (0.00 sec)

2)因爲MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,因此雖然是訪問不一樣行的記錄,可是若是是使用相同的索引鍵,是會出現鎖衝突的。

                                                                                                                         InnoDB存儲引擎使用相同索引鍵的阻塞例子       

session_1

session_2

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_with_index where id = 1 and name = '1' for update;

+------+------+

| id   | name |

+------+------+

| 1    | 1    |

+------+------+

1 row in set (0.00 sec)

 
 

雖然session_2訪問的是和session_1不一樣的記錄,可是由於使用了相同的索引,因此須要等待鎖:

mysql> select * from tab_with_index where id = 1 and name = '4' for update;

等待

3)當表有多個索引的時候,不一樣的事務可使用不一樣的索引鎖定不一樣的行,另外,不管是使用主鍵索引、惟一索引或普通索引,InnoDB都會使用行鎖來對數據加鎖。

                                                                                                                InnoDB存儲引擎的表使用不一樣索引的阻塞例子

session_1

session_2

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_with_index where id = 1 for update;

+------+------+

| id   | name |

+------+------+

| 1    | 1    |

| 1    | 4    |

+------+------+

2 rows in set (0.00 sec)

 
 

Session_2使用name的索引訪問記錄,由於記錄沒有被索引,因此能夠得到鎖:

mysql> select * from tab_with_index where name = '2' for update;

+------+------+

| id   | name |

+------+------+

| 2    | 2    |

+------+------+

1 row in set (0.00 sec)

 

因爲訪問的記錄已經被session_1鎖定,因此等待得到鎖。:

mysql> select * from tab_with_index where name = '4' for update;

4) 即使在條件中使用了索引字段,可是否使用索引來檢索數據是由MySQL經過判斷不一樣執行計劃的代價來決定的,若是MySQL認爲全表掃描效率更高,好比對一些很小的表,它就不會使用索引,這種狀況下InnoDB將使用表鎖,而不是行鎖。所以,在分析鎖衝突時,別忘了檢查SQL的執行計劃,以確認是否真正使用了索引。

相關文章
相關標籤/搜索