為什麼需要鎖(併發控制)
在併發的環境中,會存在多個使用者同時更新同一條資料,這時就會產生衝突。
衝突結果:
因此為了解決上述問題,引入了併發控制機制。
樂觀鎖(樂觀併發控制)和悲觀鎖(悲觀併發控制)是併發控制的主要手段, 其實不僅關係型資料庫中有樂觀鎖和悲觀鎖的概念,像redis,memcached等都有類似的概念。所以,不要把樂觀鎖和悲觀鎖狹隘的理解為dbms中的概念,更不能把他們和資料庫中提供的鎖機制混為一談。其實,在dbms中悲觀鎖正是利用了資料庫提供的鎖機制實現的。
因此我們針對不同的業務場景,應該選擇不同的併發控制方式。
悲觀鎖指的是外界對資料修改持保守態度(悲觀),因此,在整個資料處理過程中,將資料處於鎖定狀態。 悲觀鎖的實現,往往依靠資料庫提供的鎖機制 (也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改資料)。
dbms悲觀鎖是阻止乙個事務以影響其他使用者的方式來修改資料。如果乙個事務執行的操作 某行資料應用了鎖,那只有當這個事務把鎖釋放,其他事務才能夠執行與該鎖衝突的操作。
悲觀併發控制主要用於資料爭用激烈的環境,以及發生併發衝突時使用鎖保護資料的成本要低於回滾事務的成本的環境中。
資料庫中,悲觀鎖的流程:
對任意記錄進行修改前,先嘗試為該記錄加上排他鎖(exclusive locking)。如果加鎖失敗,說明該記錄正在被修改,那麼當前查詢可能要等待或者丟擲異常。 具體響應方式由開發者根據實際需要決定。 如果成功加鎖,那麼就可以對記錄做修改,事務完成後就會釋放鎖了。 其間如果有其他對該記錄做修改或加排他鎖的操作,都會等待我們解鎖或直接丟擲異常。
使用悲觀鎖
要使用悲觀鎖,我們必須關閉mysql資料庫的自動提交屬性(如果只是涉及到一條update sql語句等比較簡單的情況可以不用關閉自動提交),因為mysql預設使用auto commit模式,也就是說,當你執行乙個更新操作後,mysql會立刻將結果進行提交。set autocommit=0;
//0.開始事務
begin;/begin work;/start transaction; (三者選一就可以)
//1.查詢出商品資訊
select status from t_goods where id=1 for update;
//2.根據商品資訊生成訂單
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status為2
update t_goods set status=2;
//4.提交事務
上面的查詢語句中,我們使用了select…for update的方式, 這樣就通過開啟排他鎖的方式實現了悲觀鎖。此時在t_goods表中,id為1的 那條資料就被我們鎖定了,其它的事務必須等本次事務提交之後才能執行。這樣我們可以保證當前的資料不會被其它事務修改。
注意點
我們需要注意一些鎖的級別,mysql innodb預設行級鎖, 行級鎖都是基於索引的,如果一條sql語句用不到索引是不會使用行級鎖的,會使用表級鎖把整張表鎖住。另外,如果在索引上範圍查詢如 in 也會導致表鎖。上面我們使用select…for update會把資料給鎖住(id上有主鍵索引),因此不會使用表級鎖。
如果查詢語句中有表關聯,只是用for update會鎖住所有表的相關記錄,建議使用for update of t.id。如果你不想讓你的事務等待其他事務的鎖,可以在for update 後加上 nowait,這樣當遇上衝突時資料庫會丟擲異常。
優勢劣勢
悲觀併發控制實際上是"先取鎖再訪問"的保守策略,為資料處理的安全提供了保證。但是在效率方面,處理加鎖的機制會讓資料庫產生額外的開銷,還有增加產生死鎖的機會;另外,在唯讀型事務處理中由於不會產生衝突,也沒必要使用鎖,這樣做只能增加系統負載;還有會降低了並行性,乙個事務如果鎖定了某行資料,其他事務就必須等待該事務處理完才可以處理那行數。
相對悲觀鎖而言,樂觀鎖假設認為資料一般情況下不會造成衝突,所以在資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測,如果發現衝突了,則讓返回使用者錯誤的資訊,讓使用者決定如何去做。
相對於悲觀鎖,在對資料庫進行處理的時候,樂觀鎖並不會使用資料庫提供的鎖機制。一般的實現樂觀鎖的方式就是記錄資料版本。
實現資料版本有兩種方式:
第一種是使用版本號version,
第二種是使用時間戳timestamp(時間戳精度)。
資料版本,為資料增加的乙個版本標識。當讀取資料時,將版本標識的值一同讀出,資料每更新一次,同時對版本標識進行更新。當我們提交更新的時候,判斷資料庫表對應記錄的當前版本資訊與第一次取出來的版本標識進行比對,如果資料庫表當前版本號與第一次取出來的版本標識值相等,則予以更新,否則認為是過期資料。
dbms會假設多使用者併發的事務在處理時不會彼此互相影響,各事務能夠在不產生鎖的情況下處理各自影響的那部分資料。在提交資料更新之前,每個事務會先檢查在該事務讀取資料後,有沒有其他事務又修改了該資料(修改該資料的事務已提交,或未提交;事務未提交會帶來資料丟失)。如果其他事務有更新的話,正在提交的事務會進行回滾。
注:對於以上兩種方式,hibernate自帶實現方式:在使用樂觀鎖的字段前加annotation: @version, hibernate在更新時自動校驗該欄位。
使用版本號實現樂觀鎖
使用版本號時,可以在資料初始化時指定乙個版本號,每次對資料的更新操作都對版本號執行+1操作。並判斷當前版本號是不是該資料的最新的版本號。
//1.查詢出商品資訊
select status,version from t_goods where id=#
//2.根據商品資訊生成訂單
//3.修改商品status為2
update t_goods
set status=2,version=version+1
where id=# and version=#;
注意第二個事務執行update時,第乙個事務已經提交了,所以第二個事務能夠讀取到第乙個事務修改的version。
下面這種極端的情況:
我們知道mysql資料庫引擎innodb,事務的隔離級別是repeatable read,因此是不會出現髒讀、不可重複讀。
在這種極端情況下,第二個事務的update由於不能讀取第乙個事務未提交的資料(第乙個事務已經對這一條資料加了排他鎖,第二個事務需要等待獲取鎖),第二個事務獲取了排他鎖後,會發現version已經發生了改變從而提交失敗。
優勢劣勢
樂觀併發控制相信事務之間的資料競爭(datarace)的概率是比較小的, 因此盡可能直接做下去,直到提交的時候才去鎖定,所以不會產生任何鎖和死鎖。
樂觀鎖雖然沒有依賴資料庫提供的鎖機制,也可以保證資料一致性。
悲觀鎖 VS 樂觀鎖
1 資料庫的隔離級別 1.read uncommited 未提交讀 沒有提交就可以讀取到資料 發出了insert,但沒有commit就可以讀取到 2.read commited 提交讀 只有提交後才可以讀 3.repeatable read 可重複讀 mysql預設級別,必須提交才能看到,讀取資料是...
悲觀鎖 vs 樂觀鎖 vs Redis
企業面對高併發場景採用的方案.比如 產品搶購高併發時的超發現象.1 悲觀鎖 悲觀鎖 需要資料庫本身提供支援 oracle和mysql都是支援的 實現細節 當前 資料庫事務 讀取到產品後,就將目標資料直接鎖定 select for update 不允許別的執行緒進行讀寫操作,知道 當前資料庫事務完成自...
悲觀鎖樂觀鎖
1 悲觀鎖,正如其名,它指的是對資料被外界 包括本系統當前的其他事務,以及來自外部系統的事務處理 修改持保守態度,因此,在整個資料處理過程中,將資料處於鎖定狀態。悲觀鎖的實現,往往依靠資料庫提供的鎖機制 也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無...