通常我們提及數(shù)據(jù)庫(kù)都不可避免的要提到事務(wù),那么什么是事務(wù)呢?事務(wù)是指作為單個(gè)邏輯工作單元執(zhí)行的一系列操作。所以,首先事務(wù)是一系列操作,這一系列操作具有二態(tài)性,即完全地執(zhí)行或者完全地不執(zhí)行。因此事務(wù)處理可以確保除非事務(wù)單元內(nèi)的所有操作的成功完成,否則不會(huì)想數(shù)據(jù)庫(kù)更新面向數(shù)據(jù)的資源。我們這里舉一個(gè)例子,數(shù)據(jù)庫(kù)中除查詢(xún)操作以外,插入(Insert)、刪除(Delete)和更新(Update)這三種操作都會(huì)對(duì)數(shù)據(jù)造成影響,因?yàn)槭聞?wù)處理能夠保證一系列操作可以完全地執(zhí)行或者完全不執(zhí)行,因此在一個(gè)事務(wù)被提交以后,該事務(wù)中的任何一條SQL語(yǔ)句在被執(zhí)行的時(shí)候,都會(huì)生成一條撤銷(xiāo)日志(Undo Log),而撤銷(xiāo)日志中記錄的是和當(dāng)前擦作完全相反的操作,比如刪除的相反操作是插入,插入的相反操作是刪除等。我們通常所說(shuō)的事務(wù)回滾其實(shí)就是去執(zhí)行這些插銷(xiāo)日志里的相反操作,這同樣告訴我們一個(gè)道理,只有事務(wù)中的一系列操作完全執(zhí)行的情況下可以回滾,如果是在意外情況下導(dǎo)致事務(wù)中的一系列操作沒(méi)有完全執(zhí)行,這個(gè)時(shí)候我們是不能保證數(shù)據(jù)一定可以回滾的。
創(chuàng)新互聯(lián)建站服務(wù)項(xiàng)目包括樂(lè)山網(wǎng)站建設(shè)、樂(lè)山網(wǎng)站制作、樂(lè)山網(wǎng)頁(yè)制作以及樂(lè)山網(wǎng)絡(luò)營(yíng)銷(xiāo)策劃等。多年來(lái),我們專(zhuān)注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,樂(lè)山網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶(hù)以成都為中心已經(jīng)輻射到樂(lè)山省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶(hù)的支持與信任!
在數(shù)據(jù)庫(kù)相關(guān)理論中,一個(gè)邏輯工作單元想要成為事務(wù),就必須滿足 ACID,即原子性、一致性、隔離性和持久性。
我們對(duì)數(shù)據(jù)庫(kù)中事務(wù)處理的相關(guān)理論有了一個(gè)基本的認(rèn)識(shí),或許這個(gè)世界上的數(shù)據(jù)庫(kù)系統(tǒng)千差萬(wàn)別,但我相信在事務(wù)處理這個(gè)問(wèn)題上它們最終會(huì)殊途同歸,就像我們解決并發(fā)過(guò)程中的沖突問(wèn)題,常規(guī)的做法依然是加鎖一樣,這是我之所以要花費(fèi)精力去理解和解釋這些理論知識(shí)的原因,技術(shù)可謂是日新月異,如果我們總是一味地為新技術(shù)而疲于奔命,那么或許我們會(huì)漸漸地失去對(duì)這個(gè)行業(yè)的熱愛(ài),我相信原理永遠(yuǎn)比框架更為重要。
redis事務(wù)提供了一種“將多個(gè)命令打包, 然后一次性、按順序地執(zhí)行”的機(jī)制, 并且事務(wù)在執(zhí)行的期間不會(huì)主動(dòng)中斷 —— 服務(wù)器在執(zhí)行完事務(wù)中的所有命令之后, 才會(huì)繼續(xù)處理其他客戶(hù)端的其他命令。
Redis中的事務(wù)是可以視為一個(gè)隊(duì)列,即我們可以通過(guò)MULTI開(kāi)始一個(gè)事務(wù),這相當(dāng)于我們聲明了一個(gè)命令隊(duì)列。接下來(lái),我們向Redis中提交的每條命令,都會(huì)被排入這個(gè)命令隊(duì)列。當(dāng)我們輸入EXEC命令時(shí),將觸發(fā)當(dāng)前事務(wù),這相當(dāng)于我們從命令隊(duì)列中取出命令并執(zhí)行,所以Redis中一個(gè)事務(wù)從開(kāi)始到執(zhí)行會(huì)經(jīng)歷 開(kāi)始事務(wù) 、 命令入隊(duì) 和 執(zhí)行事務(wù) 三個(gè)階段。下面是一個(gè)在Redis中使用事務(wù)的簡(jiǎn)單示例:
127
.0
.0
.1
:6379>
MULTI
OK
127
.0
.0
.1
:6379>
SET
Book_Name "
GIt
Pro"
QUEUED
127
.0
.0
.1
:6379>
SADD
Program_Language "
C++" "
C#" "
Jave" "
Python"
QUEUED
127
.0
.0
.1
:6379>
GET
Book_Name
QUEUED
127
.0
.0
.1
:6379>
EXEC
1)
OK
2) (
integer) 4
3) "
GIt
Pro"
我們可以注意到Redis中的事務(wù)和通常意義上的事務(wù)基本上是一致的,即
一個(gè)事務(wù)從開(kāi)始到執(zhí)行會(huì)經(jīng)歷以下三個(gè)階段:
下面將分別介紹事務(wù)的這三個(gè)階段。
1)開(kāi)始事務(wù)
MULTI命令的執(zhí)行標(biāo)記著事務(wù)的開(kāi)始:
redis>
MULTI
OK
這個(gè)命令唯一做的就是, 將客戶(hù)端的 REDIS_MULTI 選項(xiàng)打開(kāi), 讓客戶(hù)端從非事務(wù)狀態(tài)切換到事務(wù)狀態(tài)。
圖1 客戶(hù)端狀態(tài)轉(zhuǎn)換
2)命令入隊(duì)
當(dāng)客戶(hù)端處于非事務(wù)狀態(tài)下時(shí), 所有發(fā)送給服務(wù)器端的命令都會(huì)立即被服務(wù)器執(zhí)行:
redis>
SET msg
"hello moto"
OK
redis> GET msg
"hello moto"
但是, 當(dāng)客戶(hù)端進(jìn)入事務(wù)狀態(tài)之后, 服務(wù)器在收到來(lái)自客戶(hù)端的命令時(shí), 不會(huì)立即執(zhí)行命令, 而是將這些命令全部放進(jìn)一個(gè)事務(wù)隊(duì)列里, 然后返回QUEUED, 表示命令已入隊(duì):
redis>
MULTI
OK
redis> SET msg
"hello moto"
QUEUED
redis> GET msg
QUEUED
其原理如圖2所示
圖2. 命令入隊(duì)
3)執(zhí)行事務(wù)
前面說(shuō)到, 當(dāng)客戶(hù)端進(jìn)入事務(wù)狀態(tài)之后, 客戶(hù)端發(fā)送的命令就會(huì)被放進(jìn)事務(wù)隊(duì)列里。
但其實(shí)并不是所有的命令都會(huì)被放進(jìn)事務(wù)隊(duì)列, 其中的例外就是 EXEC 、 DISCARD 、 MULTI 和 WATCH 這四個(gè)命令 —— 當(dāng)這四個(gè)命令從客戶(hù)端發(fā)送到服務(wù)器時(shí), 它們會(huì)像客戶(hù)端處于非事務(wù)狀態(tài)一樣, 直接被服務(wù)器執(zhí)行:
圖3 執(zhí)行事務(wù)
如果客戶(hù)端正處于事務(wù)狀態(tài), 那么當(dāng)EXEC命令執(zhí)行時(shí), 服務(wù)器根據(jù)客戶(hù)端所保存的事務(wù)隊(duì)列, 以先進(jìn)先出(FIFO)的方式執(zhí)行事務(wù)隊(duì)列中的命令: 最先入隊(duì)的命令最先執(zhí)行, 而最后入隊(duì)的命令最后執(zhí)行。
執(zhí)行事務(wù)中的命令所得的結(jié)果會(huì)以 FIFO 的順序保存到一個(gè)回復(fù)隊(duì)列中。
當(dāng)事務(wù)隊(duì)列里的所有命令被執(zhí)行完之后,EXEC命令會(huì)將回復(fù)隊(duì)列作為自己的執(zhí)行結(jié)果返回給客戶(hù)端, 客戶(hù)端從事務(wù)狀態(tài)返回到非事務(wù)狀態(tài), 至此, 事務(wù)執(zhí)行完畢。
redis事務(wù)使用了multi、exec、discard、watch、unwatch命令,命令的作用如圖4所示:
圖4 事務(wù)命令
使用案例:
圖5. 正常執(zhí)行
圖6 放棄事務(wù)
圖7 命令錯(cuò)誤
圖8 語(yǔ)法錯(cuò)誤
使用watch檢測(cè)balance,事務(wù)期間balance數(shù)據(jù)未變動(dòng),事務(wù)執(zhí)行成功
圖9 watch用法1
WATCH命令用于在事務(wù)開(kāi)始之前監(jiān)視任意數(shù)量的鍵: 當(dāng)調(diào)用EXEC命令執(zhí)行事務(wù)時(shí), 如果任意一個(gè)被監(jiān)視的鍵已經(jīng)被其他客戶(hù)端修改了, 那么整個(gè)事務(wù)不再執(zhí)行, 直接返回失敗。
圖10 watch用法2
圖11 修改balance
在每個(gè)代表數(shù)據(jù)庫(kù)的 redis.h/redisDb 結(jié)構(gòu)類(lèi)型中, 都保存了一個(gè) watched_keys 字典, 字典的鍵是這個(gè)數(shù)據(jù)庫(kù)被監(jiān)視的鍵, 而字典的值則是一個(gè)鏈表, 鏈表中保存了所有監(jiān)視這個(gè)鍵的客戶(hù)端。
比如說(shuō),以下字典就展示了一個(gè) watched_keys 字典的例子:
圖11 watch實(shí)現(xiàn)的原理
其中, 鍵 key1 正在被 client2 、 client5 和 client1 三個(gè)客戶(hù)端監(jiān)視, 其他一些鍵也分別被其他別的客戶(hù)端監(jiān)視著。
WATCH 命令的作用, 就是將當(dāng)前客戶(hù)端和要監(jiān)視的鍵在 watched_keys 中進(jìn)行關(guān)聯(lián)。
舉個(gè)例子, 如果當(dāng)前客戶(hù)端為 client10086 , 那么當(dāng)客戶(hù)端執(zhí)行 WATCH key1 key2 時(shí), 前面展示的 watched_keys 將被修改成這個(gè)樣子:
圖12 watch實(shí)現(xiàn)原理2
通過(guò)watched_keys字典, 如果程序想檢查某個(gè)鍵是否被監(jiān)視, 那么它只要檢查字典中是否存在這個(gè)鍵即可; 如果程序要獲取監(jiān)視某個(gè)鍵的所有客戶(hù)端, 那么只要取出鍵的值(一個(gè)鏈表), 然后對(duì)鏈表進(jìn)行遍歷即可。
圖13 watch的觸發(fā)
當(dāng)客戶(hù)端發(fā)送 EXEC 命令、觸發(fā)事務(wù)執(zhí)行時(shí), 服務(wù)器會(huì)對(duì)客戶(hù)端的狀態(tài)進(jìn)行檢查:
在Redis中,事務(wù)總是具有原子性(Atomicity)、一致性(Consistency)和隔離性(Isolation),并且當(dāng)Redis運(yùn)行在某種特定的持久化模式下,事務(wù)也具有持久性性(Durability)。
事務(wù)具有原子性指的是, 數(shù)據(jù)庫(kù)將事務(wù)中的多個(gè)操作當(dāng)作一個(gè)整體來(lái)執(zhí)行,服務(wù)器要么就執(zhí)行事務(wù)中的所有操作, 要么就一個(gè)操作也不執(zhí)行。對(duì)于Redis的事務(wù)功能來(lái)說(shuō),事務(wù)隊(duì)列中的命令要么就全部都執(zhí)行,要么就一個(gè)都不執(zhí)行,因此, Redis的事務(wù)是具有原子性的。
Redis的事務(wù)和傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)事務(wù)的最大區(qū)別在于, Redis不支持事務(wù)回滾機(jī)制(rollback), 即使事務(wù)隊(duì)列中的某個(gè)命令在執(zhí)行期間出現(xiàn)了錯(cuò)誤,整個(gè)事務(wù)也會(huì)繼續(xù)執(zhí)行下去,直到將事務(wù)隊(duì)列中的所有命令都執(zhí)行完畢為止。 下面展示了即使RPUSH命令在執(zhí)行期間出現(xiàn)了錯(cuò)誤,事務(wù)的后續(xù)命令也會(huì)繼續(xù)執(zhí)行下去, 并且之前執(zhí)行的命令也不會(huì)有任何影響:
127
.0
.0
.1
:6379>
set
msg
hello
OK
127
.0
.0
.1
:6379>
multi
OK
127
.0
.0
.1
:6379>
sadd
fruit
apple
banana
cherry
QUEUED
127
.0
.0
.1
:6379>
rpush
msg
bye
redis
QUEUED
127
.0
.0
.1
:6379>
sadd
alphabet
a
b
c
QUEUED
127
.0
.0
.1
:6379>
exec
1) (
integer) 3
2) (
error)
WRONGTYPE
Operation
against
a
key
holding
the
wrong
kind
of
value
3) (
integer) 3
不支持事務(wù)回滾是因?yàn)檫@種復(fù)雜的功能和Redis追求簡(jiǎn)單高效的設(shè)計(jì)主旨不相符,并且Redis事務(wù)的執(zhí)行時(shí)錯(cuò)誤通常都是編程錯(cuò)誤產(chǎn)生的, 這種錯(cuò)誤通常只會(huì)出現(xiàn)在開(kāi)發(fā)環(huán)境中, 而很少會(huì)在實(shí)際的生產(chǎn)環(huán)境中出現(xiàn)。
事務(wù)的一致性是指,如果數(shù)據(jù)庫(kù)執(zhí)行前是一致的,那么在事務(wù)執(zhí)行后,無(wú)論事務(wù)是否執(zhí)行成功,數(shù)據(jù)庫(kù)也應(yīng)該是一致的。
事務(wù)的耐久性指的是,當(dāng)一個(gè)事務(wù)執(zhí)行完畢時(shí),執(zhí)行這個(gè)事務(wù)所得的結(jié)果巳經(jīng)被保存到 永久性存儲(chǔ)介質(zhì)(比如硬盤(pán))里面了, 即使服務(wù)器在事務(wù)執(zhí)行完畢 之后停機(jī), 執(zhí)行事務(wù)所得的結(jié)果也不會(huì)丟失。Redis事務(wù)的耐久性由服務(wù)器所使用持久化模式?jīng)Q定的:(1) 當(dāng)服務(wù)器在無(wú)持久化的內(nèi)存模式下運(yùn)作時(shí),事務(wù)不具有耐久性。因?yàn)橐坏┓?wù)器停機(jī),服務(wù)器所有的數(shù)據(jù)都將丟失。(2) 當(dāng)服務(wù)器在ROB持久化模式下運(yùn)作時(shí),事務(wù)同樣不具有耐久性。因?yàn)榉?wù)器只會(huì)在特定的保存條件下才會(huì)執(zhí)行BGSAVE命令,并且異步執(zhí)行的BGSAVE命令不能保證事務(wù)的數(shù)據(jù)第一時(shí)間被保存到硬盤(pán)上。(3) 當(dāng)服務(wù)器運(yùn)行在AOF持久化模式下,并且appendfsync選項(xiàng)的值為always時(shí),程序總會(huì)在執(zhí)行命令之后調(diào)用同步(sync)函數(shù),將命令數(shù)據(jù)真正地保存到硬盤(pán)里。