Redis 事務那些事兒:實用技巧和避坑指南!
一、為什么我們關心Redis事務?
在Java開發的日常工作中,Redis幾乎無處不在。你可能用它做緩存、排行榜、分布式鎖,甚至用它做輕量級的數據存儲。
但隨著業務復雜度提升,很多人都會遇到這樣的問題:
- 多個Redis操作需要保證原子性,怎么做?
- Redis的事務和MySQL事務一樣靠譜嗎?
- WATCH、MULTI、EXEC這些命令到底怎么用?能不能防止并發下的數據不一致?
這些問題看似簡單,實則暗藏不少坑。今天這篇文章,我想用最實在的語言,把Redis事務的本質、用法和注意事項講清楚,幫你在實際開發中少踩坑。
二、Redis事務機制全解析
1. Redis到底支不支持事務?
結論先行:Redis支持事務,但和MySQL事務完全不是一回事。
MySQL事務強調ACID(原子性、一致性、隔離性、持久性),而Redis的事務機制更像是“命令打包、順序執行”,沒有復雜的隔離和回滾機制。
2. Redis事務的基本命令和用法
Redis事務的核心命令有四個:MULTI、EXEC、DISCARD、WATCH。
(1) MULTI/EXEC:事務的開始與提交
- MULTI:開啟事務,后續命令進入隊列
- EXEC:提交事務,隊列中的命令依次執行
舉個例子:
# 1. 初始化庫存
127.0.0.1:6379> set a:stock 100
OK
127.0.0.1:6379> set b:stock 200
OK
# 2. 開啟事務
127.0.0.1:6379> multi
OK
# 3. 將a:stock減1
127.0.0.1:6379> decr a:stock
QUEUED
# 4. 將b:stock減1
127.0.0.1:6379> decr b:stock
QUEUED
# 5. 實際執行事務
127.0.0.1:6379> exec
1) (integer) 99
2) (integer) 199
127.0.0.1:6379>
(2) DISCARD:放棄事務
如果在MULTI之后,發現有問題,可以用DISCARD放棄事務,清空命令隊列。
(3) WATCH:樂觀鎖的實現
在并發場景下,單靠MULTI/EXEC還不夠。比如轉賬操作,兩個客戶端同時讀取余額,都判斷可以轉賬,結果都扣了錢,余額就出錯了。
這時候可以用WATCH命令,類似樂觀鎖。WATCH會監控指定的key,如果在事務提交前這些key被其他客戶端修改,EXEC會失敗,事務不會執行。
示例:
127.0.0.1:6379> get a:stock
"99"
127.0.0.1:6379> watch a:stock
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decr a:stock
QUEUED
127.0.0.1:6379> decr b:stock
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379>
如上所示,a:stock在EXEC前被其他客戶端修改,EXEC會返回null,表示事務失敗。
三、Redis事務的常見“坑”和注意事項
1. 沒有回滾機制
只要EXEC執行,前面的命令就算后面有錯,也不會回滾。比如:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name tom
QUEUED
127.0.0.1:6379> incr name
QUEUED
127.0.0.1:6379> set age 18
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379>
- set name tom執行成功。
- incr name 執行時報錯(因為 name 是字符串,不能自增)。
- set age 18依然會被執行。
注意:Redis事務中,某條命令出錯不會影響其他命令的執行,也不會回滾。
那如果我們想實現回滾的效果怎么辦呢?
2. 如何用DISCARD修復?
如果你在MULTI之后發現命令寫錯了,可以在EXEC之前執行DISCARD,這樣所有已入隊的命令都不會被執行,數據不會被修改。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set addr bj
QUEUED
127.0.0.1:6379> incr addr
QUEUED
127.0.0.1:6379> set code 110
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get addr
(nil)
127.0.0.1:6379>
執行結果:
- set addr bj、set code 110都不會被執行。
- 事務被徹底放棄,Redis狀態不會有任何變化。
注意點:
- 一旦執行了EXEC,就無法再用DISCARD撤銷事務,已經執行的命令不會回滾。
- DISCARD只能在事務提交前使用,相當于“撤銷”本次事務。
3. 沒有隔離性
Redis事務期間,其他客戶端依然可以操作相關key。WATCH只能監控key本身的變化,不能保證更復雜的業務一致性。
比如你WATCH了a:stock,但b:stock被其他客戶端修改了,你的事務依然會執行。
四、實用干貨:Redis事務的正確打開方式
(1) 能用原子命令就用原子命令
Redis本身很多命令就是原子的,比如INCR、DECR、SETNX等,優先用這些。
(2) 事務只保證命令的“批量、順序、一次性”執行
不保證命令之間的隔離和回滾。
(3) WATCH適合樂觀鎖場景
比如扣庫存、轉賬等,先WATCH關鍵key,判斷條件后再MULTI/EXEC。
(4) 復雜業務建議用Lua腳本
Lua腳本在Redis中是原子執行的,可以實現更復雜的業務邏輯和回滾。
(5) 不要把Redis事務當成數據庫事務用
Redis事務和MySQL事務完全不是一回事,不能指望它幫你兜底所有一致性問題。
五、Redis事務和ACID的對比
很多同學會問:Redis事務到底支持ACID的哪幾項?
(1) 原子性(Atomicity)
Redis事務只保證“命令隊列”整體的原子性,不保證單條命令的原子性。EXEC時,要么所有命令都執行,要么都不執行(WATCH監控失敗時)。
(2) 一致性(Consistency)
Redis事務本身不保證數據的一致性,需要開發者自己保證。
(3) 隔離性(Isolation)
Redis事務沒有嚴格的隔離性,事務執行期間,其他客戶端可以修改相關key。
(4) 持久性(Durability)
取決于Redis的持久化配置(RDB、AOF),和事務機制本身無關。
一句話總結:Redis事務只保證“命令批量執行的原子性”,不保證隔離和回滾。
六、面試高頻問答
(1) Redis事務和MySQL事務的區別?
- MySQL事務支持ACID,Redis事務只保證命令批量執行的原子性。
- MySQL事務有回滾機制,Redis事務沒有。
- MySQL事務有隔離級別,Redis事務沒有。
(2) Redis事務失敗會回滾嗎?
不會。只要EXEC執行,前面的命令就算后面有錯,也不會回滾。
(3) WATCH命令的作用是什么?
實現樂觀鎖,監控指定key,防止并發下的數據不一致。
(4) Redis事務適合哪些場景?
適合批量命令、簡單樂觀鎖場景。不適合強一致性、復雜回滾的業務。