數據庫鎖

數據庫鎖一般可以分爲兩類,一個是悲觀鎖,一個是樂觀鎖。

數據庫鎖
樂觀鎖一般是指用戶自己實現的一種鎖機制,假設認爲數據一般情況下不會造成衝突,所以在數據進行提交更新的時候,纔會正式對數據的衝突與否進行檢測,如果發現衝突了,則讓返回用戶錯誤的信息,讓用戶決定如何去做。樂觀鎖的實現方式一般包括使用版本號和時間戳。

悲觀鎖一般就是我們通常說的數據庫鎖機制,以下討論都是基於悲觀鎖。

悲觀鎖主要表鎖、行鎖、頁鎖。在MyISAM中只用到表鎖,不會有死鎖的問題,鎖的開銷也很小,但是相應的併發能力很差。innodb實現了行級鎖和表鎖,鎖的粒度變小了,併發能力變強,但是相應的鎖的開銷變大,很有可能出現死鎖。同時inodb需要協調這兩種鎖,算法也變得複雜。InnoDB行鎖是通過給索引上的索引項加鎖來實現的,只有通過索引條件檢索數據,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖。

表鎖和行鎖都分爲共享鎖和排他鎖(獨佔鎖),而更新鎖是爲了解決行鎖升級(共享鎖升級爲獨佔鎖)的死鎖問題。

innodb中表鎖和行鎖一起用,所以爲了提高效率纔會有意向鎖(意向共享鎖和意向排他鎖)。

在mysql中有表鎖,讀鎖鎖表,會阻塞其他事務修改表數據。寫鎖鎖表,會阻塞其他事務讀和寫。
Innodb引擎又支持行鎖,行鎖分爲共享鎖,一個事務對一行的共享只讀鎖。排它鎖,一個事務對一行的排他讀寫鎖。
這兩中類型的鎖共存的問題考慮這個例子:事務A鎖住了表中的一行,讓這一行只能讀,不能寫。之後,事務B申請整個表的寫鎖。如果事務B申請成功,那麼理論上它就能修改表中的任意一行,這與A持有的行鎖是衝突的。數據庫需要避免這種衝突,就是說要讓B的申請被阻塞,直到A釋放了行鎖。
數據庫要怎麼判斷這個衝突呢?

step1:判斷表是否已被其他事務用表鎖鎖表
step2:判斷表中的每一行是否已被行鎖鎖住。
注意step2,這樣的判斷方法效率實在不高,因爲需要遍歷整個表。於是就有了意向鎖。在意向鎖存在的情況下,事務A必須先申請表的意向共享鎖,成功後再申請一行的行鎖。

在意向鎖存在的情況下,上面的判斷可以改成

step1:不變
step2:發現表上有意向共享鎖,說明表中有些行被共享行鎖鎖住了,因此,事務B申請表的寫鎖會被阻塞。
注意:申請意向鎖的動作是數據庫完成的,就是說,事務A申請一行的行鎖的時候,數據庫會自動先開始申請表的意向鎖,不需要我們程序員使用代碼來申請。

行鎖的細分
共享鎖
加鎖與解鎖:當一個事務執行select語句時,數據庫系統會爲這個事務分配一把共享鎖,來鎖定被查詢的數據。在默認情況下,數據被讀取後,數據庫系統立即解除共享鎖。例如,當一個事務執行查詢“SELECT * FROM accounts”語句時,數據庫系統首先鎖定第一行,讀取之後,解除對第一行的鎖定,然後鎖定第二行。這樣,在一個事務讀操作過程中,允許其他事務同時更新accounts表中未鎖定的行。

兼容性:如果數據資源上放置了共享鎖,還能再放置共享鎖和更新鎖。

併發性能:具有良好的併發性能,當數據被放置共享鎖後,還可以再放置共享鎖或更新鎖。所以併發性能很好。

排他鎖
加鎖與解鎖:當一個事務執行insert、update或delete語句時,數據庫系統會自動對SQL語句操縱的數據資源使用獨佔鎖。如果該數據資源已經有其他鎖(任何鎖)存在時,就無法對其再放置獨佔鎖了。

兼容性:獨佔鎖不能和其他鎖兼容,如果數據資源上已經加了獨佔鎖,就不能再放置其他的鎖了。同樣,如果數據資源上已經放置了其他鎖,那麼也就不能再放置獨佔鎖了。

併發性能:最差。只允許一個事務訪問鎖定的數據,如果其他事務也需要訪問該數據,就必須等待。

更新鎖
更新鎖在的初始化階段用來鎖定可能要被修改的資源,這可以避免使用共享鎖造成的死鎖現象。例如,對於以下的update語句:

UPDATE accounts SET balance=900 WHERE id=1

更新操作需要分兩步:讀取accounts表中id爲1的記錄 –> 執行更新操作。

如果在第一步使用共享鎖,再第二步把鎖升級爲獨佔鎖,就可能出現死鎖現象。例如:兩個事務都獲取了同一數據資源的共享鎖,然後都要把鎖升級爲獨佔鎖,但需要等待另一個事務解除共享鎖才能升級爲獨佔鎖,這就造成了死鎖。

更新鎖有如下特徵:

加鎖與解鎖:當一個事務執行update語句時,數據庫系統會先爲事務分配一把更新鎖。當讀取數據完畢,執行更新操作時,會把更新鎖升級爲獨佔鎖。

兼容性:更新鎖與共享鎖是兼容的,也就是說,一個資源可以同時放置更新鎖和共享鎖,但是最多放置一把更新鎖。這樣,當多個事務更新相同的數據時,只有一個事務能獲得更新鎖,然後再把更新鎖升級爲獨佔鎖,其他事務必須等到前一個事務結束後,才能獲取得更新鎖,這就避免了死鎖。

併發性能:允許多個事務同時讀鎖定的資源,但不允許其他事務修改它。

數據庫隔離級別
瞭解了數據的鎖機制,數據庫的隔離級別也就好理解多了。每一種隔離級別滿足不同的數據要求,使用不同程度的鎖。

Read Uncommitted,讀寫均不使用鎖,數據的一致性最差,也會出現許多邏輯錯誤。

Read Committed,使用寫鎖,但是讀會出現不一致,不可重複讀。

Repeatable Read, 使用讀鎖和寫鎖,解決不可重複讀的問題,但會有幻讀。

Serializable, 使用事務串形化調度,避免出現因爲插入數據沒法加鎖導致的不一致的情況。

讀不提交,造成髒讀(Read Uncommitted)
一個事務中的讀操作可能讀到另一個事務中未提交修改的數據,如果事務發生回滾就可能造成錯誤。

例子:A打100塊給B,B看賬戶,這是兩個操作,針對同一個數據庫,兩個事物,如果B讀到了A事務中的100塊,認爲錢打過來了,但是A的事務最後回滾了,造成損失。

避免這些事情的發生就需要我們在寫操作的時候加鎖,使讀寫分離,保證讀數據的時候,數據不被修改,寫數據的時候,數據不被讀取。從而保證寫的同時不能被另個事務寫和讀。

讀提交(Read Committed)
我們加了寫鎖,就可以保證不出現髒讀,也就是保證讀的都是提交之後的數據,但是會造成不可重讀,即讀的時候不加鎖,一個讀的事務過程中,如果讀取數據兩次,在兩次之間有寫事務修改了數據,將會導致兩次讀取的結果不一致,從而導致邏輯錯誤。

可重讀(Repeatable Read)
解決不可重複讀問題,一個事務中如果有多次讀取操作,讀取結果需要一致(指的是固定一條數據的一致,幻讀指的是查詢出的數量不一致)。 這就牽涉到事務中是否加讀鎖,並且讀操作加鎖後是否在事務commit之前持有鎖的問題,如果不加讀鎖,必然出現不可重複讀,如果加鎖讀完立即釋放,不持有,那麼就可能在其他事務中被修改,若其他事務已經執行完成,此時該事務中再次讀取就會出現不可重複讀,

所以讀鎖在事務中持有可以保證不出現不可重複讀,寫的時候必須加鎖且持有,這是必須的了,不然就會出現髒讀。Repeatable Read(可重讀)也是MySql的默認事務隔離級別,上面的意思是讀的時候需要加鎖並且保持

可串行化(Serializable)
解決幻讀問題,在同一個事務中,同一個查詢多次返回的結果不一致。事務A新增了一條記錄,事務B在事務A提交前後各執行了一次查詢操作,發現後一次比前一次多了一條記錄。幻讀是由於併發事務增加記錄導致的,這個不能像不可重複讀通過記錄加鎖解決,因爲對於新增的記錄根本無法加鎖。需要將事務串行化,才能避免幻讀。
這是最高的隔離級別,它通過強制事務排序,使之不可能相互衝突,從而解決幻讀問題。簡言之,它是在每個讀的數據行上加上共享鎖。在這個級別,可能導致大量的超時現象和鎖競爭。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章