事務(wù)是關(guān)系型數(shù)據(jù)庫的特征之一,那么作為 Nosql
的代表 Redis
中有事務(wù)嗎?如果有,那么 Redis
當(dāng)中的事務(wù)又是否具備關(guān)系型數(shù)據(jù)庫的 ACID
四大特性呢?
這個(gè)答案可能會令很多人感到意外,Redis
當(dāng)中是存在“事務(wù)”的。這里我把 Redis
的事務(wù)帶了引號,原因在后面分析。
Redis
當(dāng)中的單個(gè)命令都是原子操作,但是如果我們需要把多個(gè)命令組合操作又需要保證數(shù)據(jù)的一致性時(shí),就可以考試使用 Redis
提供的事務(wù)(或者使用前面介紹的 Lua
腳本)。
Redis
當(dāng)中,通過下面 4
個(gè)命令來實(shí)現(xiàn)事務(wù):
multi
:開啟事務(wù)exec
:執(zhí)行事務(wù)discard
:取消事務(wù)watch
:監(jiān)視Redis
的事務(wù)主要分為以下 3
步:
multi
開啟一個(gè)事務(wù)。QUEUED
。exec
提交事務(wù)之后,Redis
會依次執(zhí)行隊(duì)列里面的命令,并依次返回所有命令結(jié)果(如果想要放棄事務(wù),可以執(zhí)行 discard
命令)。接下來讓我們依次執(zhí)行以下命令來體會一下 Redis
當(dāng)中的事務(wù):
multi //開啟事務(wù) set name lonely_wolf //設(shè)置 name,此時(shí) Redis 會將命令放入隊(duì)列 set age 18 //設(shè)值 age,此時(shí) Redis 會將命令放入隊(duì)列 get name //獲取 name,此時(shí) Redis 會將命令放入隊(duì)列 exec //提交事務(wù),此時(shí)會依次執(zhí)行隊(duì)列里的命令,并依次返回結(jié)果
執(zhí)行完成之后得到如下效果:
Redis
中每個(gè)客戶端都有記錄當(dāng)前客戶端的事務(wù)狀態(tài) multiState
,下面就是一個(gè)客戶端 client
的數(shù)據(jù)結(jié)構(gòu)定義:
typedef struct client { uint64_t id;//客戶端唯一 id multiState mstate; //MULTI 和 EXEC 狀態(tài)(即事務(wù)狀態(tài)) //...省略其他屬性 } client;
multiState
數(shù)據(jù)結(jié)構(gòu)定義如下:
typedef struct multiState { multiCmd *commands;//存儲命令的 FIFO 隊(duì)列 int count;//命令總數(shù) //...省略了其他屬性 } multiState;
multiCmd
是一個(gè)隊(duì)列,用來接收并存儲開啟事務(wù)之后發(fā)送的命令,其數(shù)據(jù)結(jié)構(gòu)定義如下:
typedef struct multiCmd { robj **argv;//用來存儲參數(shù)的數(shù)組 int argc;//參數(shù)的數(shù)量 struct redisCommand *cmd;//命令指針 } multiCmd;
我們以上面事務(wù)的示例截圖中事務(wù)為例,可以得到如下所示的一個(gè)簡圖:
傳統(tǒng)的關(guān)系型數(shù)據(jù)庫中,一個(gè)事務(wù)一般都具有 ACID
特性。那么現(xiàn)在就讓我們來分析一下 Redis
是否也滿足這 ACID
四大特性。
在討論事務(wù)的原子性之前,我們先來看 2
個(gè)例子。
模擬事務(wù)在執(zhí)行命令前發(fā)生異常。依次執(zhí)行以下命令:
multi //開啟事務(wù) set name lonely_wolf //設(shè)置 name,此時(shí) Redis 會將命令放入隊(duì)列 get //執(zhí)行一個(gè)不完成的命令,此時(shí)會報(bào)錯(cuò) exec //在發(fā)生異常后提交事務(wù)
最終得到了如下圖所示的結(jié)果,我們可以看到,當(dāng)命令入隊(duì)的時(shí)候報(bào)錯(cuò)時(shí),事務(wù)已經(jīng)被取消了:
模擬事務(wù)在執(zhí)行命令前發(fā)生異常。依次執(zhí)行以下命令:
flushall //為了防止影響,先清空數(shù)據(jù)庫 multi //開啟事務(wù) set name lonely_wolf //設(shè)置 name,此時(shí) Redis 會將命令放入隊(duì)列 incr name //這個(gè)命令只能用于 value 為整數(shù)的字符串對象,此時(shí)執(zhí)行會報(bào)錯(cuò) exec //提交事務(wù),此時(shí)在執(zhí)行第一條命令成功,執(zhí)行第二條命令失敗 get name //獲取 name 的值
最終得到了如下圖所示的結(jié)果,我們可以看到,當(dāng)執(zhí)行事務(wù)報(bào)錯(cuò)的時(shí)候,之前已經(jīng)成功的命令并沒有被回滾,也就是說在執(zhí)行事務(wù)的時(shí)候某一個(gè)命令失敗了,并不會影響其他命令的執(zhí)行,即 Redis
的事務(wù)并不會回滾:
Redis 中的事務(wù)為什么不會滾
這個(gè)問題的答案在 Redis
官網(wǎng)中給出了明確的解釋:
總結(jié)起來主要就是 3
個(gè)原因:
Redis
作者認(rèn)為發(fā)生事務(wù)回滾的原因大部分都是程序錯(cuò)誤導(dǎo)致,這種情況一般發(fā)生在開發(fā)和測試階段,而生產(chǎn)環(huán)境很少出現(xiàn)。1
,但是程序邏輯寫成了加 2
,那么這種錯(cuò)誤也是無法通過事務(wù)回滾來進(jìn)行解決的。Redis
追求的是簡單高效,而傳統(tǒng)事務(wù)的實(shí)現(xiàn)相對比較復(fù)雜,這和 Redis
的設(shè)計(jì)思想相違背。一致性指的就是事務(wù)執(zhí)行前后的數(shù)據(jù)符合數(shù)據(jù)庫的定義和要求。這一點(diǎn) Redis
中的事務(wù)是符合要求的,上面講述原子性的時(shí)候已經(jīng)提到,不論是發(fā)生語法錯(cuò)誤還是運(yùn)行時(shí)錯(cuò)誤,錯(cuò)誤的命令均不會被執(zhí)行。
事務(wù)中的所有命令都會按順序執(zhí)行,在執(zhí)行 Redis
事務(wù)的過程中,另一個(gè)客戶端發(fā)出的請求不可能被服務(wù),這保證了命令是作為單獨(dú)的獨(dú)立操作執(zhí)行的。所以 Redis
當(dāng)中的事務(wù)是符合隔離性要求的。
如果 Redis
當(dāng)中沒有被開啟持久化,那么就是純內(nèi)存運(yùn)行的,一旦重啟,所有數(shù)據(jù)都會丟失,此時(shí)可以認(rèn)為 Redis
不具備事務(wù)的持久性;而如果 Redis
開啟了持久化,那么可以認(rèn)為 Redis
在特定條件下是具備持久性的。
上面我們講述 Redis
中事務(wù)時(shí),提到的的常用命令還有一個(gè) watch
命令,這個(gè)又是做什么用的呢?我們還是先來看一個(gè)例子。
首先打開一個(gè)客戶端一,依次執(zhí)行以下命令:
flushall //清空數(shù)據(jù)庫 multi //開啟事務(wù) get name //獲取 name,此時(shí)正常返回 nil set name lonely_wolf //設(shè)置 name get name //獲取 name,此時(shí)正常應(yīng)該返回 lonely_wolf
得到如下效果圖:
這時(shí)候我們先不執(zhí)行事務(wù),打開另一個(gè)客戶端二,來執(zhí)行一個(gè)命令 set name zhangsan
:
客戶端二執(zhí)行成功了,這時(shí)候再返回到客戶端一執(zhí)行 exec
命令:
可以發(fā)現(xiàn),第一句話返回了 zhangsan
。也就是說,name
這個(gè) key
值在入隊(duì)之后到 exec
之前發(fā)生了變化,一旦發(fā)生這種情況,可能會引起很嚴(yán)重的問題,所以在關(guān)系型數(shù)據(jù)庫可以通過鎖來解決這種問題,那么 Redis
當(dāng)中試如何解決的呢?
是的,在 Redis
當(dāng)中就是通過 watch
命令來處理這種場景的。
watch
命令可以為 Redis
事務(wù)提供 CAS
樂觀鎖行為,它可以在 exec
命令執(zhí)行之前,監(jiān)視任意 key
值的變化,也就是說當(dāng)多個(gè)線程更新同一個(gè) key
值的時(shí)候,會跟原值做比較,一旦發(fā)現(xiàn)它被修改過,則拒絕執(zhí)行命令,并且會返回 nil
給客戶端。
下面還是讓我們通過一個(gè)示例來演示一下。
打開一個(gè)客戶端一,依次執(zhí)行如下命令:
flushall //清空數(shù)據(jù)庫 watch name //監(jiān)視 name multi //開啟事務(wù) set name lonely_wolf //設(shè)置 name set age 18 // 設(shè)置 age get name //獲取 name get age //獲取 age
執(zhí)行之后得到如下效果圖:
這時(shí)候再打開一個(gè)客戶端二,執(zhí)行 set name zhangsan
命令:
然后再回到客戶端一執(zhí)行 exec
命令。這時(shí)候會發(fā)現(xiàn)直接返回了 nil
,也就是事務(wù)中所有的命令都沒有被執(zhí)行(即:只要檢測到一個(gè) key
值被修改過,那么整個(gè)事務(wù)都不會被執(zhí)行):
下面是一個(gè) Redis
服務(wù)的數(shù)據(jù)結(jié)構(gòu)定義:
typedef struct redisDb { dict *watched_keys; //被 watch 命令監(jiān)視的 key int id; //Database ID //...省略了其他屬性 } redisDb;
可以看到,redisDb
中的 watched_keys
存儲了一個(gè)字典,這個(gè)字典當(dāng)中的 key
存的就是被監(jiān)視的 key
,然后字典的值存的就是客戶端 id
。然后每個(gè)客戶端還有一個(gè)標(biāo)記屬性 CLIENT_DIRTY_CAS
,一旦我們執(zhí)行了一些如 set
,sadd
等能修改 key
值對應(yīng) value
的命令,那么客戶端的 CLIENT_DIRTY_CAS
標(biāo)記屬性將會被修改,后面執(zhí)行事務(wù)提交命令 exec
時(shí)發(fā)現(xiàn)客戶端的標(biāo)記屬性被修改過(樂觀鎖的體現(xiàn)),則會拒絕執(zhí)行事務(wù)。
本文主要介紹了 Redis
當(dāng)中的事務(wù)機(jī)制,在介紹事務(wù)實(shí)現(xiàn)原理的同時(shí)從傳統(tǒng)關(guān)系型數(shù)據(jù)庫的 ACID
四大特性對比分析了 Redis
當(dāng)中的事務(wù),并最終了解到了 Redis
的事務(wù)似乎并不是那么“完美”。
到此這篇關(guān)于Redis事務(wù)為什么不支持回滾 的文章就介紹到這了,更多相關(guān)Redis事務(wù)回滾 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
標(biāo)簽:景德鎮(zhèn) 邯鄲 丹東 大理 本溪 吉安 鶴崗 昭通
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Redis事務(wù)為什么不支持回滾》,本文關(guān)鍵詞 Redis,事務(wù),為什么,不支持,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。