做爲關係型數據庫中一項很是重要的基礎功能——事務,在 Redis 中是如何處理並使用的?html
事務指的是提供一種將多個命令打包,一次性按順序地執行的機制,而且保證服務器只有在執行完事務中的全部命令後,纔會繼續處理此客戶端的其餘命令。java
事務也是其餘關係型數據庫,所必備的一項很是重要的能力。以支付的場景爲例,正常狀況下只有正常消費完成以後,纔會減去帳戶餘額。但若是沒有事務的保障,可能會發生消費失敗了,但依舊會把帳戶的餘額給扣減了,我想這種狀況應該任何人都沒法接受吧?因此事務是數據庫中一項很是重要的基礎功能。redis
事務在其餘語言中,通常分爲如下三個階段:shell
以 Java 中的事務執行爲例:數據庫
// 開啓事務 begin(); try { //...... // 提交事務 commit(); } catch(Exception e) { // 回滾事務 rollback(); }
Redis 中的事務從開始到結束也是要經歷三個階段:編程
其中,開啓事務使用 multi
命令,事務執行使用 exec
命令,放棄事務使用 discard
命令。服務器
multi
命令用於開啓事務,實現代碼以下:併發
> multi OK
multi
命令可讓客戶端從非事務模式狀態,變爲事務模式狀態,以下圖所示:
注意:multi 命令不能嵌套使用,若是已經開啓了事務的狀況下,再執行 multi
命令,會提示以下錯誤:app
(error) ERR MULTI calls can not be nestedide
執行效果,以下代碼所示:
127.0.0.1:6379> multi OK 127.0.0.1:6379> multi (error) ERR MULTI calls can not be nested
當客戶端是非事務狀態時,使用 multi
命令,客戶端會返回結果 OK
,若是客戶端已是事務狀態,再執行 multi
命令會 multi
命令不能嵌套的錯誤,但不會終止客戶端爲事務的狀態,以下圖所示:
客戶端進入事務狀態以後,執行的全部常規 Redis 操做命令(非觸發事務執行或放棄和致使入列異常的命令)會依次入列,命令入列成功後會返回 QUEUED
,以下代碼所示:
> multi OK > set k v QUEUED > get k QUEUED
執行流程以下圖所示:
注意:命令會按照先進先出(FIFO)的順序出入列,也就是說事務會按照命令的入列順序,從前日後依次執行。
執行事務的命令是 exec
,放棄事務的命令是 discard
。
執行事務示例代碼以下:
> multi OK > set k v2 QUEUED > exec 1) OK > get k "v2"
放棄事務示例代碼以下:
> multi OK > set k v3 QUEUED > discard OK > get k "v2"
執行流程以下圖所示:
事務執行中的錯誤分爲如下三類:
入列時錯誤,會終止整個事務。
> get k "v" > multi OK > set k v2 QUEUED > expire k 10s QUEUED > exec 1) OK 2) (error) ERR value is not an integer or out of range > get k "v2"
執行命令解釋以下圖所示:
從以上結果能夠看出,即便事務隊列中某個命令在執行期間發生了錯誤,事務也會繼續執行,直到事務隊列中全部命令執行完成。
示例代碼以下:
> get k "v" > multi OK > set k v2 QUEUED > multi (error) ERR MULTI calls can not be nested > exec 1) OK > get k "v2"
執行命令解釋以下圖所示:
能夠看出,重複執行 multi
會致使入列錯誤,但不會終止事務,最終查詢的結果是事務執行成功了。除了重複執行 multi
命令,還有在事務狀態下執行 watch
也是一樣的效果,下文會詳細講解關於 watch
的內容。
示例代碼以下:
> get k "v2" > multi OK > set k v3 QUEUED > set k (error) ERR wrong number of arguments for 'set' command > exec (error) EXECABORT Transaction discarded because of previous errors. > get k "v2"
執行命令解釋以下圖所示:
Redis 官方文檔的解釋以下:
If you have a relational databases background, the fact that Redis commands can fail during a transaction, but still Redis will execute the rest of the transaction instead of rolling back, may look odd to you.
However there are good opinions for this behavior:
- Redis commands can fail only if called with a wrong syntax (and the problem is not detectable during the command queueing), or against keys holding the wrong data type: this means that in practical terms a failing command is the result of a programming errors, and a kind of error that is very likely to be detected during development, and not in production.
- Redis is internally simplified and faster because it does not need the ability to roll back.
An argument against Redis point of view is that bugs happen, however it should be noted that in general the roll back does not save you from programming errors. For instance if a query increments a key by 2 instead of 1, or increments the wrong key, there is no way for a rollback mechanism to help. Given that no one can save the programmer from his or her errors, and that the kind of errors required for a Redis command to fail are unlikely to enter in production, we selected the simpler and faster approach of not supporting roll backs on errors.
大概的意思是,做者不支持事務回滾的緣由有如下兩個:
這裏不支持事務回滾,指的是不支持運行時錯誤的事務回滾。
watch
命令用於客戶端併發狀況下,爲事務提供一個樂觀鎖(CAS,Check And Set),也就是能夠用 watch
命令來監控一個或多個變量,若是在事務的過程當中,某個監控項被修改了,那麼整個事務就會終止執行。
watch
基本語法以下:
watch key [key ...]
watch
示例代碼以下:
> watch k OK > multi OK > set k v2 QUEUED > exec (nil) > get k "v"
從以上命令能夠看出,若是 exec
返回的結果是 nil
時,表示 watch
監控的對象在事務執行的過程當中被修改了。從 get k
的結果也能夠看出,在事務中設置的值 set k v2
並未正常執行。
執行流程以下圖所示:
注意: watch
命令只能在客戶端開啓事務以前執行,在事務中執行 watch
命令會引起錯誤,但不會形成整個事務失敗,以下代碼所示:
> multi OK > set k v3 QUEUED > watch k (error) ERR WATCH inside MULTI is not allowed > exec 1) OK > get k "v3"
執行命令解釋以下圖所示:
unwatch
命令用於清除全部以前監控的全部對象(鍵值對)。
unwatch
示例以下所示:
> set k v OK > watch k OK > multi OK > unwatch QUEUED > set k v2 QUEUED > exec 1) OK 2) OK > get k "v2"
能夠看出,即便在事務的執行過程當中,k 值被修改了,由於調用了 unwatch
命令,整個事務依然會順利執行。
如下是事務在 Java 中的使用,代碼以下:
import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; public class TransactionExample { public static void main(String[] args) { // 建立 Redis 鏈接 Jedis jedis = new Jedis("xxx.xxx.xxx.xxx", 6379); // 設置 Redis 密碼 jedis.auth("xxx"); // 設置鍵值 jedis.set("k", "v"); // 開啓監視 watch jedis.watch("k"); // 開始事務 Transaction tx = jedis.multi(); // 命令入列 tx.set("k", "v2"); // 執行事務 tx.exec(); System.out.println(jedis.get("k")); jedis.close(); } }
事務爲多個命令提供一次性按順序執行的機制,與 Redis 事務相關的命令有如下五個:
正常狀況下 Redis 事務分爲三個階段:開啓事務、命令入列、執行事務。Redis 事務並不支持運行時錯誤的事務回滾,但在某些入列錯誤,如 set key
或者是 watch
監控項被修改時,提供整個事務回滾的功能。
Redis 事務中如何解決併發修改的問題?Redis 支持事務回滾嗎?使用 Redis 事務時會出現哪三種錯誤?這三種錯誤對事務有何影響?只有高手才能答對的問題,你能答上來幾個?
https://redis.io/topics/transactions
https://redisbook.readthedocs.io/en/latest/feature/transaction.html#id3