數據庫鎖和數據庫隔離級別
最近突然發現忘了數據庫鎖和數據庫隔離級別,時常弄混它們之間的關系。為此特此寫下此博客,以方便自己復習,同時也可以幫助博友。
數據庫鎖
數據庫鎖就是事務T在對某個數據對象(例如表、記錄等)操作之前,先向系統發出請求,對其加鎖。加鎖后事務T就對該數據對象有了一定的控制,在事務T釋放它的鎖之前,其它的事務不能更新此數據對象。
數據庫鎖是實現并發控制的重要技術,但是“鎖”會帶來系統額外的開銷。所以需要注意選擇封鎖粒度時必須同時考慮開銷和并發度兩個因素,進行權衡,以求得最優的效果。
鎖的類型主要有兩種類型排它鎖(也叫獨占鎖)和共享鎖,當然還有很多教程增加了一種--更新鎖。
共享(S)鎖:多個事務可封鎖一個共享頁;任何事務都不能修改該頁; 通常是該頁被讀取完畢,S鎖立即被釋放。在執行select語句的時候需要給操作對象(表或者一些記錄)加上共享鎖,但加鎖之前需要檢查是否有排他鎖,如果沒有,則可以加共享鎖(一個對象上可以加n個共享鎖),否則不行。共享鎖通常在執行完select語句之后被釋放,當然也有可能是在事務結束(包括正常結束和異常結束)的時候被釋放,主要取決與數據庫所設置的事務隔離級別。
排它(X)鎖:僅允許一個事務封鎖此頁;其他任何事務必須等到X鎖被釋放才能對該頁進行訪問;X鎖一直到事務結束才能被釋放。執行insert、update、delete語句的時候需要給操作的對象加排他鎖(我感覺在執行insert的時候應該是在表級加排他鎖),在加排他鎖之前必須確認該對象上沒有其他任何鎖,一旦加上排他鎖之后,就不能再給這個對象加其他任何鎖。排他鎖的釋放通常是在事務結束的時候(當然也有例外,就是在數據庫事務隔離級別被設置成Read Uncommitted(讀未提交數據)的時候,這種情況下排他鎖會在執行完更新操作之后就釋放,而不是在事務結束的時候)。
更新(U)鎖:用來預定要對此頁施加X鎖,它允許其他事務讀,但不允許再施加U鎖或X鎖;當被讀取的頁將要被更新時,則升級為X鎖;U鎖一直到事務結束時才能被釋放。數據庫是支持在一個事務中進行自動鎖升級的,例如,在某個事務中先執行select語句,后執行update語句,這兩條語句操作了同一個對象,并且假定共享鎖是在事務結束的時候被釋放的。如果數據庫不支持自動鎖升級,那么當update語句請求排他鎖的時候將不能成功。因為之前select語句的共享鎖沒有被釋放,那么事務就進入了無限等待,即死鎖。有了自動鎖升級,在執行update語句的時候就可以將之前加的共享鎖升級為排他鎖,但有個前提,就是這個共享鎖必須是本事務自己加的,而且在操作對象上沒有在加其他任何鎖,否則共享鎖是不能被升級為排他鎖的,必須等待其他鎖的釋放。
因為通常在執行更新操作的時候要先查詢,也就是我們通常會在update語句和delete語句中加where子句。那么,有的數據庫系統可能會在執行查詢的時候先給操作對象加共享鎖,然后在更新的時候加排他鎖,但這么做會有問題,也就是如果兩個事務同時要更新一個對象,都先給這個對象加了共享鎖,當要更新的時候,都請求升級鎖,但由于這個對象上存在對方事務加的共享鎖。。所以無法升級。這樣兩個事務就在等待對方釋放共享鎖,進入死鎖狀態。更新鎖就是為了解決這個問題,即在執行查詢操作的時候加的不是共享鎖而是更新鎖(一個對象上只能有一個更新鎖和n個共享鎖),當要更新的時候,再將更新鎖升級為排他鎖,升級前提是這個對象上只有本事務加的更新鎖,沒有其他任何鎖了。其實,,我想,如果在執行查詢的時候就給事務加排他鎖不也能解決死鎖問題嗎,但這樣似乎會減弱系統的并發性能。
數據庫的事務隔離級別
在數據庫操作中,為了有效保證并發讀取數據的正確性,提出的事務隔離級別。
在沒有數據庫的事務隔離級別時會出現如下問題:
更新丟失
兩個事務都同時更新一行數據,但是第二個事務卻中途失敗退出,導致對數據的兩個修改都失效了。這是因為系統沒有執行任何的鎖操作,因此并發事務并沒有被隔離開來。
臟讀
一個事務開始讀取了某行數據,但是另外一個事務已經更新了此數據但沒有能夠及時提交。這是相當危險的,因為很可能所有的操作都被回滾。
不可重復讀
不可重復讀(Non-repeatable Reads):一個事務對同一行數據重復讀取兩次,但是卻得到了不同的結果。
包括以下情況:
(1) 事務T1讀取某一數據后,事務T2對其做了修改,當事務T1再次讀該數據時得到與前一次不同的值。
(2) 幻讀(Phantom Reads):事務在操作過程中進行兩次查詢,第二次查詢的結果包含了第一次查詢中未出現的數據或者缺少了第一次查詢中出現的數據(這里并不要求兩次查詢的SQL語句相同)。這是因為在兩次查詢過程中有另外一個事務插入數據造成的。
為了避免上面出現的幾種情況,在標準SQL規范中,定義了4個事務隔離級別,不同的隔離級別對事務的處理不同。
未授權讀取
也稱為讀未提交(Read Uncommitted):允許臟讀取,但不允許更新丟失。如果一個事務已經開始寫數據,則另外一個事務則不允許同時進行寫操作,但允許其他事務讀此行數據。該隔離級別可以通過“排他寫鎖”實現。
授權讀取
也稱為讀提交(Read Committed):允許不可重復讀取,但不允許臟讀取。這可以通過“瞬間共享讀鎖”和“排他寫鎖”實現。讀取數據的事務允許其他事務繼續訪問該行數據,但是未提交的寫事務將會禁止其他事務訪問該行。
可重復讀取(Repeatable Read)
可重復讀取(Repeatable Read):禁止不可重復讀取和臟讀取,但是有時可能出現幻影數據。這可以通過“共享讀鎖”和“排他寫鎖”實現。讀取數據的事務將會禁止寫事務(但允許讀事務),寫事務則禁止任何其他事務。
序列化(Serializable)
序列化(Serializable):提供嚴格的事務隔離。它要求事務序列化執行,事務只能一個接著一個地執行,但不能并發執行。如果僅僅通過“行級鎖”是無法實現事務序列化的,必須通過其他機制保證新插入的數據不會被剛執行查詢操作的事務訪問到。
隔離級別越高,越能保證數據的完整性和一致性,但是對并發性能的影響也越大。對于多數應用程序,可以優先考慮把數據庫系統的隔離級別設為Read Committed。它能夠避免臟讀取,而且具有較好的并發性能。盡管它會導致不可重復讀、虛讀和第二類丟失更新這些并發問題,在可能出現這類問題的個別場合,可以由應用程序采用悲觀鎖或樂觀鎖來控制。
posted on 2014-08-12 09:36 順其自然EVO 閱讀(224) 評論(0) 編輯 收藏 所屬分類: 數據庫