文章目錄
- Redis 事務
- 1)基本認識
- 2)事務操作
- 1.MULTI
- 2.EXEC
- 3.錯誤處理
- 4.DISCARD
- 5.WATCH
- 6.SCRIPT
Redis 事務
官方文檔,永遠是你學習的第一手資料:Redis 事務
1)基本認識
?談到事務,大家首先都會聯想到 mysql 中復雜但又功能強大的“事務”,和 mysql 相比,redis 所提供的事務簡直就是個“弟弟”。我們從 mysql 事務的四大基本特點進行比較:
原子性:
?原子性最初的含義就是把多個操作打包到一起,要么全部執行,要么全部不執行。但是mysql 在原子性這條道路上走的更遠,它要求多個操作要么全部執行成功,要么全部不執行,如果其中任一操作執行失敗,都會 rollback 確保數據的一致性。
?Redis 事務可以說具有原子性,也可以說沒有,這是一個理解角度的問題。Redis 事務確實可以將多個操作打包一起執行,從這方面談,Redis 是具有原子性的;但是和 mysql 不同,Redis 事務并不保證所有命令執行成功,由于 mysql 的標桿作用“拉高”了原子性的標準,從這一方面說,Redis 并不具有原子性。
?早些版本的 Redis 官網中明確提出了, Redis 事務具有原子性,但現在去看,官網已經把第一句刪掉了。看連 Redis 官方都 “慫了”,所以我們還是傾向于認為 Redis 不具有原子性
?那為什么 Redis 不提供和 mysql 一樣強大的事務機制呢?首先 mysql 為了實現事務機制付出了巨大的代價,而 Redis 則是主打一個輕量簡單,如果和 mysql 一樣就丟了自己的特色,那又怎么從這么多數據庫中沖殺出來呢?
一致性:
?由于 Redis 并不提供事務回滾機制,當其中某些操作失敗時就會造成數據不一致的問題。例如以下這個場景:張三給李四轉賬 1000 元。張三余額 -1000 的操作成功,但是李四余額 + 1000 的操作失敗,而 redis 并不會因為操作執行失敗而回滾數據,從而導致數據不一致的問題發生
持久性:
?mysql 中又 redo log 保證事務的持久性,但是 redis 事務本身并不具有持久性,持久化還得依賴redis的 rdb 或者 aof 機制。但是否開啟持久化,是redis-server自己的決定,和事務本身無關
隔離性:
?Redis 沒有也不需要隔離性。隔離性是針對并發讀寫的問題而引入了,而 Redis 是一個單進程的數據庫,不存在這方面的煩惱。
2)事務操作
1.MULTI
-
multi
指令用于開啟一個事務127.0.0.1:6379> multi OK
-
Redis 服務端為每一個客戶端維護一個事務命令隊列,
multi
后所有命令(除了 exec)都會被添加到隊列中而不是立即執行(queued 狀態)127.0.0.1:6379> set key1 1 QUEUED 127.0.0.1:6379> set key1 2 QUEUED
2.EXEC
-
exec
指令用于按次序一次性執行事務隊列中命令127.0.0.1:6379> exec 1) OK 2) OK 127.0.0.1:6379> get key1 "1"
3.錯誤處理
在 Redis 事務中存在兩種類型的錯誤:
-
在調用 exec 前發生錯誤,例如某個指令中存在語法錯誤。當這種錯誤發生時,整個事務都會被直接丟棄
127.0.0.1:6379> multi OK 127.0.0.1:6379> get key1 QUEUED 127.0.0.1:6379> abc (error) ERR unknown command `abc`, with args beginning with: 127.0.0.1:6379> exec (error) EXECABORT Transaction discarded because of previous errors.
-
在調用 exec 期間發生的錯誤。在這種情況下,Redis 會繼續執行剩余的命令,不管某些命令是否失敗。
127.0.0.1:6379> FLUSHALL OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> set key1 1 QUEUED 127.0.0.1:6379> LPOP key1 QUEUED 127.0.0.1:6379> set key2 2 QUEUED 127.0.0.1:6379> get key2 QUEUED 127.0.0.1:6379> EXEC 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 3) OK 4) "2"
上面的案例也驗證了 Redis 事務只能保證多條一起執行,但并不保證所有的命令都會執行成功。失敗了也不會數據回滾,因而存在一致性的問題
4.DISCARD
-
discard
指令用于終止取消當前正在執行的事務,隊列中的所有指令都會被直接丟棄127.0.0.1:6379> multi OK 127.0.0.1:6379> get key1 QUEUED 127.0.0.1:6379> get key2 QUEUED 127.0.0.1:6379> discard OK 127.0.0.1:6379> exec (error) ERR EXEC without MULTI
-
當前沒有正在執行的事務,則
discard
指令無效127.0.0.1:6379> discard (error) ERR DISCARD without MULTI
5.WATCH
用法:
-
在執行事務期間,某個鍵值被其他客戶端修改了,其結果就容易令人產生歧義,例如下面這個場景
time client_1 client_2 t1 execute command multi
t2 execute command set key1 1
…… t3 execute command set key1 2
t4 execute command exec
-
watch
與事務配合使用,它允許你監視多個 key,如果在事務的執行期間任何一個鍵被其他客戶端修改,都會導致當前事務全部丟棄,從而確保了事務執行期間數據的一致性。watch 的作用時間從 multi 開始到執行 exec 結束。// client1 127.0.0.1:6379> mset key1 1 key2 2 OK 127.0.0.1:6379> watch key1 key2 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> mget key1 key2 QUEUED 127.0.0.1:6379> exec (nil) // 事務執行失敗// client2:在client1事務執行期間進行如下修改 127.0.0.1:6379> incr key1 (integer) 2 127.0.0.1:6379> decr key2 (integer) 1
-
unwatch
的作用與 watch 相反,它用于清除對所有 key 的 “watch”。在執行 exec 或者 client 退出時,所有的 key 都會 “unwatched”
原理:
?watch 本質就是一把樂觀鎖,通過CAS機制實現:每個 "watched " 變量都有一個初始版本號,修改變量會讓其版本號變大,在執行 exec 時會比較當前版本號與 watch 時的版本號是否一致,如果不一致,說明變量在事務執行期間發生了修改,當前事務就會被 discard
案例:
下面是官方文檔提供的一個使用案例,通過 watch 去創造一個新的原子操作:
WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC
?從 zset 中刪除一個元素有兩部:1)找到最小的元素 2) 將最小的元素刪除。如果刪除失敗,說明最小值被其他客戶端修改,那么我們就重復上面操作,直到刪除一個最小值
6.SCRIPT
?任何 redis 事務能完成的操作,我們都可以使用 redis script 完成。redis script 天生就有把多個指令打包執行的能力,感興趣的可以去網上找找 lua 腳本操作 redis 的教程