資料庫併發如何讓資料操作序列化

wuzhenhuai發表於2019-04-01

本章主要講解MySQL是如何在併發請求保證資料的正確性。
引入了一些概念,指引學習方向,文末有我們學習參考文章

學習一個新鮮事物的時,我們總會碰到一些新的概念或設計,如果你細心領悟或許可以舉一反三,通過之前學過的東西與之關聯。但是如果你並無感悟,倒也無妨。因為大多數你只需要整理後自己能記住就可以。畢竟我們不是開創者而是使用者。接下來我將要介紹幾個概念或是設計是針對於今天所探討的主題。

MVCC (多版本併發控制)

對於未接觸過這個概念的人肯定是一臉霧水,那我只簡介一下它的作用。如果想學習可以自行查詢資料。

它會在每個資料行後面追加隱藏兩個欄位,分別代表建立(建立的事務版本號),刪除(刪除的事務版本號)。

事務版本號:每啟動一個事務都會產生版本號

id name create_version delete_version
1 dog 1 2
1 pig 2 null
2 cat 2 3
3 cats 3 null

上表為MVCC的一個簡圖,我來描述一下它的經歷。

  1. 事務1 :插入一條id=1,name=dog的資料。
  2. 事務2:更新了id=1,name=pig。 並且插入一條資料id=2,name=cat
  3. 事務3:插入了一條id=3,name=cats,並且刪除了id=2的資料

有了這個設計我們就能弄出不同版本的查詢結果(隔離級別中的快照讀就與之相識)

  1. 獲取最新版本(版本3)的資料,查詢條件為:create_version>=3 and delete_version=null。
  2. 獲取指定版本(版本2)的資料,查詢條件為:create_version<=2 and (delete_version=null or delete_version>2)。

鎖 (防止資料併發操作)

鎖一個特別簡單的名詞,但是初學者未必能理的清楚概念。像我這樣文字理解能不好的人就卡了半天。

最後我的理解是:阻礙你獲取資料的就是鎖。鎖是你與資料交流間的一道屏障。

行鎖

共享鎖(S鎖)又稱為讀鎖(Share lock,簡記為S鎖),若事務T對資料物件A加上S鎖,則其它事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。
排它鎖(X鎖)又稱為寫鎖((eXclusive lock,簡記為X鎖)),若事務T對資料物件A加上X鎖,則只允許T讀取和修改A,其它任何事務都不能再對A加任何型別的鎖,直到T釋放A上的鎖。它防止任何其它事務獲取資源上的鎖,直到在事務的末尾將資源上的原始鎖釋放為止。

這裡我進行了一個比喻,假設資料是一個透明的箱子。如果箱子裡東西能看到就是可查詢,能觸碰到就是可修改。

共享鎖:新增條件(能看到箱子裡東西的事務都能新增,否者需要等待),相當於給箱子加了一個鎖,這個鎖只有自己能開啟。

排它鎖:新增條件(能觸碰到箱子裡東西的事務都能新增,否者需要等待),相當於開啟箱子給裡面東西套上一個盒子,再對盒子加鎖,這個鎖只有自己能開啟

  • 事務1 為資料新增共享鎖,事務2能再新增共享鎖嗎?如果能,事務2能修改資料嗎,事務1能修改資料嗎?
  • 事務1 為資料新增共享鎖,事務2能新增排它鎖嗎?如果能,事務2能修改資料嗎,事務1能修改資料嗎?
  • 事務1 為資料新增排它鎖,事務2能新增共享鎖嗎?如果能,事務2能修改資料嗎,事務1能修改資料嗎?
  • 事務1 為資料新增排它鎖,事務2能新增排它鎖嗎?如果能,事務2能修改資料嗎,事務1能修改資料嗎?

間隙鎖 (gap鎖)

會在查詢資料的兩側插入間隙鎖,導致無法插入。 這與InnoDB索引順序排序有關。

四種事務隔離

在資料庫操作中,為了有效保證併發讀取資料的正確性,提出的事務隔離級別。我們的資料庫鎖,也是為了構建這些隔離級別存在的。

鎖和多版本資料(MVCC)是 InnoDB 實現一致性讀和隔離級別的手段。

因此,在不同的隔離級別下,InnoDB 處理 SQL 時採用的一致性讀策略和需要的鎖是不同的。

Read Uncommitted (RU)

在該隔離級別,所有事務都可以看到其他未提交事務的執行結果。本隔離級別很少用於實際應用,因為它的效能也不比其他級別好多少。讀取未提交的資料,也被稱之為髒讀(Dirty Read)。

Read Committed (RC)

這是大多數資料庫系統的預設隔離級別(但不是MySQL預設的)。它滿足了隔離的簡單定義:一個事務只能看見已經提交事務所做的改變。這種隔離級別 也支援所謂的不可重複讀(Nonrepeatable Read),因為同一事務的其他例項在該例項處理其間可能會有新的commit,所以同一select可能返回不同結果。

Repeatable Read (RR)

這是MySQL的預設事務隔離級別,它確保同一事務的多個例項在併發讀取資料時,會看到同樣的資料行。不過理論上,這會導致另一個棘手的問題:幻讀 (Phantom Read)。簡單的說,幻讀指當使用者讀取某一範圍的資料行時,另一個事務又在該範圍內插入了新行,當使用者再讀取該範圍的資料行時,會發現有新的“幻影” 行。InnoDB和Falcon儲存引擎通過多版本併發控制(MVCC,Multiversion Concurrency Control)機制解決了該問題。

Serializable(可序列化)

這是最高的隔離級別,它通過強制事務排序,使之不可能相互衝突,從而解決幻讀問題。簡言之,它是在每個讀的資料行上加上共享鎖。在這個級別,可能導致大量的超時現象和鎖競爭。

事務中sql語句有加鎖與不加鎖兩種,我們把不加鎖的定義為快照讀,加鎖的定義為當前讀,以下是分類

  • 快照讀:就是select
    • select * from table ….;
  • 當前讀:特殊的讀操作,插入/更新/刪除操作,屬於當前讀,處理的都是當前的資料,需要加鎖。
    • select * from table where ? lock in share mode; S鎖,Gap鎖
    • select * from table where ? for update; X鎖,Gap鎖
    • insert; X鎖,Gap鎖
    • update ; X鎖,Gap鎖
    • delete; X鎖,Gap鎖

MVCC定義了隔離級別不同的快照讀版本,只對RC與RR進行分析。

  1. RC隔離級別:每次快照讀都是獲取最新版本。
  2. RR隔離級別:每次快照讀都是獲取指定版本。

不同的隔離級別當前讀加鎖情況不同,只對RC與RR進行分析。

  1. 共同點:在新增X鎖與S鎖方面是一樣的。
  2. 不同點:RR隔離級別有時還會新增Gap鎖。

加鎖分析

資料庫加鎖解鎖時間點

資料庫遵循的是兩段鎖協議,將事務分成兩個階段,加鎖階段和解鎖階段(所以叫兩段鎖)

事務 加鎖/解鎖處理
begin
insert into test ….. 加insert對應的鎖
update test set… 加update對應的鎖
delete from test …. 加delete對應的鎖
delete from test …. 事務提交時,同時釋放insert、update、delete對應的鎖

但在實際使用過程當中,MySQL做了一些改進,在MySQL Server過濾條件,發現不滿足後,會呼叫unlock_row方法,把不滿足條件的記錄釋放鎖 (違背了二段鎖協議的約束)。這樣做,保證了最後只會持有滿足條件記錄上的鎖,但是每條記錄的加鎖操作還是不能省略的。可見即使是MySQL,為了效率也是會違反規範的。(參見《高效能MySQL》中文第三版p181)

資料庫如何加鎖

資料庫加鎖是比較複雜,有比較多的情況。需要你瞭解InnoDB的索引機制。這裡簡單通過sql執行計劃(explain)分析,步驟如下:

  • type=range
    • key=PRIMARY:主鍵的範圍掃描,會在主鍵B+樹新增行鎖。RR隔離級別下主鍵B+樹會新增間隙鎖。
    • key=唯一索引:唯一索引的範圍掃描,會在主鍵B+樹新增行鎖,唯一索引B+樹上新增行鎖。RR隔離級別下唯一索引B+樹會新增間隙鎖。
    • key=普通索引:普通索引的範圍掃描,會在主鍵B+樹新增行鎖,普通索引B+樹上新增行鎖。RR隔離級別下普通索引B+樹會新增間隙鎖。
  • type=ref
    • key=普通索引:普通索引掃描,返回匹配某個單獨值的所有行,會在主鍵B+樹新增行鎖,普通索引B+樹上新增行鎖。RR隔離級別下普通索引B+樹會新增間隙鎖。
  • type=eq_ref
    • key=唯一索引:唯一索引掃描,返回匹配某個單獨值行,會在主鍵B+樹新增行鎖,唯一索引B+樹上新增行鎖。
  • type=const
    • key=主鍵索引:主鍵索引掃描,返回匹配某個單獨值行,會在主鍵B+樹新增行鎖。

以下是作者的理解,符合所學知識,大家可以參考。

  • 是否可重複讀是針對於快照讀,幻讀是針對於當前讀。
  • RC的不可重複讀,是因為在RC隔離中快照讀是獲取最新快照版本,所有每次相同sql快照讀的資料都可能不一樣。
  • RR可重複讀,是因為在RR隔離中快照讀是獲取指定快照版本,所有每次相同sql快照讀資料的是一樣的。
  • RC隔離中存在幻讀可能性。舉個例子有一個獲取某表全部資訊sql當前讀(select from table for update)(sql的語義是鎖住全表並獲取最新資訊),但是這時如果另一個事務執行(insert into table) 語句仍然可以插入,因為RC隔離中沒有間隙鎖。這會導致前面select from table for update 的資料不是最新有效資料。這就是幻讀。

    幻讀——一個當前讀的查詢sql但是保證不了資料的正確性,這個查詢就可能存在幻讀。

  • RR隔離中有間隙鎖,所以不會幻讀。資料上會出現幻讀的其實已經被InnoDB引擎優化了。

資料庫併發如何讓資料操作序列化

真正理解Mysql的四種隔離級別
MySQL鎖總結
MYSQL MVCC實現原理
理解事務 - MySQL 事務處理機制
Innodb中的事務隔離級別和鎖的關係
MySQL 加鎖處理分析

相關文章