1. 程式人生 > >資料庫鎖、隔離級別

資料庫鎖、隔離級別

併發控制主要是為了多執行緒操作時帶來的資源讀寫問題。如果不加以空間可能會出現死鎖,讀髒資料、不可重複讀、丟失更新等異常。

併發操作可以通過加鎖的方式進行控制,鎖又可分為樂觀鎖和悲觀鎖。

    悲觀鎖(Pessimistic Locking)併發模式假定系統中存在足夠多的資料修改操作,以致於任何確定的讀操作都可能會受到由個別的使用者所製造的資料修改的影響。也就是說悲觀鎖假定衝突總會發生,通過獨佔正在被讀取的資料來避免衝突。但是獨佔資料會導致其他程序無法修改該資料,進而產生阻塞,讀資料和寫資料會相互阻塞。

   樂觀鎖(Optimistic Locking)假定系統的資料修改只會產生非常少的衝突,也就是說任何程序都不大可能修改別的程序正在訪問的資料。樂觀併發模式下,讀資料和寫資料之間不會發生衝突,只有寫資料與寫資料之間會發生衝突。即讀資料不會產生阻塞,只有寫資料才會產生阻塞。

資料庫隔離級別有四種,應用《高效能MySQL》一書中的說明:

READ UNCOMMITTED(讀取未提交內容)

在READ UNCOMMITTED隔離級,所有事務都可以“看到”未提交事務的執行結果。在這種級別上,可能會產生很多問題,除非使用者真的知道自己在做什麼,並有很好的理由選擇這樣做。本隔離級很少用於實際應用,因它的效能也不比其他級別好多少,而別的級別還有其他更多的優點。讀取未提交資料,也被稱之為“髒讀(Dirty Read)”。

READ COMMITTED(讀取提交內容)

大多數資料庫系統的預設隔離級別是 READ COMMITTED(但這不是MySQL預設的!)。它滿足了隔離的早先簡單定義:一個事務在開始時,只能“看見”已經提交事務所做的改變,一個事物從開始到提交前,所做的任何資料改變都是不可見的,除非已經提交。這種隔離級別也支援所謂的“不可重複讀”(Nonrepeatable Read)。這意味著使用者運行同一語句兩次,看到的結果是不同的。

REPEATABLE READ(可重讀)

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

REPEATABLE READ 是MySQL的預設事務隔離級別。InnoDB和Falcon儲存引擎也都遵循這種設定。

SERIALIZABLE(可序列化)

SERIALIZABLE是最高級別的隔離級,它通過強制事務排序,使之不可能相互衝突,從而解決幻讀問題。簡言之,SERIALIZABLE是在每個讀的資料行上加鎖。在這個級別,可能導致大量的超時(Timeout)現象和鎖競爭(Lock Contention)現象。一般很少看到有使用者選擇這種隔離級。但如果使用者的應用為了資料的穩定性,需要強制減少併發的話,也可以選擇這種隔離級。

MySQL修改事務隔離級別的方法:

1.全域性修改,修改mysql.ini配置檔案,在最後加上:

#可選引數有:READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE.

[mysqld]

transaction-isolation = REPEATABLE-READ

  這裡全域性預設是REPEATABLE-READ,其實MySQL本來預設也是這個級別。

2.對當前session修改,在登入mysql客戶端後,執行命令:

set session transaction isolation level read uncommitted;

    要記住mysql有一個autocommit引數,預設是on,他的作用是每一條單獨的查詢都是一個事務,並且自動開始,自動提交(執行完以後就自動結束了,如果你要適用select for update,而不手動呼叫 start transaction,這個for update的行鎖機制等於沒用,因為行鎖在自動提交後就釋放了),所以事務隔離級別和鎖機制即使你不顯式呼叫start transaction,這種機制在單獨的一條查詢語句中也是適用的,分析鎖的運作的時候一定要注意這一點。

鎖機制

共享鎖:由讀表操作加上的鎖,加鎖後其它使用者只能獲取該表或行的共享鎖,不能獲取排它鎖,也就是說只能讀不能寫。

排它鎖:由寫表操作加上的鎖,加鎖後其它使用者不能獲取該表或行的任何鎖,典型是mysql事務中

start transaction;

select * from user where userId = 1 for update;

執行完這句以後

(1)當其他事務想要獲取共享鎖,比如事務隔離級別為SERIALIZABLE的事務,執行

  select * from user;

  將會被掛起,因為SERIALIZABLE的select語句需要獲取共享鎖。

(2)當其他事務執行

  select * from user where userId = 1 for update;

  update user set userAge = 100 where userId = 1; 

  也會被掛起,因為for update會獲取這一行資料的排它鎖,需要等到前一個事務釋放該排它鎖才可以繼續進行。

鎖的範圍

行鎖: 對某行記錄加上鎖。

表鎖: 對整個表加上鎖。

這樣組合起來就有:行級共享鎖,表級共享鎖,行級排他鎖,表級排他鎖。

為什麼MySQL預設的隔離級別是repeatable read呢?

其實這是個歷史原因:在MySQL5.0時,binlog_format只支援Statement,如果設定隔離級別為read committed會導致主從不一致。在MySQL5.1之後binlog支援ROW模式,在該模式下使用read committed上述情況則不成立。

Oracle資料庫支援READ COMMITTED 和 SERIALIZABLE這兩種事務隔離級別,所以Oracle不支援髒讀。SQL標準所定義的預設事務隔離級別是SERIALIZABLE,但是Oracle 預設使用的是READ COMMITTED。

設定隔離級別使用

SET TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]