redis事务实现
redis如何實現事務
事務的執行包含三個步驟,redis提供multi、exec兩個命令來完成這三個步驟。
第一步:客戶端要使用一個命令顯示的表示一個事務的開啟。在redis中這個命令就是multi。
第二步:客戶端把事務中本身要執行的具體操作(例如增刪改查)發送給服務器端。這些操作就是redis本身提供的數據讀寫命令,例如GET、SET等。這些命令雖然被客戶端發送到了服務器端,但redis實例只是把這些命令暫存到一個命令隊列中,并不會立即執行。
第三步:客戶端向服務器端發送提交事務的命令,讓數據庫實際執行第二步中發送的具體操作。redis提供的exec就是執行事務提交的。當服務器收到exec命令后,才會實際執行隊列中的命令。
#開啟事務
127.0.0.1:6379> MULTI
OK
#將a:stock減1,
127.0.0.1:6379> DECR a:stock
QUEUED
#將b:stock減1
127.0.0.1:6379> DECR b:stock
QUEUED
#實際執?事務
127.0.0.1:6379> EXEC
1) (integer) 4
2) (integer) 9
a:stock和b:stock兩個鍵的初始值是5和10.在multi命令后執行兩個decr,是把a:stock和b:stock兩個鍵的值分別-1,他們執行的結果都是QUEUED,這就表示這些操作都被暫時存放到了命令隊列,沒有實際執行。等到exec命令后,可以看到返回結果4和9,這就表明,兩個decr命令成功執行了。
redis的事務機制能保證ACID嗎?
原子性
如果事務正常執行,沒有發生任何錯誤,那么,multi和exec配合使用,就可以保證多個操作都完成。但是,如果事務執行發生錯誤了,原子性還能保證嗎?我們需要分三種情況來看。
第一種情況是,在執行exec命令前,客戶端發送的操作命令本身就有錯誤(比如語法錯誤,使用了不存在的命令),在命令入隊時就會被檢測出來。對于這種情況,在命令入隊時,redis就會報錯并且記錄下這個錯誤。此時,我們還能繼續提交命令。等到執行exec命令之后,redis就會拒絕執行所有提交的命令操作,返回事務失敗的結果。這樣一來所有命令都不會被執行,保證了原子性。
#開啟事務
127.0.0.1:6379> MULTI
OK
#發送事務中的第?個操作,但是Redis不?持該命令,返回報錯信息
127.0.0.1:6379> PUT a:stock 5
(error) ERR unknown command `PUT`, with args beginning with: `a:stock`, `5`,
#發送事務中的第?個操作,這個操作是正確的命令,Redis把該命令?隊
127.0.0.1:6379> DECR b:stock
QUEUED
#實際執?事務,但是之前命令有錯誤,所以Redis拒絕執?
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
在這個例子中,事務里包含了一個redis本身就不支持的put命令,所以在put命令入隊時,redis就保存了。雖然,事務中有一個正確的decr,但是,在最后執行exec命令后,整個事務被放棄執行了。
第二種情況。
和第一種情況不同的是,事務操作入隊時,命令和操作的數據類型不匹配,但redis實例沒有檢查出錯誤。但是,在執行exec命令后,redis實際執行這些事務操作時,就會報錯。不過,需要注意的是,雖然redis會對錯誤命令報錯,但還是會把正確的命令執行完。在這種情況下,事務的原子性就無法得到保證了。
#開啟事務
127.0.0.1:6379> MULTI
OK
#發送事務中的第?個操作,LPOP命令操作的數據類型不匹配,此時并不報錯
127.0.0.1:6379> LPOP a:stock
QUEUED
#發送事務中的第?個操作
127.0.0.1:6379> DECR b:stock
QUEUED
#實際執?事務,事務第?個操作執?報錯
127.0.0.1:6379> EXEC
1) (error) WRONGTYPE Operation against a key holding the wrong kind of value
2) (integer) 8
事務中的LPOP命令對String類型數據進行操作,入隊時沒有報錯,但是,在exec執行時報錯了。lpop命令本身沒有執行成功,但事務中的decr命令卻成功執行了。
redis不像mysql提供了回滾機制,可以回滾事務,redis沒有提供回滾機制。雖然redis提供了discard命令,但是這個命令只能用來主動放棄事務執行,把暫存的命令隊列清空,起不到回滾的作用。
#讀取a:stock的值4
127.0.0.1:6379> GET a:stock
"4"
#開啟事務
127.0.0.1:6379> MULTI
OK
#發送事務的第?個操作,對a:stock減1
127.0.0.1:6379> DECR a:stock
QUEUED
#執?DISCARD命令,主動放棄事務
127.0.0.1:6379> DISCARD
OK
#再次讀取a:stock的值,值沒有被修改
127.0.0.1:6379> GET a:stock
"4"
這個例子中,a:stock鍵的值一開始為4,然后,執行一個事務,相對a:stock的值-1.但是,在事務的最后,執行了discard命令,所以事務就被放棄了,再次查看a:stock的值,會發現仍然是4。
最后,再來看看第三種情況:在執行事務的exec命令時,redis發生了故障,導致事務執行失敗
這種情況下,如果redis開啟了AOF日志,那么,只會有部分事務操作被記錄到AOF日志中。可以使用redis-check-aof工具檢查AOF日志文件,這個工具可以把未完成的事務操作從AOF文件中刪除。這樣一來,使用AOF恢復數據庫實例后,事務操作不會再被執行,從而保證原子性。
當然,如果AOF日志并沒有開啟,那么實例重啟后,數據也就沒辦法恢復了,此時,就談不上原子性了。
總結:
命令入隊就報錯,會放棄事務執行,保證原子性
命令入隊不報錯,實際執行時報錯,不保證原子性
exec命令執行時實例故障,如果開啟了AOF日志,那么保證原子性。
一致性
事務的一致性保證會受到錯誤命令、實例故障的影響。所以,我們按照命令出錯和實例故障的發生時機,分成三種情況來看。
情況一:命令入隊時就報錯
在這種情況下,事務本身就會被放棄執行,所以可以保證數據庫的一致性。
情況二:命令入隊時沒報錯,實際執行時報錯
在這種情況下,有錯誤的命令不會被執行,正確的命令可以正常執行,也不會改變數據庫的一致性。
情況三:EXEC命令執行時實例發生故障
如果我們沒有開啟RDB或AOF,那么,實例故障重啟后,數據都沒有了,數據庫是一致的。
如果我們使用了RDB快照,因為RDB快照不會在事務執行時執行,所以,事務命令操作的結果不會被保存到RDB快照中,使用RDB快照進行恢復時,數據庫里的數據也是一致的。
如果我們使用了AOF日志,而事務操作還沒有被記錄到AOF日志時,實例就發生了故障,那么,使用AOF日志恢復的數據庫數據是一致的。如果只有部分操作被記錄到了AOF日志,我們可以使用redis-check-aof清除事務中已經完成的操作,數據庫恢復后也是一致的。
所以,總結來說,在命令執行錯誤或Redis發生故障的情況下,Redis事務機制對一致性屬性是有保證的
隔離性
redis單線程執行,不會存在隔離性問題。
持久性
因為Redis是內存數據庫,所以,數據是否持久化保存完全取決于Redis的持久化配置模式。
如果Redis沒有使用RDB或AOF,那么事務的持久化屬性肯定得不到保證。
如果Redis使用了RDB模式,那么,在一個事務執行后,而下一次的RDB快照還未執行前,如果發生了實例宕機,這種情況下,事務修改的數據也是不能保證持久化的。
如果Redis采用了AOF模式,因為AOF模式的三種配置選項no、everysec和always都會存在數據丟失的情況,所以,事務的持久性屬性也還是得不到保證。
所以,不管Redis采用什么持久化模式,事務的持久性屬性是得不到保證的。
總結
- 上一篇: 用建行网银转账会显示付款人的姓名和账号吗
- 下一篇: 【原创】关于include用法的总结【V