Redis事務以MULTI開始,中間添加多種命令,這些命令不會立即執行,而是被放入到一個隊列中,當執行EXEC時,隊列中的所有命令被依次執行。
當命令放在MULTI中,但還未執行EXEC時,每個命令返回值為QUEUED,Redis事務將多個命令使用MULTI包括起來,調用EXEC一起執行,減少與客戶端之間通信往返次數,提升執行多個命令時的性能
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set key 1 QUEUED 127.0.0.1:6379> LPUSH list a QUEUED 127.0.0.1:6379> EXEC 1) OK 2) (integer) 1 127.0.0.1:6379> keys * 1) "list" 2) "key" 127.0.0.1:6379>
從2.6.5版本開始,在執行EXEC之前,redis命令在加入隊列時,如果出現錯誤(一般為語法錯誤),則執行EXEC時,該事務不會被執行,並自動丟棄
127.0.0.1:6379> FLUSHALL OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET flag 1 QUEUED 127.0.0.1:6379> LPUSH list a QUEUED 127.0.0.1:6379> LPUSH list (error) ERR wrong number of arguments for 'lpush' command 127.0.0.1:6379> EXEC (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> keys * (empty list or set) ## 正確的腳本也未執行,當前事務被丟棄 127.0.0.1:6379>
在2.6.5版本之前,執行MULTI之后,EXEC之前發生錯誤時,當執行EXEC命令,redis忽略掉錯誤命令,執行正確的命令。
在EXEC執行之后發生錯誤,其他正確的命令會被執行,redis不會回滾
127.0.0.1:6379> FLUSHALL OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set mykey 1 QUEUED 127.0.0.1:6379> LPUSH mykey 12 QUEUED 127.0.0.1:6379> EXEC 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 127.0.0.1:6379> keys * 1) "mykey" ## 正確命令被執行,錯誤的命令被丟棄 127.0.0.1:6379>
Redis不支持事務回滾,官網給出了兩點理由
1、redis命令只有兩個錯誤會出現,一個為錯誤的語法結構,一個為對數據類型使用錯誤的方法處理,如上例中使用LPUSH操作字符串類型的mykey。這兩個問題在開發環境自測的時候就能夠發現。
2、redis內部的結構簡單,而且速度更快,因為它不需要回滾功能。
DISCARD命令可用於終止當前事務並丟棄,也即是清空隊列中的命令。該命令必須應用在MULTI命令中
127.0.0.1:6379> FLUSHALL OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set key v QUEUED 127.0.0.1:6379> set mykey 2 QUEUED 127.0.0.1:6379> LPUSH list aa 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>
以上為redis簡單的事務操作,但以上存在數據安全問題
假設小王暑假打工賺了一百塊錢,存了起來,但由於小王所在地的一些方面的原因,錢放進去,但還未執行EXEC
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET money 100 QUEUED 127.0.0.1:6379>
此時小王媽媽去銀行給小王匯過來1000元,並且營業員服務周到,各種硬件完備,很快就辦完了業務
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set money 1000 QUEUED 127.0.0.1:6379> EXEC 1) OK 127.0.0.1:6379> get money "1000" 127.0.0.1:6379>
此時小王的匯款也結束了,(執行了EXEC)
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET money 100 QUEUED 127.0.0.1:6379> EXEC 1) OK 127.0.0.1:6379> get money "100" 127.0.0.1:6379>
平白無故丟了一千大洋,還得上十年學,十個暑假才能賺回來這些錢,很心塞。小王的問題也有辦法解決,那就是在redis 2.2及以后版本之后引入的WATCH命令
小王再次存錢卡主
127.0.0.1:6379> FLUSHALL OK 127.0.0.1:6379> WATCH money ### 監控money OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set money 100 QUEUED
小王的媽媽依然是去銀行轉錢給小王,效率依然杠杠的
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set money 1000 QUEUED 127.0.0.1:6379> EXEC 1) OK 127.0.0.1:6379> get money "1000" 127.0.0.1:6379>
小王存的錢開始被寫入
127.0.0.1:6379> FLUSHALL OK 127.0.0.1:6379> WATCH money OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set money 100 QUEUED 127.0.0.1:6379> EXEC (nil) 127.0.0.1:6379> get money "1000" 127.0.0.1:6379>
小王存錢沒有成功,查看了下余額,發現已經有1000大洋了,那么這一百塊就可以去揮霍了,吃飽喝足,網吧包夜走起。。。
WATCH命令是一種樂觀鎖的實現,基於CAS(check and set,在java的JUC下atomic中,一些Atomic開頭的類,也使用了CAS原理,只不過在java中被稱為compare and set,但大致意思是一樣的)。watch用於監控某個key是否發生了改變,如果在一個事務中,某個key在設置參數之后,在執行exec之前,其他客戶端修改了該key,則該事務將返回null。
1、watch必須與multi一起使用,才會發生作用,並且其必須在multi之前執行
2、watch監控的元素在當前事務提交之前發生變化(另一個事務執行了exec),則無論當前事務中有多少命令,全部失敗。
3、watch監控的元素在當前事務提交之前,被放入到另外一個事務的隊列中,但並未執行exec,則當前事務可正常提交
4、watch監控的元素,被另外一個客戶端在非MULTI包括的命令中修改,則無論當前事務中有多少命令,也將全部失敗