Redis的ACID屬性

事務是數據庫的一個重要屬性,有關事務的4個特性,原子性、一致性、隔離性、持久性,也就是ACID,這些屬性既包含了對事務執行結果的要求,也有數據庫在事務執行先後的數據狀態變化的要求。redis

Redis能夠徹底保證ACID屬性嗎?若是保證不了,在一些場景下數據可能會出錯,因此咱們須要瞭解redis對於這些特性的支持狀況shell

事務ACID的要求

原子性

指事務是一個不可分割的工做單位,事務中的操做要麼都發生,要麼都不發生。數據庫

一致性

事務先後數據的完整性必須保持一致。bash

例如服務器

A有800,B有200,A給B轉帳200markdown

操做前A:800,B:200併發

操做後A:600,B:400工具

數據庫只有操做先後這兩種狀態,沒有其餘中間狀態的存在spa

隔離性

事務的隔離性是多個用戶併發訪問數據庫時,數據庫爲每個用戶開啓的事務,不能被其餘事務的操做數據所幹擾,多個併發事務之間要相互隔離。這個會牽扯到數據庫的隔離級別,在這邊不作詳細的介紹,後面會單獨出一篇文章介紹線程

持久性

持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即便數據庫發生故障也不該該對其有任何影響

Redis的事務實現

事務的執行包含三個步驟,Redis提供了MULTI、EXEC兩個命令完成這三個步驟

第一步,客戶端要使用一個命令顯式地表示一個事務的開啓。在 Redis 中,這個命令就是 MULTI。

第二步,客戶端把事務中自己要執行的具體操做(例如增刪改數據)發送給服務器端。這些操做就是 Redis 自己提供的數據讀寫命令,例如 GET、SET 等。不過,這些命令雖然被客戶端發送到了服務器端,但 Redis 實例只是把這些命令暫存到一個命令隊列中,並不會當即執行。

第三步,客戶端向服務器端發送提交事務的命令,讓數據庫實際執行第二步中發送的具體操做。Redis 提供的 EXEX命令就是事務提交的命令。當服務端收到這個命令後,纔會實際執行命令隊列中的全部命令

下面代碼顯示了使用命令執行事務的一個過程

#開啓事務
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 命令後,能夠看到返回了 四、9,這就代表,兩個 DECR 命令已經成功地執行了。

好了,經過使用 MULTI 和 EXEC 命令,咱們能夠實現多個操做的共同執行,可是這符合事務要求的 ACID 屬性嗎?接下來,咱們就來具體分析下。

Redis 的事務機制能保證哪些屬性

原子性

主要正對三種異常狀況看下

第一種

在執行EXEC命令前,客戶端發送操做命令自己存在錯誤(好比語法錯誤,使用了不存在的命令),在命令入隊時就被 Redis 實例判斷出來了。

對於這種狀況,在命令入隊時,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 命令後,整個事務被放棄執行了。

第二種

事務在操做入隊是,命令和操做的數據類型不匹配。可是,在執行完 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
複製代碼

看到這裏,你可能有個疑問,傳統數據庫(例如 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"
複製代碼

第三種

在執行事務的EXEC命令時,Redis實例發生故障,致使事務執行失敗

在這種狀況下,若是 Redis 開啓了 AOF 日誌,那麼,只會有部分的事務操做被記錄到 AOF 日誌中。咱們須要使用 redis-check-aof 工具檢查 AOF 日誌文件,這個工具能夠把未完成的事務操做從 AOF 文件中去除。這樣一來,咱們使用 AOF 恢復實例後,事務操做不會再被執行,從而保證了原子性。

固然,若是 AOF 日誌並無開啓,那麼實例重啓後,數據也都無法恢復了,此時,也就談不上原子性了。

作個小總結

  • 命令入隊時報錯,會放棄事務執行,保證原子性
  • 命令入隊時正常,執行是報錯,不保證原則性
  • EXEC命令時,實例發生故障,若是開啓了AOF日誌,能夠保證原子性

一致性

和原子性同樣也要分三種狀況考慮

第一種

命令入隊就報錯

事務會放棄執行,保證一致性

第二種

命令入隊正常,實際執行報錯

在這種狀況下,有錯誤的命令不會被執行,正確的命令能夠正常執行,也不會改變數據庫的一致性。

第三種

EXEC命令執行時實例發生故障

在這種狀況下,實例故障後會進行重啓,這就和數據恢復的方式有關了,咱們要根據實例是否開啓了 RDB 或 AOF 來分狀況討論下。

若是咱們沒有開啓 RDB 或 AOF,那麼,實例故障重啓後,數據都沒有了,數據庫是一致的。

若是咱們使用了 RDB 快照,由於 RDB 快照不會在事務執行時執行,因此,事務命令操做的結果不會被保存到 RDB 快照中,使用 RDB 快照進行恢復時,數據庫裏的數據也是一致的。

若是咱們使用了 AOF 日誌,而事務操做尚未被記錄到 AOF 日誌時,實例就發生了故障,那麼,使用 AOF 日誌恢復的數據庫數據是一致的。若是隻有部分操做被記錄到了 AOF 日誌,咱們可使用 redis-check-aof 清除事務中已經完成的操做,數據庫恢復後也是一致的。

因此,總結來講,在命令執行錯誤或 Redis 發生故障的狀況下,Redis 事務機制對一致性屬性是有保證的。接下來,咱們再繼續分析下隔離性。

隔離性

事務的隔離性保證,會受到和事務一塊兒執行的併發操做的影響。而事務執行又能夠分紅命令入隊(EXEC 命令執行前)和命令實際執行(EXEC 命令執行後)兩個階段,因此,咱們就針對這兩個階段,分紅兩種狀況來分析:

一、併發操做在 EXEC 命令前執行,此時,隔離性的保證要使用 WATCH 機制來實現,不然隔離性沒法保證;

二、併發操做在 EXEC 命令後執行,此時,隔離性能夠保證。

咱們先來看第一種狀況。一個事務的 EXEC 命令尚未執行時,事務的命令操做是暫存在命令隊列中的。此時,若是有其它的併發操做,咱們就須要看事務是否使用了 WATCH 機制。

WATCH 機制的做用是,在事務執行前,監控一個或多個鍵的值變化狀況,當事務調用 EXEC 命令執行時,WATCH 機制會先檢查監控的鍵是否被其它客戶端修改了。若是修改了,就放棄事務執行,避免事務的隔離性被破壞。而後,客戶端能夠再次執行事務,此時,若是沒有併發修改事務數據的操做了,事務就能正常執行,隔離性也獲得了保證。

WATCH 機制的具體實現是由 WATCH 命令實現的,我給你舉個例子,你能夠看下下面的圖,進一步理解下 WATCH 命令的使用。

在 t2 時刻,客戶端 X 發送的 EXEC 命令尚未執行,可是客戶端 Y 的 DECR 命令就執行了,此時,a:stock 的值會被修改,這就沒法保證 X 發起的事務的隔離性了。

剛剛說的是併發操做在 EXEC 命令前執行的狀況,下面我再來講一說第二種狀況:兵法操做在EXEC命令以後被服務器接受並執行

由於 Redis 是用單線程執行命令,並且,EXEC 命令執行後,Redis 會保證先把命令隊列中的全部命令執行完。因此,在這種狀況下,併發操做不會破壞事務的隔離性。

持久性

由於 Redis 是內存數據庫,因此,數據是否持久化保存徹底取決於 Redis 的持久化配置模式。

若是 Redis 沒有使用 RDB 或 AOF,那麼事務的持久化屬性確定得不到保證。若是 Redis 使用了 RDB 模式,那麼,在一個事務執行後,而下一次的 RDB 快照還未執行前,若是發生了實例宕機,這種狀況下,事務修改的數據也是不能保證持久化的。

若是 Redis 採用了 AOF 模式,由於 AOF 模式的三種配置選項 no、everysec 和 always 都會存在數據丟失的狀況,因此,事務的持久性屬性也仍是得不到保證。

因此,無論 Redis 採用什麼持久化模式,事務的持久性屬性是得不到保證的。

總結

Redis 經過 MULTI、EXEC、DISCARD 和 WATCH 四個命令來支持事務機制,這 4 個命令的做用,我總結在下面的表中,你能夠再看下。

事務的 ACID 屬性是咱們使用事務進行正確操做的基本要求。Redis 的事務機制能夠保證一致性和隔離性,可是沒法保證持久性。不過,由於 Redis 自己是內存數據庫,持久性並非一個必須的屬性,咱們更加關注的仍是原子性、一致性和隔離性這三個屬性。

相關文章
相關標籤/搜索