1. 程式人生 > >redis的事務不是原子性

redis的事務不是原子性

最新 通過 存儲 內部 定義 部分 不能 所有 客戶

Reference: https://blog.csdn.net/u011692780/article/details/81213010

一、事務的四大特性

關系型數據庫的事務具有四個特性:

1. 原子性

2. 一致性

3. 隔離性

4. 持久性

二、而在我們redis數據庫中,事務回事什麽樣子的呢?

首先我們給出一個定義:redis的事務中,一次執行多條命令,本質是一組命令的集合,一個事務中所有的命令將被序列化,即按順序執行而不會被其他命令插入

在redis中,事務的作用就是在一個隊列中一次性、順序性、排他性的執行一系列的命令。

事務的生命周期:

1. 事務的創建:使用MULTI開啟一個事務

2. 加入隊列:在開啟事務的時候,每次操作的命令將會被插入到一個隊列中,同時這個命令並不會被真的執行

3. EXEC命令進行提交事務

常用的關於事務的命令有:

1. MULTI:使用該命令,標記一個事務塊的開始,通常在執行之後會回復OK,(但不一定真的OK),這個時候用戶可以輸入多個操作來代替逐條操作,redis會將這些操作放入隊列中。

2. EXEC:執行這個事務內的所有命令

3. DISCARD:放棄事務,即該事務內的所有命令都將取消

4. WATCH:監控一個或者多個key,如果這些key在提交事務(EXEC)之前被其他用戶修改過,那麽事務將執行失敗,需要重新獲取最新數據重頭操作(類似於樂觀鎖)。

5. UNWATCH:取消WATCH命令對多有key的監控,所有監控鎖將會被取消。

註意:關於樂觀鎖等概念:

樂觀鎖:就像他的名字,不會認為數據不會出錯,他不會為數據上鎖,但是為了保證數據的一致性,他會在每條記錄的後面添加一個標記(類似於版本號),假設A 獲取K1這條標記,得到了k1的版本號是1,並對其進行修改,這個時候B也獲取了k1這個數據,當然,B獲取的版本號也是1,同樣也對k1進行修改,這個時候,如果B先提交了,那麽k1的版本號將會改變成2,這個時候,如果A提交數據,他會發現自己的版本號與最新的版本號不一致,這個時候A的提交將不會成功,A的做法是重新獲取最新的k1的數據,重復修改數據、提交數據。

悲觀鎖:這個模式將認定數據一定會出錯,所以她的做法是將整張表鎖起來,這樣會有很強的一致性,但是同時會有極低的並發性(常用語數據庫備份工作,類似於表鎖)。

那麽,現在我們來執行一次具體看看redis的事務機制:

首先我會開啟事務,並向數據庫中存儲4條數據,可以看到沒執行一條命令的時候都會顯示入隊,並不會返回執行結果,說明redis中在事務提交之前,其內部的所有命令將不會被執行:

那麽,如果中間有命令出錯了會怎樣呢?現在我隨便打幾個字符試一試:

可以看出,在第三條命令中我隨便打了幾個字符,提交事務的時候並沒有成功,這也很符合我們對事務的理解,嗯~具有原子性。但是,有一個細節,那就是錯誤命令在我輸入的時候就已經報錯了,也就是說這個條錯誤命令在進入隊列的時候redis就已經知道這是一條錯誤命令,這樣,整個事務的命令將全部失敗,那麽,有沒有一種可能某個錯誤指令在進入隊列的時候redis還沒有發現他的錯誤呢?我們試一試下面這個例子:

問題出現了,我們可以看到,name+1這條指令其實是錯誤的,但是提交事務的時候會發現,這條錯誤命令確實沒有執行,但是其他正確的命令卻執行,這是為什麽的?

原因是在redis中,對於一個存在問題的命令,如果在入隊的時候就已經出錯,整個事務內的命令將都不會被執行(其後續的命令依然可以入隊),如果這個錯誤命令在入隊的時候並沒有報錯,而是在執行的時候出錯了,那麽redis默認跳過這個命令執行後續命令。也就是說,redis只實現了部分事務。

下面我們來看看剛剛提到的鎖的問題,我們說過,redis的鎖CAS(check and set)類似於樂觀鎖,redis的實現原理是使用watch進行監視一個(或多個)數據,如果在事務提交之前數據發生了變化(估計使用了類似於樂觀鎖的標記),那麽整個事務將提交失敗,我們可以舉一個例子,我們開啟兩個終端,模擬兩個人的操作,設置一條數據為count,初始時100,現在A對其進行監控,並且為count增加20

在沒有提交之前,B也獲取了這個count,為其減少50,

那麽這個時候A如果提交事務,會出現失敗提示:

可以看到,在A對數據的修改過程中,B對數據進行了修改,那麽這條數據的“標記”就發生了變化,已經不是當初A取出數據的時候的標記了,這樣,A的事務也就提交失敗了。

最後通過上述的實驗,我們總結redis事務的三條性質:

1. 單獨的隔離操作:事務中的所有命令會被序列化、按順序執行,在執行的過程中不會被其他客戶端發送來的命令打斷
2. 沒有隔離級別的概念:隊列中的命令在事務沒有被提交之前不會被實際執行
3. 不保證原子性:redis中的一個事務中如果存在命令執行失敗,那麽其他命令依然會被執行,沒有回滾機制

三、Redis中的事務為什麽沒有原子性與watch鎖

Redis事務可以一次執行多個命令,它先以 MULTI 開始一個事務, 然後將多個命令入隊到事務中, 最後由 EXEC 命令觸發事務, 一並執行事務中的所有命令

在傳統的關系型數據中,只要有任意一條指令失敗,則整個事務都會被撤銷回滾,而在Redis中,中間某條指令的失敗不會導致前面已做指令的回滾,也不會造成後續的指令不做,也因此得出 Redis 事務的執行並不是原子性的。

multi,代表一段事務的開始

exec,代表一個事務的提交

queued,代表某個指令在隊列中

但是,也有例外,比如如下這種情況

discard,代表某個事務撤銷

watch 鎖 ,在事務中不能改變被鎖的值 (exec提交後返回nil)

四、總結

在redis中,對於一個存在問題的命令,如果在入隊的時候就已經出錯,整個事務內的命令將都不會被執行(其後續的命令依然可以入隊),如果這個錯誤命令在入隊的時候並沒有報錯,而是在執行的時候出錯了,那麽redis默認跳過這個命令執行後續命令。也就是說,redis只實現了部分事務。

總結redis事務的三條性質:

1. 單獨的隔離操作:事務中的所有命令會被序列化、按順序執行,在執行的過程中不會被其他客戶端發送來的命令打斷
2. 沒有隔離級別的概念:隊列中的命令在事務沒有被提交之前不會被實際執行
3. 不保證原子性:redis中的一個事務中如果存在命令執行失敗,那麽其他命令依然會被執行,沒有回滾機制
---------------------
作者:XCCS_澍
來源:CSDN
原文:https://blog.csdn.net/u011692780/article/details/81213010
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

redis的事務不是原子性