數據庫的資源是有限的,一行數據在同一個時間點只能被同一類型的任務去更新。如果並發執行了,必然會導致數據庫數據和預期執行的不一致。為了防止這種不一致性。我們就有了樂觀鎖和悲觀鎖這兩種處理並發的鎖機制。
悲觀鎖:悲觀鎖認為並發是每時每刻都在發生的。因此為了防止並發,我們在update數據庫的數據行之前,需要先把這行數據先鎖定起來。其他的任務如果想要update當前的數據行,需要等待當前的任務完成,亦或是選擇放棄等待,給外界提示。通過這樣的方式。悲觀鎖實現了更新數據行的串行化,即每個更新語句之間是串聯的執行的。悲觀鎖由數據庫提供支持,oracle mysql均提供 select for update 這種語句,它對查詢出來的行進行加鎖。這種加鎖的方式,第一個加鎖成功后,后面任務會一直嘗試加鎖到加上為止。oracle 還提供了select for update nowait 語句,它會嘗試加鎖,一旦加鎖失敗就會立即返回加鎖失敗。悲觀鎖的優點是更新的方式簡單,缺點是更新的速度變慢了。
樂觀鎖:樂觀鎖認為並發並不是每時每刻都在發生。有可能會發生,但是大概率不會發生。樂觀鎖是通過在db行上加上版本的方式來實現的。每次更新之前,都查詢出來要更新行的版本值是多少,然后在更新的時候,更新的條件上要帶上查詢出來的版本,更新的內容需要把版本值加1.通過這種行為模式,如果更新的返回值是1,代表在更新的這一刻是沒有並發,如果更新的返回值是0,代表着數據行被別人更新過了,程序需要做另外的動作。樂觀鎖的優點是更新數據的速度提高了,缺點是一旦並發發生的概率大了,程序需要處理更新失敗的情況。
舉例說明 悲觀鎖和樂觀鎖的適應場景。
eg1:用戶表的數據里面有個收貨人信息數據,用戶可以通過多端進行修改,這種場景就符合我們說的並發並不是每時每刻都在發生的。用戶可以通過各個客戶端修改收貨人信息,但是一般的情況下,都是只有一個端去修改的。大概率是不會並發的修改的。假設收貨人信息表的結構是:
t_receiver_info
id
name 收貨人姓名
version 版本
那么修改之前的查詢是:
select * from t_receiver_info id=$id; 假設查詢出來的version=3,
執行update 語句: update t_receiver_info set name=new_name,version=version+1 where id=$id and version=3;
如果執行返回1,代表沒有並發。程序直接向上面返回成功,如果執行返回0,代表有並發,程序可以直接向上面返回更新失敗。
這個例子使用select for update也可以更新。這里就不舉例了。
