Redis事務為什么不支持回滾
前言
事務是關系型數(shù)據(jù)庫的特征之一,那么作為 Nosql 的代表 Redis 中有事務嗎?如果有,那么 Redis 當中的事務又是否具備關系型數(shù)據(jù)庫的 ACID 四大特性呢?
Redis 有事務嗎
這個答案可能會令很多人感到意外,Redis 當中是存在“事務”的。這里我把 Redis 的事務帶了引號,原因在后面分析。
Redis 當中的單個命令都是原子操作,但是如果我們需要把多個命令組合操作又需要保證數(shù)據(jù)的一致性時,就可以考試使用 Redis 提供的事務(或者使用前面介紹的 Lua 腳本)。
Redis 當中,通過下面 4 個命令來實現(xiàn)事務:
multi:開啟事務exec:執(zhí)行事務discard:取消事務watch:監(jiān)視
Redis 的事務主要分為以下 3 步:
- 執(zhí)行命令
multi開啟一個事務。 - 開啟事務之后執(zhí)行的命令都會被放入一個隊列,如果成功之后會固定返回
QUEUED。 - 執(zhí)行命令
exec提交事務之后,Redis會依次執(zhí)行隊列里面的命令,并依次返回所有命令結果(如果想要放棄事務,可以執(zhí)行discard命令)。
接下來讓我們依次執(zhí)行以下命令來體會一下 Redis 當中的事務:
multi //開啟事務 set name lonely_wolf //設置 name,此時 Redis 會將命令放入隊列 set age 18 //設值 age,此時 Redis 會將命令放入隊列 get name //獲取 name,此時 Redis 會將命令放入隊列 exec //提交事務,此時會依次執(zhí)行隊列里的命令,并依次返回結果
執(zhí)行完成之后得到如下效果:

Redis 事務實現(xiàn)原理
Redis 中每個客戶端都有記錄當前客戶端的事務狀態(tài) multiState,下面就是一個客戶端 client 的數(shù)據(jù)結構定義:
typedef struct client {
uint64_t id;//客戶端唯一 id
multiState mstate; //MULTI 和 EXEC 狀態(tài)(即事務狀態(tài))
//...省略其他屬性
} client;
multiState 數(shù)據(jù)結構定義如下:
typedef struct multiState {
multiCmd *commands;//存儲命令的 FIFO 隊列
int count;//命令總數(shù)
//...省略了其他屬性
} multiState;
multiCmd 是一個隊列,用來接收并存儲開啟事務之后發(fā)送的命令,其數(shù)據(jù)結構定義如下:
typedef struct multiCmd {
robj **argv;//用來存儲參數(shù)的數(shù)組
int argc;//參數(shù)的數(shù)量
struct redisCommand *cmd;//命令指針
} multiCmd;
我們以上面事務的示例截圖中事務為例,可以得到如下所示的一個簡圖:

Redis 事務 ACID 特性
傳統(tǒng)的關系型數(shù)據(jù)庫中,一個事務一般都具有 ACID 特性。那么現(xiàn)在就讓我們來分析一下 Redis 是否也滿足這 ACID 四大特性。
A - 原子性
在討論事務的原子性之前,我們先來看 2 個例子。
模擬事務在執(zhí)行命令前發(fā)生異常。依次執(zhí)行以下命令:
multi //開啟事務 set name lonely_wolf //設置 name,此時 Redis 會將命令放入隊列 get //執(zhí)行一個不完成的命令,此時會報錯 exec //在發(fā)生異常后提交事務
最終得到了如下圖所示的結果,我們可以看到,當命令入隊的時候報錯時,事務已經被取消了:

模擬事務在執(zhí)行命令前發(fā)生異常。依次執(zhí)行以下命令:
flushall //為了防止影響,先清空數(shù)據(jù)庫 multi //開啟事務 set name lonely_wolf //設置 name,此時 Redis 會將命令放入隊列 incr name //這個命令只能用于 value 為整數(shù)的字符串對象,此時執(zhí)行會報錯 exec //提交事務,此時在執(zhí)行第一條命令成功,執(zhí)行第二條命令失敗 get name //獲取 name 的值
最終得到了如下圖所示的結果,我們可以看到,當執(zhí)行事務報錯的時候,之前已經成功的命令并沒有被回滾,也就是說在執(zhí)行事務的時候某一個命令失敗了,并不會影響其他命令的執(zhí)行,即 Redis 的事務并不會回滾:

Redis 中的事務為什么不會滾
這個問題的答案在 Redis 官網中給出了明確的解釋:

總結起來主要就是 3 個原因:
Redis作者認為發(fā)生事務回滾的原因大部分都是程序錯誤導致,這種情況一般發(fā)生在開發(fā)和測試階段,而生產環(huán)境很少出現(xiàn)。- 對于邏輯性錯誤,比如本來應該把一個數(shù)加
1,但是程序邏輯寫成了加2,那么這種錯誤也是無法通過事務回滾來進行解決的。 Redis追求的是簡單高效,而傳統(tǒng)事務的實現(xiàn)相對比較復雜,這和Redis的設計思想相違背。
C - 一致性
一致性指的就是事務執(zhí)行前后的數(shù)據(jù)符合數(shù)據(jù)庫的定義和要求。這一點 Redis 中的事務是符合要求的,上面講述原子性的時候已經提到,不論是發(fā)生語法錯誤還是運行時錯誤,錯誤的命令均不會被執(zhí)行。
I - 隔離性
事務中的所有命令都會按順序執(zhí)行,在執(zhí)行 Redis 事務的過程中,另一個客戶端發(fā)出的請求不可能被服務,這保證了命令是作為單獨的獨立操作執(zhí)行的。所以 Redis 當中的事務是符合隔離性要求的。
D - 持久性
如果 Redis 當中沒有被開啟持久化,那么就是純內存運行的,一旦重啟,所有數(shù)據(jù)都會丟失,此時可以認為 Redis 不具備事務的持久性;而如果 Redis 開啟了持久化,那么可以認為 Redis 在特定條件下是具備持久性的。
watch 命令
上面我們講述 Redis 中事務時,提到的的常用命令還有一個 watch 命令,這個又是做什么用的呢?我們還是先來看一個例子。
首先打開一個客戶端一,依次執(zhí)行以下命令:
flushall //清空數(shù)據(jù)庫 multi //開啟事務 get name //獲取 name,此時正常返回 nil set name lonely_wolf //設置 name get name //獲取 name,此時正常應該返回 lonely_wolf
得到如下效果圖:

這時候我們先不執(zhí)行事務,打開另一個客戶端二,來執(zhí)行一個命令 set name zhangsan:

客戶端二執(zhí)行成功了,這時候再返回到客戶端一執(zhí)行 exec 命令:

可以發(fā)現(xiàn),第一句話返回了 zhangsan。也就是說,name 這個 key 值在入隊之后到 exec 之前發(fā)生了變化,一旦發(fā)生這種情況,可能會引起很嚴重的問題,所以在關系型數(shù)據(jù)庫可以通過鎖來解決這種問題,那么 Redis 當中試如何解決的呢?
是的,在 Redis 當中就是通過 watch 命令來處理這種場景的。
watch 命令的作用
watch 命令可以為 Redis 事務提供 CAS 樂觀鎖行為,它可以在 exec 命令執(zhí)行之前,監(jiān)視任意 key 值的變化,也就是說當多個線程更新同一個 key 值的時候,會跟原值做比較,一旦發(fā)現(xiàn)它被修改過,則拒絕執(zhí)行命令,并且會返回 nil 給客戶端。
下面還是讓我們通過一個示例來演示一下。
打開一個客戶端一,依次執(zhí)行如下命令:
flushall //清空數(shù)據(jù)庫 watch name //監(jiān)視 name multi //開啟事務 set name lonely_wolf //設置 name set age 18 // 設置 age get name //獲取 name get age //獲取 age
執(zhí)行之后得到如下效果圖:

這時候再打開一個客戶端二,執(zhí)行 set name zhangsan命令:

然后再回到客戶端一執(zhí)行 exec命令。這時候會發(fā)現(xiàn)直接返回了 nil,也就是事務中所有的命令都沒有被執(zhí)行(即:只要檢測到一個 key 值被修改過,那么整個事務都不會被執(zhí)行):

watch 原理分析
下面是一個 Redis 服務的數(shù)據(jù)結構定義:
typedef struct redisDb {
dict *watched_keys; //被 watch 命令監(jiān)視的 key
int id; //Database ID
//...省略了其他屬性
} redisDb;
可以看到,redisDb 中的 watched_keys 存儲了一個字典,這個字典當中的 key 存的就是被監(jiān)視的 key ,然后字典的值存的就是客戶端 id。然后每個客戶端還有一個標記屬性 CLIENT_DIRTY_CAS,一旦我們執(zhí)行了一些如 set,sadd 等能修改 key 值對應 value 的命令,那么客戶端的 CLIENT_DIRTY_CAS 標記屬性將會被修改,后面執(zhí)行事務提交命令 exec 時發(fā)現(xiàn)客戶端的標記屬性被修改過(樂觀鎖的體現(xiàn)),則會拒絕執(zhí)行事務。
總結
本文主要介紹了 Redis 當中的事務機制,在介紹事務實現(xiàn)原理的同時從傳統(tǒng)關系型數(shù)據(jù)庫的 ACID 四大特性對比分析了 Redis 當中的事務,并最終了解到了 Redis 的事務似乎并不是那么“完美”。
到此這篇關于Redis事務為什么不支持回滾 的文章就介紹到這了,更多相關Redis事務回滾 內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

