悲觀鎖
當我們要對資料庫中的一條資料進行修改的時候,為了避免同時被其他人修改,最好的辦法就是直接對該資料進行加鎖以防止併發。這種藉助資料庫鎖機制在修改資料之前鎖定,再修改的方式被稱為悲觀併發控制(PCC)。
之所以叫做悲觀鎖,是因為抱有悲觀的態度去修改資料的併發控制方式,認為資料併發修改的概率比較大,所以需要在修改之前先加鎖。
悲觀併發控制實際上是“先取鎖,再訪問”的保守策略,為資料處理的安全提供了保證。
在效率上,處理加鎖的機制會讓資料庫產生額外的開銷,還會有死鎖的可能性。降低並行性,一個事務如果鎖定了某行資料,其他事務就必須等待該事務處理完才可以處理那行資料。
悲觀鎖的實現方式:悲觀鎖的實現,依靠資料庫提供的鎖機制。在資料庫中,悲觀鎖的流程如下:
- 在對資料修改前,嘗試增加排他鎖。
- 加鎖失敗,意味著資料正在被修改,進行等待或者丟擲異常。
- 加鎖成功,對資料進行修改,提交事務,鎖釋放。
- 如果我們加鎖成功,有其他執行緒對該資料進行操作或者加排他鎖的操作,只能等待或者丟擲異常。
樂觀鎖
樂觀鎖是相對悲觀鎖而言的,樂觀鎖假設資料一般情況下不會造成衝突,所以在資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測。
相對於悲觀鎖,在資料庫進行處理的時候,樂觀鎖不會使用資料庫提供的鎖機制,一般是增加 version 引數,記錄資料版本
樂觀併發控制相信事務之間的資料競爭概率非常小,因此儘可能直接操作,提交的時候才去鎖定,不會產生任何鎖和死鎖。
上手試一試
基於 MySQL InnoDB 引擎
使用悲觀鎖
begin;
select quantity from products where id = 1 for update;
update products set quanntity = 2 where id = 1;
commit;
以上,對 id 為 1 的產品進行修改,先通過 for update 的方式進行加鎖,然後再修改。典型的悲觀鎖策略。
如果修改庫存的邏輯發生併發,同一時間只有一個執行緒可以開啟事務並獲得 id = 1 的鎖,其他事務必須等本次提交之後才能執行,這樣可以保證資料不被其他事務修改。
使用排他鎖會把資料鎖住,不過需要注意一些基本的鎖級別,MySQL InnoDB 預設行級鎖。行級鎖是基於索引的,如果一條 SQL 語句用不到索引是不會使用行級鎖,會使用表級鎖把整張表鎖住。
使用樂觀鎖
select quantity from products where id = 1
update products set quantity = 2 where id = 1 and quantity = 3
先查詢庫存表當前庫存數,然後更新的時候判斷資料表對應資料的 quantity 與第一次取出來的是否一致,一致則更新,否則認為是過期資料。
這樣實現有一個問題,執行緒 1 從資料庫取出 quantity 為 3,執行緒 2 也取出同一條資料的 quantity,進行操作,變成了 2,然後又進行某些操作 變成了 3,此時執行緒 1 進行更新操作成功。但是這個過程有問題。
引入 version 引數,樂觀鎖每次在執行資料修改的操作,都會帶上版本號,一旦版本號和資料的版本號一致就可以執行修改操作並對 version 執行 +1 操作,否則就執行失敗。
這樣實現也有一個問題,如果真的有高併發的時候,就只有一個執行緒可以修改成功,就會存在大量的失敗。
如果你的應用存在超高併發,這樣解決也不好,因為會總讓使用者感知到失敗。
嘗試減小樂觀鎖力度,最大程度提高吞吐。
update products set quantity = quantity - 1 where id = 1 and quantity - 1 > 0
使用這條 SQL 語句,在執行過程中,會在一次原子操作中查詢一遍 quantity 的值,並且減去 1。
簡述區別
- 樂觀鎖不是真的加鎖,效率高,但是要控制好鎖的力度。
- 悲觀鎖依賴資料庫鎖,效率低。
總結
無論是悲觀鎖還是樂觀鎖,都是人們定義出來的概念,可以認為是一種思想。
大家要記住鎖機制一定要在事務中才能生效哦。
以上是我對樂觀鎖與悲觀鎖一點基礎實踐,希望能和大家再深入瞭解瞭解。