在關係型資料庫中,悲觀並發控制(悲觀鎖)和樂觀並發控制(樂觀鎖)是資源並發控制主要採用的解決方案。
無論是悲觀鎖還是樂觀鎖,都是人們定義出來的概念,可以認為是一種思想。其實不僅僅是關係型資料庫系統中有樂觀鎖和悲觀鎖的概念,像 memcache、hibernate、tair 等都有類似的概念。
針對於不同的業務場景,應該選用不同的並發控制方式。更不要把他們和資料中提供的鎖機制(行鎖、表鎖、排他鎖、共享鎖)混為一談。
下面來分別了解一下悲觀鎖和樂觀鎖,先定義一個商品表簡單表設計如下:
CREATE TABLE `products` (
`id` INT unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
`quantity` INT unsigned COMMENT '庫存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
悲觀鎖#
當我們要對一個資料庫中的一條資料進行修改的時候,為了避免同時被其他人修改,最好的辦法就是直接對該資料進行加鎖以防止並發。
這種借助資料庫鎖機制在修改資料之前先鎖定,再修改的方式被稱之為悲觀並發控制(又名 “悲觀鎖”,Pessimistic Concurrency Control,縮寫 “PCC”)。
這種設計採用了 “一鎖二查三更新” 模式,就是採用資料庫中自帶 select ... for update
關鍵字進行對當前事務添加行級鎖先將要操作的資料進行鎖上,之後執行對應查詢資料並執行更新操作。
BEGIN;
SELECT quantity FROM products WHERE id = 1 FOR UPDATE;
UPDATE products SET quantity = quantity - 1 WHERE id = 1;
COMMIT;
在 MySQL 資料庫中的是 InnoDB 預設是行級鎖。行級鎖都是基於索引的,如果一條 SQL 語句沒有命中索引是無法使用行級鎖的,會把整張表鎖住。所以在
select ... for update
中盡量縮小查詢範圍並命中索引。
悲觀並發控制主要用於資料競爭激烈的環境,以及發生並發衝突時使用鎖保護資料的成本要低於回滾事務的成本的環境中。
樂觀鎖#
樂觀鎖( Optimistic Concurrency Control,縮寫 “OCC” ) 相對悲觀鎖而言,樂觀鎖假設認為資料一般情況下不會造成衝突,所以在資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測,如果發現衝突了,則讓返回用戶錯誤的信息,讓用戶決定如何去做。
樂觀鎖並不會使用資料庫提供的鎖機制。一般的實現樂觀鎖的方式就是記錄資料版本。資料版本可以通過版本號或者時間戳來實現。
SELECT quantity, version FROM products WHERE id = 1;
UPDATE products SET quantity = quantity - 1, version = version + 1
WHERE id = 1 AND version = 原版本號;
使用版本號時,可以在資料初始化時指定一個版本號,每次對資料的更新操作都對版本號執行 + 1 操作。並判斷當前版本號是不是該資料的最新的版本號。
但是使用版本號對於並發高網站來說,只有一個線程可以成功修改資料,會出現很多業務報錯,對用戶來說很不友好。
所以我們可以根據業務情況來減小樂觀鎖力度,最大程度的提升吞吐率,提高並發能力。比如下單商品業務只需要商品有庫存就能下單.
UPDATE products SET quantity = quantity - 1 WHERE id = 1 AND quantity - 1 > 0;
樂觀並發控制多數用於資料競爭較少的環境,這種環境中,偶爾回滾事務的成本會低於讀取資料時鎖定資料的成本,因此可以獲得比其他並發控制方法更高的吞吐量。
簡述區別#
-
樂觀鎖並未真正加鎖,效率高。一旦鎖的粒度掌握不好,更新失敗的概率就會比較高,容易發生業務失敗。
-
悲觀鎖依賴資料庫鎖,效率低。更新失敗的概率比較低。
參考鏈接#
- https://learnku.com/articles/27880
- https://blog.debuginn.com/p/mysql-lock-occ-pcc
- https://www.hollischuang.com/archives/934
謝致#
感謝你的閱讀,讓我們一起探索知識,共同成長。