鎖的演算法,隔離級別的問題

2021-09-20 14:56:20 字數 4043 閱讀 7150

innodb儲存引擎有3中行鎖的演算法設計,分別是:

record lock:單個行記錄上的鎖。

gap lock:間隙鎖,鎖定乙個範圍,但不包含記錄本身。

next-key lock:gap lock+record lock,鎖定乙個範圍,並且鎖定記錄本身。

record lock總是會去鎖住索引記錄。如果innodb儲存引擎表建立的時候沒有設定任何乙個索引,這時innodb儲存引擎會使用隱式的主鍵來進行鎖定。

next-key lock是結合了gap lock和record lock的一種鎖定演算法,在next-key lock演算法下,innodb對於行的查詢都是採用這種鎖定演算法。對於不同sql查詢語句,可能設定共享的(share)next-key lock和排他的(exlusive)next-key lock。

可以通過乙個例子來演示next-key lock的鎖定演算法,建立一張表t,插入值為1、2、3、4、7、8的6條記錄。

create table t(a int,primary key(a))engine=innodb;

begin;

insert into t select 1;

insert into t select 2;

insert into t select 3;

insert into t select 4;

insert into t select 7;

insert into t select 8;

commit;

select * from t;

接著開啟兩個會話,會話a在乙個事務中執行select * from t where a<6 lock in share mode,會話b中,插入小於6或者等於6的記錄,如下所示:

在這種情況下,不論插入的記錄是5還是6,都會被鎖定。因為在next-key lock演算法下,鎖定的是(-∞,6)這個數值區間的所有數值。但是插入9這個數值是可以的,因為該記錄不在鎖定的範圍內,而對於單個值的索引查詢,不需要用到gap lock,只要加乙個record lock即可,因此innodb儲存引擎會自己選乙個最小的演算法模型。同樣,對於上面的表t,進行如下操作:

這時插入記錄5或6都是可行的了。需要注意的是,上面演示的兩個例子都是在innodb的預設配置下,即事務的隔離級別為repeatable read的模式下。因為在repeatable read模式下,next-key lock演算法是預設的行記錄鎖定演算法。

通過鎖可以實現事務的隔離性的要求,使得事務可以併發地工作。鎖提高了併發,但是卻會帶來問題。不過,好在因為事務隔離性的要求,鎖只會帶來3種問題。如果可以防止這3種情況的發生,那將不會產生併發異常。

丟失更新(lost update)是乙個經典的資料庫問題。實際上,所有多使用者計算機系統環境下有可能產生這個問題。簡單說來,出現下面的情況時,就會發生丟失更新:

(1)事務t1查詢一行資料,放入本地記憶體,並顯示給乙個終端使用者user1。

(2)事務t2也查詢該行資料,並將取得的資料顯示給終端使用者user2。

(3)user1修改這行記錄,更新資料庫並提交。

(4)user2修改這行記錄,更新資料庫並提交。

顯然,這個過程中使用者user1的修改更新操作「丟失」了。這可能會發生乙個恐怖的結果。設想銀行丟失了更新操作:乙個使用者賬戶中有10 000元人民幣,他用兩個網上銀行的客戶端轉賬,第一次轉9 000人民幣,因為網路和資料的關係,這時需要等待。但是如果這時使用者可以操作另乙個網上銀行客戶端,轉賬1元。如果最終兩筆操作都成功了,使用者的賬號餘款是9 999人民幣,第一轉的9 000人民幣並沒有得到更新。也許有人會說,不對,我的網銀是繫結usb key的,不會發生這種情況——通過usb key登入也許可以解決這個問題,但是更重要的是,要在資料庫層解決這個問題,以避免任何可能發生丟失更新的情況。

要避免丟失更新發生,其實需要讓這種情況下的事務變成序列操作,而不是併發的操作。即在上述四種的第(1)種情況下,對使用者讀取的記錄加上乙個排他鎖,同樣,發生第(2)種情況下的操作時,使用者也需要加乙個排他鎖。這種情況下,第(2)步就必須等待第(1)、(3)步完成,最後完成第(4)步,如以下所示:

我發現,程式設計師可能在了解如何使用select、insert、update、delete語句後就開始編寫應用程式。因此,丟失更新是程式設計師最容易犯的錯誤,也是最不易發現的乙個錯誤,特別是由於這種現象只是隨機的、零星的出現,但其可能造成的後果卻十分嚴重。

理解髒讀之前,需要理解髒資料的概念。髒資料和髒頁有所不同。髒頁指的是在緩衝池中已經被修改的頁,但是還沒有重新整理到磁碟,即資料庫例項記憶體中的頁和磁碟的頁中的資料是不一致的,當然在重新整理到磁碟之前,日誌都已經被寫入了重做日誌檔案中。而所謂髒資料,是指在緩衝池中被修改的資料,並且還沒有被提交(commit)。

對於髒頁的讀取,是非常正常的。髒頁是因為資料庫例項記憶體和磁碟的非同步同步造成的,這並不影響資料的一致性。並且因為是非同步的,因此可以帶來效能的提高。而髒資料卻不同,髒資料是指未提交的資料。如果讀到了髒資料,即乙個事務可以讀到另外乙個事務中未提交的資料,則顯然違反了資料庫的隔離性。

髒讀指的就是在不同的事務下,可以讀到另外事務未提交的資料,簡單來說,就是可以讀到髒資料。比如下面的例子所示:

事務的隔離級別進行了更換,由預設的repeatable read換成了read uncommitted,因此在會話a中事務並沒有提交的前提下,會話b中兩次select操作取得了不同的結果,並且這兩個記錄是在會話a中並未提交的資料,即產生了髒讀,違反了事務的隔離性。

髒讀現象在生產環境中並不常發生。從上面的例子中就可以發現,髒讀發生的條件是需要事務的隔離級別為read uncommitted,而目前絕大部分的資料庫都至少設定成read committed。innodb儲存引擎預設的事務隔離級別為read repeatable,microsoft sql server資料庫為read committed,oracle資料庫同樣也是read committed。

不可重複讀是指在乙個事務內多次讀同一資料。在這個事務還沒有結束時,另外乙個事務也訪問該同一資料。那麼,在第乙個事務的兩次讀資料之間,由於第二個事務的修改,第乙個事務兩次讀到的資料可能是不一樣的。這樣就發生了在乙個事務內兩次讀到的資料是不一樣的,因此稱為不可重複讀。

不可重複讀和髒讀的區別是:髒讀是讀到未提交的資料;而不可重複讀讀到的確實是已經提交的資料,但是其違反了資料庫事務一致性的要求。

可以通過下面乙個例子來觀察不可重複讀的情況:

會話a中開始乙個事務,第一次讀取到的記錄是1;另乙個會話b中開始了另乙個事務,插入一條2的記錄。在沒有提交之前,會話a中的事務再次讀取時,讀到的記錄還是1,沒有發生髒讀的現象。但會話2中的事務提交後,在對會話a中的事務進行讀取時,這時讀到的是1和2兩條記錄。這個例子的前提是,在事務開始前,會話a和會話b的事務隔離級別都調整為了read committed。

一般來說,不可重複讀的問題是可以接受的,因為其讀到的是已經提交的資料,本身並不會帶來很大的問題。因此,很多資料庫廠商(如oracle、microsoft sql server)將其資料庫事務的預設隔離級別設定為read committed,在這種隔離級別下允許不可重複讀的現象。

innodb儲存引擎中,通過使用next-key lock演算法來避免不可重複讀的問題。在mysql官方文件中,將不可重複讀定義為phantom problem,即幻象問題。在next-key lock演算法下,對於索引的掃瞄,不僅僅是鎖住掃瞄到的索引,而且還鎖住這些索引覆蓋的範圍(gap)。因此對於這個範圍內的插入都是不允許的。這樣就避免了另外的事務在這個範圍內插入資料導致的不可重複讀的問題。因此,innodb儲存引擎的預設事務隔離級別是read repeatable,採用next-key lock演算法,就避免了不可重複讀的現象。

鎖的演算法,隔離級別的問題

innodb儲存引擎有3中行鎖的演算法設計,分別是 record lock 單個行記錄上的鎖。gap lock 間隙鎖,鎖定乙個範圍,但不包含記錄本身。next key lock gap lock record lock,鎖定乙個範圍,並且鎖定記錄本身。record lock總是會去鎖住索引記錄。如...

事務隔離級別導致鎖級別的不同

mysql支援4種事務隔離級別,他們分別是 read uncommitted,read committed,repeatable read,serializable.如沒有指定,mysql預設採用的是repeatable read oracle預設的是read committed mysql在rep...

SQLserver鎖和事務隔離級別的比較與使用

來自 物件 鎖 每條sql 語句 隔離 事務 鎖 併發問題 丟失更新 未確認的讀取 髒讀 不一致的分析 非重複讀 多次讀取相同的資料 行 不一致 其他使用者更改update 幻像讀 多次讀取有不存在和新增的資料 其他使用者插入insert 或刪除delete 隔離級別 隔離級別 髒讀 不可重複讀取 ...