1. 程式人生 > 其它 >Redis | Redis 的事務一

Redis | Redis 的事務一

技術標籤:redisRedis事務

對於關係型資料庫而言,事務是很重要的功能,資料庫的事務在執行時具備 ACID 四種屬性,即 原子性、一致性、隔離性 和 永續性。在 Redis 中同樣也有事務的功能,我整理了 Redis 關於事務的命令和一些簡單的說明,讓我們看看 Redis 的事務。

命令介紹

Redis 關於事務的命令只有簡單的幾個,如下圖:

可以看到 Redis 關於事務的命令只有 5 條。下面來分別介紹一下這幾條命令。

multi:開始事務;

exec:提交事務;

discard:取消事務;

watch:監視某個 key;

unwatch:取消監視某個 key。

Redis 關於事務的命令就這麼幾個,基本上常用的就是 multi、exec 和 watch 這三個命令。我們來分別介紹一下這幾個命令。

命令的使用

我們先來簡單的看一個例子,主要來了解一下 multi 和 exec 的使用。我們構建一個場景,首先商店的 tshirt 有 10 件,張三的錢有 1000 元,張三有 tshirt 共0 件,我們以此來初始化環境,命令如下:

127.0.0.1:6379> set tshirt 10OK127.0.0.1:6379> set zhang:money 1000OK127.0.0.1:6379> set zhang:tshirt 0OK

當張三購買 tshirt 的時候,會發生三個事情,首先是 tshirt 要減庫存,然後張三的錢要減少,最後張三的 tshirt 會多一件

。有這麼一個過程,這個過程要麼都完成,要麼都不完成。也就是說,張三不能錢減少了,tshirt 卻沒增加也不能是庫存減少了,而張三的錢沒減少。基本就是這麼一個過程。

那麼,當我們執行命令時,要麼同時完成三個操作,要麼這三個操作一個也不完成,這就是所謂的原子性。而提到原子性,就離不開事務。我們使用 Redis 來完成上面的步驟。

127.0.0.1:6379> multiOK127.0.0.1:6379> decr tshirtQUEUED127.0.0.1:6379> decrby zhang:money 100QUEUED127.0.0.1:6379> incr zhang:tshirt
QUEUED127.0.0.1:6379> exec1) (integer) 92) (integer) 9003) (integer) 1

當我們通過 multi 開啟一個事務後,multi 之後,exec 之前的命令是不會馬上執行的。通過命令返回的 QUEUED 可以看出,multi 之後的命令都儲存到了一個佇列之中。當輸入命令 exec 之後,在佇列中的命令會一次性的執行。在提交了 exec 命令後的返回中可以看出,tshirt 減了 1,zhang:money 減少了 100,zhang:tshirt 增加了 1,順利的完成了一次簡單的交易操作。

目前只有一個客戶端在購買 tshirt,如果是兩個客戶會怎樣呢?我們通過 flushdb 先清空一下 Redis,再重新初始化一下我們的環境,命令如下:

127.0.0.1:6379> flushdbOK127.0.0.1:6379> set tshirt 1OK127.0.0.1:6379> set zhang:money 1000OK127.0.0.1:6379> set zhang:tshirt 0OK127.0.0.1:6379> set li:money 1000OK127.0.0.1:6379> set li:tshirt 0OK

這次,分別有了 zhang 和 li 兩個人,庫存的 tshirt 只有一件,然後我們讓他們分別來買這僅有的 1 件 tshirt。zhang 和 li 在兩個不同的命令列視窗中操作,命令如下:

get tshirt

zhang 和 li 都分別執行上面的命令,來檢視 tshirt 的剩餘數量,兩人都同時發現還有一件 tshirt。然後兩個人開始下單。先來看 zhang 的命令,命令如下:

127.0.0.1:6379> multiOK127.0.0.1:6379> decr tshirtQUEUED127.0.0.1:6379> decrby zhang:money 100QUEUED127.0.0.1:6379> incr zhang:tshirtQUEUED

上面的命令是 zhang 支付後的命令,接著是 li 支付後的命令,在另外一個命令列視窗輸入命令如下:

127.0.0.1:6379> multiOK127.0.0.1:6379> decr tshirtQUEUED127.0.0.1:6379> decrby li:money 100QUEUED127.0.0.1:6379> incr li:tshirtQUEUED127.0.0.1:6379> exec1) (integer) 02) (integer) 9003)(integer)1

接著回剛才 zhang 的命令列視窗輸入 exec 命令,看結果,結果如下:

127.0.0.1:6379> exec1) (integer) -12) (integer) 9003) (integer) 1

可以看到,執行 exec 後,tshirt 成為了 -1,相應的 zhang:money 和 zhang:tshirt 也更新了。雖然執行是沒問題,但是邏輯上錯了。tshirt 庫存成為了 -1,那就相當於是超賣了。這樣就產生了問題。


在解決這個問題之前,我們把上面的內容梳理一下。Redis 的事務支援 原子性 和 隔離性,當事務開始執行時,事務佇列中的命令會一次性執行完成,不會被其他的命令打斷,從而可以它擁有原子性;當我們對一個 key 進行修改操作時,另外一個客戶端也對 key 進行了修改操作,這樣對我們的修改操作就產生了影響,那麼看起來就不具備隔離性了,但是 Redis 提供了 watch 命令,我們可以通過 watch 命令來保證其隔離性但是上面的測試中,沒有使用 watch,因此也就沒有體現出隔離性)。

Redis 的事務不支援回滾當事務開始執行時(即執行了 exec 命令),事務就會將所有的命令執行完成除非在 multi 命令後錯誤的輸入了一條不存在的命令,此時執行 exec 命令時不會執行事務中的命令,如下所示:

127.0.0.1:6379> multiOK127.0.0.1:6379> incr tshirtQUEUED127.0.0.1:6379> gets tshirt(error) ERR unknown command `gets`, with args beginning with: `tshirt`, 127.0.0.1:6379> exec(error)EXECABORTTransactiondiscardedbecauseofpreviouserrors.

可以看到,在上面的命令中,輸入了 gets,而 gets 並不是 Redis 的命令,因此 gets 並沒有入隊,當執行 exec 後,整個命令也沒有被執行。

除了上面的問題外,當 Redis 的事務中有錯誤的命令使用,Redis 會執行所有的命令,如下:

​​​​​​​

127.0.0.1:6379> multiOK127.0.0.1:6379> incr tshirtQUEUED127.0.0.1:6379> sadd zhang:money 100QUEUED127.0.0.1:6379> incr tshirtQUEUED127.0.0.1:6379> exec1) (integer) 12) (error) WRONGTYPE Operation against a key holding the wrong kind of value3) (integer) 2

上面的命令中,使用 sadd 對字串進行操作顯然是錯誤的,但是在輸入完命令後,命令入隊了,因為在檢查命令語法時命令的使用格式是正確的,只有實際執行時才會發現問題所在,這就類似我們寫程式碼時的編譯時錯誤和執行時錯誤類似。因此,第一條命令執行成功了,第三條命令也執行成功了。為什麼後面 sadd 後面的指令能成功呢?因為這是程式設計師造成的問題,和 Redis 本身沒有太多的關係,就像我們寫的程式,在執行時邏輯出錯,編譯器是幫我們檢查不出來的。那麼 Redis 為什麼不進行回滾呢?因為 Redis 的設計初衷就是為了快,如果加入回滾的功能,那麼必定就會影響 Redis 的效率。對於不同的工具都具備其不同的設計考慮,一味的求全,而喪失了其設計本意,那麼可能就是失敗的設計了。

總結

Redis 的提供了對事務的支援,由於 Redis 本身的特性,因此對於事務的支援較弱,它不支援回滾。

Redis 通過 multi 命令可以讓其後續的命令進入佇列,當執行 exec 命令時,佇列中的命令一次性執行完成,保證了原子性。當使用了不正確的命令操作 key 時,在執行該命令時會報錯,這樣可能會影響到資料的一致性,但是隻要對 Redis 的命令使用正確就可以保證一致性了。因為事務中修改的 key 可能被其他的客戶端進行修改,因此,無法保證隔離性,但是 Redis 提供了 watch 命令,通過 watch 命令監控當前要修改的 key 是否被修改,就可以保證事務的隔離性。至於永續性,Redis 本身就是當作快取在用,那麼其永續性是否能夠保證呢?那就看 Redis 的使用場景和整個專案的設計了。當多個客戶端發起事務時,哪個客戶端先通過 exec 進行提交,那麼就先執行那個客戶端的事務。

關於 watch 的使用,在下一篇文章中進行整理。