事務特點
Redis 事務可以一次執行多個命令, 并且帶有以下三個重要的保證:
-
批量操作在發送 EXEC 命令前被放入隊列緩存。
-
收到 EXEC 命令后進入事務執行,事務中任意命令執行失敗,其余的命令依然被執行。不具備原子性。
-
在事務執行過程,其他客戶端提交的命令請求不會插入到事務執行命令序列中。
Redis 事務的缺點
- 不支持回滾: Redis事務在執行
EXEC
命令之前,如果通過WATCH
檢測到監視的鍵發生了變化,則會拒絕執行整個事務并返回空結果。但請注意,這并不是傳統數據庫中的“回滾”操作,因為Redis不會自動撤銷已經執行過的命令,它僅僅是在事務中止時阻止后續未執行的命令。 - 命令排隊一次性執行: 在Redis事務中,所有命令會被放入一個隊列,在
EXEC
命令被執行時按照先進先出的順序執行,期間不能中斷或插入新的命令。這意味著事務開始后無法根據中間結果動態調整事務內的操作,降低了靈活性。 - 無隔離級別: Redis的事務沒有提供如SQL數據庫那樣的多種事務隔離級別(如讀已提交、可重復讀等)。所有事務都是在單線程環境下的串行化執行,因此避免了臟讀、不可重復讀等問題,但這也意味著在高并發場景下可能會有性能瓶頸。
- Watch-Multi-Exec模式的問題: 使用
WATCH
進行樂觀鎖控制時,一旦網絡延遲或者客戶端異常導致事務未能及時執行,監視的數據可能已經被其他客戶端修改,此時即便事務最終執行,也無法保證數據一致性。 - 批量操作不具備完全的原子性: 雖然Redis的所有命令在服務器內部是原子執行的,但在一個事務中多個命令的組合并不能視為一個原子操作。例如,事務中包含對多個key的操作,即使其中一個操作失敗,事務內其它命令也會被執行完畢,而不是整體取消。
三個階段
一個事務從開始到執行會經歷以下三個階段:
- 開始事務。
- 命令入隊。
- 執行事務。
案例步驟
從multi 開始,一系列操作,最后執行 exec ,批量執行處理
127.0.0.1:6379> multi
OK
127.0.0.1:6379> keys *
QUEUED
127.0.0.1:6379> hset person name zhang age 34
QUEUED
127.0.0.1:6379> hdel person age
QUEUED
127.0.0.1:6379> hset person email zhang@sina.com
QUEUED
127.0.0.1:6379> exec
1) 1) "listz"2) "book"3) "word"4) "myset"5) "mydest"6) "student"
2) (integer) 2
3) (integer) 1
4) (integer) 1
127.0.0.1:6379> hgetall person
1) "name"
2) "zhang"
下表列出了 redis 事務的相關命令:
序號 | 命令及描述 |
---|---|
1 | [DISCARD] 取消事務,放棄執行事務塊內的所有命令。 |
2 | [EXEC] 執行所有事務塊內的命令。 |
3 | [MULTI] 標記一個事務塊的開始。 |
4 | [UNWATCH] 取消 WATCH 命令對所有 key 的監視。 |
5 | [WATCH key [key ...]] 監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那么事務將被打斷。 |
樂觀鎖
樂觀鎖(Optimistic Locking)是一種在數據庫并發控制中的策略,它假設多用戶同時訪問同一數據時發生沖突的概率較低,并且在更新數據之前并不立即進行加鎖操作。與悲觀鎖不同的是,悲觀鎖在讀取數據時就直接獲取并持有鎖,直到事務結束才釋放;而樂觀鎖則是:
- 讀取階段:當一個事務想要修改數據時,它不會立即鎖定該數據行。每個事務在讀取數據時都會記錄下當時的數據版本號或時間戳等信息。
- 驗證階段:在事務提交更新操作前,會再次檢查當前要更新的數據是否自上次讀取以來沒有被其他事務修改過。這通常通過比較數據的版本號來實現,如果版本號未變,則認為可以安全地執行更新。
- 更新階段:如果數據版本驗證通過(即版本號仍為事務開始讀取時的版本),則執行更新操作,并將數據版本號遞增,確保后續的并發事務能夠識別出這次更新。如果發現版本號已被改變,說明存在并發修改,此時樂觀鎖機制會讓當前事務回滾,并提示并發錯誤,通常需要重新讀取數據并嘗試更新。
Redis 樂觀鎖
Redis 樂觀鎖是一種在分布式系統中實現并發控制的機制,它借鑒了數據庫領域的樂觀并發控制思想,并通過Redis提供的命令來實現。在樂觀鎖策略下,假定多個客戶端同時訪問同一數據時,通常不會發生沖突或至少沖突的概率較低。因此,在讀取數據時不立即加鎖,而是在更新數據前才去檢查在此期間是否有其他客戶端修改過該數據。
在Redis中,樂觀鎖主要通過WATCH
命令和事務(multi/exec)來實現:
- WATCH命令:客戶端使用
WATCH
命令監視一個或多個鍵,這些鍵的數據狀態將被記錄下來。當執行WATCH
后,如果任何被監視的鍵在事務提交前發生了變化,則整個事務將會被打斷,即不會執行EXEC
命令內的操作。 - 事務處理:客戶端可以將一系列命令放入事務中,使用
MULTI
開始一個事務塊,然后執行一系列的操作指令。最后,用EXEC
命令嘗試提交事務。只有在所有被WATCH
的鍵自WATCH
以來未被其他客戶端改變的情況下,事務中的命令才會被執行。
舉例來說:
- 客戶端A對一個鍵進行
WATCH
。 - 然后客戶端A開始一個事務,并準備修改這個鍵的值。
- 在事務提交(
EXEC
)之前,如果其他客戶端改變了該鍵的值,那么客戶端A的事務在執行EXEC
時會發現數據已經被修改,從而導致事務回滾,不執行任何操作。
下面具體來舉例:
主要是 watch 和 multi 兩個命令配合使用,實現樂觀鎖
watch 監視某一個可能變化的 key。然后開啟事務,一系列操作放入隊列等待執行,如果在exec 執行事務之前,其他的客戶端對監視的key 做了修改,則exec 執行結果為nil 。什么都不執行。
127.0.0.1:6379> watch mm #### 開始監視 mm 變量
OK
127.0.0.1:6379> multi #### 開啟事務
OK
127.0.0.1:6379> incrby mm 500 #### 第一次 給 mm 加500 操作放入隊列
QUEUED
127.0.0.1:6379> incrby mm 500 #### 第二次 給 mm 加500 操作放入隊列
QUEUED
### 此時去另一個客戶端執行修改操作
另一個客戶端的修改操作
127.0.0.1:6379> decrby mm 500 #### 給 mm 減去500
(integer) -500 #### 立即起效,結果為 -500
然后再回原來執行事務的客戶端執行下面操作:
127.0.0.1:6379> exec #### 開始執行事務
(nil) #### 沒有任何執行結果 因為樂觀鎖起作用了
127.0.0.1:6379> get mm #### 再次查看結果
"-500" #### 是另一個客戶端的執行結果。剛才加的兩次500 無效。
127.0.0.1:6379> watch mm #### 再次監控
OK
127.0.0.1:6379> multi #### 開啟事務
OK
127.0.0.1:6379> incrby mm 500 #### 加500
QUEUED
127.0.0.1:6379> incrby mm 500 #### 加500
QUEUED
127.0.0.1:6379> exec #### 執行事務
1) (integer) 0
2) (integer) 500 #### 執行成功,因為其他客戶端沒有修改被監視的變量 mm .
127.0.0.1:6379>