我們知道Redis是支持事務的,但是它里面的事務為什么不支持回滾呢?
1.在Redis中,命令只會因為錯誤的語法而失敗,或者是命令用在了錯誤類型的鍵上面;
也就是說,從實用的角度說,失敗的命令是由編譯錯誤造成的,而這些錯誤應該在開發過程中被發現,而不應該出現在生產環境中。
2.因為不需要對回滾進行支持,所以Redis的內部可以保持簡單且快速。
3.有種觀點認為,Redis處理事務的做法會產生bug,但是需要注意的是,通常情況下,回滾並不能解決編程錯誤而帶來的問題。
例如,如果你本來想通過INCR命令將鍵的值加1,卻不小心加了2,又或者對錯誤類型的鍵執行了INCR,回滾是沒辦法處理這些情況的。
鑒於沒有任何機制能避免程序員自己造成的錯誤,並且這類錯誤通常不會在生產環境出現,所以Redis選擇了更簡單、更快速的無回滾方式處理事務。
我們都知道,事務有 4 大特性。分別是:原子性(Atomicity)
、一致性(Consistency)
、隔離性(Isolation)
、持久性(Durability)
。
原子性(Atomicity)
原子性
是指事務是一個不可分割的工作單位,事務中的操作要么全部成功,要么全部失敗。比如在同一個事務中的 SQL 語句,要么全部執行成功,要么全部執行失敗。
然而,Redis 中的事務,如果在執行中間失敗了,在事務開始之前到遇到命令執行失敗這中間執行的命令不會回滾。
這就導致了,Redis 的事務沒有保證原子性。
下面看一個例子:
redis 127.0.0.1:7000> multi OK redis 127.0.0.1:7000> set a aaa QUEUED redis 127.0.0.1:7000> set b bbb QUEUED redis 127.0.0.1:7000> set c ccc QUEUED redis 127.0.0.1:7000> hhh www.xttblog.com QUEUED redis 127.0.0.1:7000> exec 1) OK 2) OK 3) OK 4)-ERR Operation against a key holding the wrong kind of value
雖然上面這段命令執行過程中會遇到錯誤,但是不會回滾。
set a、set b 等命令操作執行成功了。可以通過 get 取到對應的值。具體我就不貼代碼了。
Redis 執行事務過程
Redis 客戶端提供了管道操作。
管道可以理解為一個打包的批量執行腳本,但批量指令並非原子化的操作;
中間某條指令的失敗不會導致前面已做指令的回滾,也不會造成后續的指令不做。
一致性(Consistency)
Redis 事務的命令主要是 multi(開啟事務) exec(執行事務) discard(丟棄事務)。
Redis 事務在執行的過程中,不會處理其它命令,而是等所有命令都執行完后,再處理其它命令。
因此在 Redis 事務在執行過程中發生錯誤或進程被終結,都能保證數據的一致性。
隔離性(Isolation)
前面也說了,Redis 的事務在執行的過程中,不會處理其它命令,而是等所有命令都執行完后,再處理其它命令。
不管用不用管道,只要執行了 multi,就會阻塞其他操作。因此 Redis 事務是滿足隔離性的。
Redis的事務沒有隔離級別
況且 Redis 是一個單線程的。
另外需要注意的是:Redis 雖然保證了隔離性,但是它對事務沒有隔離級別的概念,
所以就不會產生我們使用關系型數據庫需要關注的臟讀,幻讀,重復讀的問題
。
持久性(Durability)
這個特性可談可不談,因為大部分情況下,Redis 是用來做緩存的。很多公司是沒有做持久化的,因此可以說 Redis 事務的持久性是不支持的。
Redis 事務不過是用隊列包裹起了一組 Redis 命令,並沒有提供任何額外的持久性功能,
所以事務的持久性由 Redis 所使用的持久化模式決定:
- 在單純的內存模式下,事務肯定是不持久的。
- 在 RDB 模式下,服務器可能在事務執行之后、RDB 文件更新之前的這段時間失敗,所以 RDB 模式下的 Redis 事務也是不持久的。
- 在 AOF 的
總是 SYNC
模式下,事務的每條命令在執行成功之后,都會立即調用 fsync 或 fdatasync 將事務數據寫入到 AOF 文件。但是,這種保存是由后台線程進行的,主線程不會阻塞直到保存成功,所以從命令執行成功到數據保存到硬盤之間,還是有一段非常小的間隔,所以這種模式下的事務也是不持久的。 - 其他 AOF 模式也和
總是 SYNC
模式類似,所以它們都是不持久的。
因此,我們可以說 Redis 的事務是不支持持久化的,或者說持久化是有缺陷的。就像 Redis 的分布式鎖一樣。
watch 機制實現樂觀鎖
雖說 Redis 不支持直接回滾,但我們可以通過 Redis 提供的一個命令來實現回滾。
這個命令就是 watch,該命令可以為 Redis 事務提供 check-and-set (CAS)行為。
我們可以使用 watch 命令來監視一個或多個 key,如果被監視的 key 在事務執行前被修改過那么本次事務將會被取消,也就是所謂的回滾。
只有確保被監視的 key,在事務開始前到執行 這段時間內未被修改過事務才會執行成功(類似樂觀鎖)
如果一次事務中存在被監視的 key,無論此次事務執行成功與否,該 key 的監視都將會在執行后失效 也就是說監視是一次性的。
總結
總的來說:Redis 事務就是一次性、順序性、排他性的執行一個隊列中的一系列命令
。沒有用過或了解過 Redis 事務的網友,千萬別拿它和數據庫事務相比較,否則面試中肯定會吃虧!
如果你想讓幾個 Redis 的命令保證原子性,那我建議你使用 Lua 腳本,而不是 Redis 事務!
參考:https://mp.weixin.qq.com/s/Wqa6lpyiwaldMbt1neScQA