1. 程式人生 > >關於InnoDB的讀寫鎖類型以及加鎖方式

關於InnoDB的讀寫鎖類型以及加鎖方式

索引 不完全 b2c text 按順序 net c中 加鎖 並不是

(本文為了方便,英文關鍵詞都都采用小寫方式,相關知識點會簡單介紹,爭取做到可以獨立閱讀)

文章開始我會先介紹本文需要的知識點如下:

  • innodb的聚簇索引(聚集索引)和非聚簇索引(二級索引、非聚集索引)的知識
  • innodb的隔離級別(isolation level)
  • 簡單的sql知識(能讀懂sql語句)
  • MVCC(Multi-Version Concurrent Control)多版本並發控制
  • 數據的臟讀、幻讀(如果有時間會詳細講一下臟讀如果沒時間,網上講這個地方的也很多)
問題1:讀有幾種模式、加鎖有幾種方式
我們先看一個mysql表和幾條語句 表名稱:my_table 搜索引擎:innodb 表結構: 技術分享圖片
1. select * from my_table where id = 1;
2. select * from my_table where id = 1 lock in share mode;
3. select * from my_table where id = 1 for update;
4. update my_table set address = ‘tianjin‘ where id = 1;
先說隔離級別,mysql隔離級別分為四種: 未提交讀(read uncommitted)、提交讀(read committed)、重復讀(repeatable read)、序列化(serializable) 其中mysql默認的隔離級別為重復讀(repeatable read),以下簡稱為rr,本文也只介紹這種模式
讀的模式分為兩種:
  • 快照讀(snapshot read)
  • 當前讀(current read)
我們先來了解一下MVCC: MVCC是為了實現數據庫的並發控制而設計的一種協議。與其相對的事LBCC即基於鎖的並發控制(Lock-Based Concurrent Control)。要實現數據庫的並發訪問控制,最簡單的做法就是加鎖訪問,即讀的時候不能寫(這個讀為當前讀,後面介紹。允許多個線程同時對想讀的內容加鎖,即共享鎖或叫S鎖),寫的時候不能讀(只能有一個線程對同一內容進行寫操作,即排它鎖,X鎖)。這樣的加鎖訪問,其實並不算是真正的並發,或者說它只能實現並發的讀,既讀寫串行化,這樣就大大降低了數據庫的讀寫性能。 LBCC是四種隔離級別中級別最高的Serialize隔離級別。MVCC對比LBCC它的最大好處便是,讀不加鎖,讀寫不沖突。在MVCC中,讀操作可以分成兩類,快照讀(Snapshot read)和當前讀(current read)。快照讀,讀取的是記錄的可見版本(可能是歷史版本,即最新的數據可能正在被當前執行的事務並發修改),不會對返回的記錄加鎖,如上面的sql語句1;而當前讀,讀取的是記錄的最新版本,並且會對返回的記錄加鎖,保證其他事務不會並發修改這條記錄。如上面的sql語句2,3,4。不同的是2加的是s鎖,3、4加的是x鎖,insert加的也是x鎖。 註:MVCC只在RC和RR兩個隔離級別下工作,其他兩個隔離級別都和MVCC不兼容
加鎖的方式:(未涉及意向鎖)
先看一個sql語句
update my_table set name =‘zhang‘ where id = 1;
假設id為主鍵:此條sql執行的時候會給此行數據加x鎖,如下圖 技術分享圖片

mysql的innodb默認的隔離模式為RR模式,既可重復讀,Innodb的RR隔離級別保證對讀取到的記錄加鎖 (記錄鎖),同時保證對讀取的範圍加鎖,新的滿足查詢條件的記錄不能夠插入 (間隙鎖),因此不存在幻讀現象。但是標準的RR只能保證在同一事務中多次讀取同樣記錄的結果是一致的,而無法解決幻讀(不保證在事務中出現)問題。Innodb的幻讀解決是依靠MVCC的實現機制做到的。其他模式以後有時間再加在這裏不對其他模式做講解。 這裏因為id為主鍵,innodb中在主鍵上存在聚簇索引,其他的索引均為二級索引,這裏做一下簡單介紹 聚簇索引:在innodb存儲引擎中,主鍵的存在至關重要,及時你不為表設置主鍵,存儲引擎也會隱式的定義一個主鍵,只是對用戶來說透明。之所以說他重要,是因為聚簇索引的存儲是和數據存儲在一起的,而聚簇索引的數據就是數據存儲的順序。如果需要查找的數據是連續的,那麽按照聚簇索引查找到的數據位置也是連續的,只需要按順序讀取就可以。對於聚集索引,葉子結點即存儲了真實的數據行,不再有另外單獨的數據頁(這裏和後面的二級索引有區別,二級索引的葉子節點指向數據頁數據行的邏輯指針,需要按照指針再去檢索數據)。 在一張表上最多只能創建一個聚集索引,因為真實數據的物理順序只能有一種。 二級索引:表數據存儲順序與索引順序無關。對於非聚集索引,葉結點包含索引字段值及指向數據頁數據行的邏輯指針,其行數量與數據表行數據量一致。 看上面的sql語句,或者看之前的幾條sql,這個語句執行的時候會給這條記錄加x鎖,這時候如果其他事務中的語句也在進行鎖的操作(既更新、插入或者刪除,以及2語句當前讀操作加的s鎖)就會造成鎖爭用(innodb出現鎖爭用的時候處理方式為回滾超時獲取不到鎖的事務)。 我們先按照上面四條語句兩條並發時的相互影響的情況來 情況1:id為主鍵
1. select * from my_table where id = 1;
2. select * from my_table where id = 1 lock in share mode;
我們上面說過,語句1為快照讀,對其他的讀或者寫沒有影響。所以這兩條語句並行時,1讀快照,2為語句加s鎖。
select * from my_table where id = 1 lock in share mode;
情況2:id為主鍵
2. select * from my_table where id = 1 lock in share mode;
3. select * from my_table where id = 1 for update;
其中語句2加s鎖,3加x鎖(在數據被加s鎖的時候,其他的給這條想要讀取這條記錄也需要給這條記錄加s鎖,這就是為什麽s鎖是共享鎖。此時是不允許再給這條記錄加x鎖的)兩種鎖是不能同時存在在一條記錄上的。所以兩條語句不能並發執行。 情況3:id為主鍵
3. select * from my_table where id = 1 for update;
4. update my_table set address = ‘tianjin‘ where id = 1;
這種情況下兩條語句都需要給數據加x鎖,所以顯然不能並發執行。

下面我們來討論一下id不為主鍵的情況

id若不為主鍵,則不能使用聚簇索引,而在innodb中有一下幾種情況
  • 二級唯一索引
  • 二級不唯一索引
  • 沒有索引
由於只要不是快照讀則一定會加鎖,我們已經了解了鎖的形式,則不難明白不論是先加x鎖還是s鎖哪一種,都一定不能再加另一種鎖,所以我們下面只分析加鎖的方式 情況4:假設id為二級唯一索引(unique)
4. update my_table set address = ‘tianjin‘ where id = 1;
這裏很明顯需要加x鎖,但是這裏的加鎖和id為主鍵(索引為聚簇索引)的情況加鎖不完全一樣,會稍微復雜一點。 這個時候我們需要對索引知識有一定的了解,上面說過二級索引中的葉子節點存儲的除了索引信息還有到實際數據的邏輯指針,也就是說我們需要現在二級唯一索引中查找到這條記錄的邏輯指針,然後通過指針去查找到數據實際的存儲位置並給這條數據加鎖。註意,這裏的加鎖應該是加在了索引上和數據本身上(或者說是聚簇索引上也可以,因為兩者是存儲在一個結構中的,而innodb中二級索引葉子中的邏輯指針並不是數據存儲的物理地址,而是主鍵值)而不只是二級唯一索引上。如果想用好innodb,他的索引結構是必須要學習的,如果以後有時間會詳細介紹。 情況5:age為二級非唯一索引,id為主鍵
5. update my_table set address = ‘tianjin‘ where age = 25;
此種情況比前一種情況更特殊,因為情況3和4都只能找到一條記錄,只需要對這條記錄加鎖,則不會發生結果集被修改的情況。但是如果age為二級非唯一索引,我們看到如下表格中有兩條記錄age=25 技術分享圖片 如果我們在update的過程中,有一個用戶插入了一條age也為25的數據,那麽就是發生一種現象,你明明更新了所有的age=25的數據,但是執行完了卻有一條數據沒有更新的幻覺,這就是幻讀(可以自行查找資料,避免本文過長)。這個時候顯然只給查找出的數據加鎖是解決不了這個問題的。所以就有了gap鎖(間隙鎖字面上可能更好理解)這裏需要畫圖大家理解一下: 技術分享圖片 如圖這裏在age為25的有兩個 ,id分別為1和3。我們在修改執行上面語句的時候,如果沒有gap鎖,則可能發生一種情況:另一個事務執行如下語句
update my_table set age=25 where id=2;
則發生幻讀現象。gap鎖可以防止在語句或者事務執行過程中有滿足條件的記錄插入進來造成幻讀。所以說在此種情況下,除了給滿足條件的二級索引和數據(或聚簇索引)加x鎖之外還要給相關的間隙加鎖。可以理解為這個加gap鎖是在二級索引的範圍內防止新的索引項加入,因為二級索引本身也是有序的。

情況6:age上無索引,id為主鍵
5. update my_table set address = ‘tianjin‘ where age = 25;
這種情況下,所有記錄都被加上了X鎖,每條記錄間的間隙(GAP)也同時被加上了GAP鎖。在實際情況中可能sql更復雜,如果用到了其他列索引,mysql會按照索引順序篩選結果,也許並不會進行全表加x鎖和gap鎖。 註:轉自自己的博客

關於InnoDB的讀寫鎖類型以及加鎖方式