事務機制:Redis能實現ACID屬性嗎

事務的ACID屬性:

  1. 原子性 Automatic:指事務是一個不可分割的工作單位,事務中的操作要么都發生,要么都不發生。
  2. 一致性 Consistency:事務前后數據的完整性必須保持一致,即事務完成后,符合邏輯運算。
  3. 隔離性 Isolation:多個事務的操作互相獨立互相封閉,即事務A讀取不了事務B的數據,反之亦然。
  4. 持久性 Durability:事務提交,其對系統的影響是永久的。

Redis如何實現事務:

一般來說,事務的操作包括三個步驟。1.開啟事務;2.執行事務動作;3.提交事務。這正如我們日常在MySQL中使用的事務一樣:

BEGIN;
UPDATE purchase_order SET type = 1 WHERE id = 1;
COMMIT;

在redis中,只不過開啟事務的命令是MULTI,提交事務的是EXEC。整個過程大致如下:

  1. 使用MULTI命令顯式開啟一個事務。
  2. 客戶端將事務中所需要執行的命令發送給服務端,服務端將收到的命令暫存到隊列中依次執行。
  3. 使用EXEC命令提交事務,當服務器端收到 EXEC 命令后,才會實際執行命令隊列中的所有命令。
127.0.0.1:6379> mget a:quantity b:quantity
1) "1"
2) "5"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr a:quantity
QUEUED
127.0.0.1:6379> decr b:quantity
QUEUED
127.0.0.1:6379> exec
1) (integer) 2
2) (integer) 4
127.0.0.1:6379> mget a:quantity b:quantity
1) "2"
2) "4"

在上面我們可以看到,通過使用 MULTI 和 EXEC 命令,我們可以實現多個操作的共同執行,但是這符合事務要求的 ACID 屬性嗎?

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

1. 原子性

如果事務正常執行,沒有發生任何錯誤,那么,MULTI 和 EXEC 配合使用,就可以保證多個操作都完成。但是,如果事務執行發生錯誤了,原子性還能保證嗎?我們需要分以下三種情況 —— 命令入隊時就報錯;命令入隊時沒報錯,實際執行時報錯;EXEC 命令執行時實例故障。

第一種情況: 在執行 EXEC 命令前,客戶端發送的操作命令本身就有錯誤(比如語法錯誤,使用了不存在的命令),在命令入隊時就被 Redis 實例判斷出來了。

127.0.0.1:6379> mget a:quantity b:quantity
1) "2"
2) "4"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> put a:quantity
(error) ERR unknown command 'put'
127.0.0.1:6379> decr b:quantity
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> mget a:quantity b:quantity
1) "2"
2) "4"

在上面示例中,由于事務里包含了一個 Redis 本身就不支持的 PUT 命令,在 PUT 命令入隊時,Redis 就報錯了。雖然,事務里還有一個正確的 DECR 命令,但是,在最后執行 EXEC 命令后,整個事務被放棄執行了,所以這種情況是滿足原子性的。

第二種情況: 事務操作入隊時,命令和操作的數據類型不匹配,但 Redis 實例沒有檢查出錯誤。但是,在執行完 EXEC 命令以后,Redis 實際執行這些事務操作時,就會報錯。不過,需要注意的是,雖然 Redis 會對錯誤命令報錯,但還是會把正確的命令執行完。在這種情況下,事務的原子性就無法得到保證了。

127.0.0.1:6379> mget a:quantity b:quantity
1) "2"
2) "4"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> lpush a:quantity 2
QUEUED
127.0.0.1:6379> decr b:quantity
QUEUED
127.0.0.1:6379> exec
1) (error) WRONGTYPE Operation against a key holding the wrong kind of value
2) (integer) 3
127.0.0.1:6379> mget a:quantity b:quantity
1) "2"
2) "3"

對于上面的示例,我們很容易聯想到數據庫中的事務回滾機制,當事務執行失敗時,事務中的所有操作都會被撤銷,而在redis中并沒有提供事務回滾機制。( DISCARD 命令只能用來主動放棄事務執行,把暫存的命令隊列清空,起不到回滾的效果)

第三種情況: 在執行事務的 EXEC 命令時,Redis 實例發生了故障,導致事務執行失敗。在這種情況下,如果 Redis 開啟了 AOF 日志,那么,只會有部分的事務操作被記錄到 AOF 日志中。我們需要使用 redis-check-aof 工具檢查 AOF 日志文件,這個工具可以把未完成的事務操作從 AOF 文件中去除。這樣一來,我們使用 AOF 恢復實例后,事務操作不會再被執行,從而保證了原子性。當然,如果 AOF 日志并沒有開啟,那么實例重啟后,數據也都沒法恢復了,此時,也就談不上原子性了。

2. 一致性

對于一致性,我們還是分為以下三種情況進行分析 —— 命令入隊時就報錯;命令入隊時沒報錯,實際執行時報錯;EXEC 命令執行時實例故障。

第一種情況: 命令入隊時就報錯。在討論原子性的時候說過,這種情況下一整個事務都將放棄執行,也就意味著事務執行前后數據的一致,所以這種情況時可以保證一致性的。

第二種情況: 命令入隊時沒報錯,實際執行時報錯。這種情況下,事務中部分命令成功部分失敗,也不會改變數據庫的一致性。

第三種情況: EXEC 命令執行時實例發生故障。這種情況下,數據的一致性就取決于了數據恢復方式了,也就是取決于 AOF 還是 RDB。如果我們壓根沒有開啟 AOF 或 RDB,那么實例故障重啟后,數據都沒有了,數據庫是一致的。如果我們使用了 RDB 快照,因為 RDB 快照不會在事務執行時執行,所以,事務命令操作的結果不會被保存到 RDB 快照中,使用 RDB 快照進行恢復時,數據庫里的數據也是一致的。如果我們使用了 AOF 日志,而事務操作還沒有被記錄到 AOF 日志時,實例就發生了故障,那么,使用 AOF 日志恢復的數據庫數據是一致的。如果只有部分操作被記錄到了 AOF 日志,我們可以使用 redis-check-aof 清除事務中已經完成的操作,數據庫恢復后也是一致的。

3. 隔離性

事務隔離性的保證受到事務并發操作順序的影響,事務執行有命令入隊(EXEC 命令執行前)和命令實際執行(EXEC 命令執行后)兩個階段,所以,我們就針對這兩個階段,分成兩種情況來分析:1、并發操作在 EXEC 命令之后被服務器端接收并執行,此時,隔離性可以保證。2、并發操作在命令執行前發生,此時隔離性需要 WATCH 機制來保證,否則隔離性無法保證,WATCH 機制的作用是,在事務執行前,監控一個或多個鍵的值變化情況,當事務調用 EXEC 命令執行時,WATCH 機制會先檢查監控的鍵是否被其它客戶端修改了。如果修改了,就放棄事務執行,避免事務的隔離性被破壞。然后,客戶端可以再次執行事務,此時,如果沒有并發修改事務數據的操作了,事務就能正常執行,隔離性也得到了保證。。Redis 的 WATCH 機制

4. 持久性

因為 Redis 是內存數據庫,所以,數據是否持久化保存完全取決于 Redis 的持久化配置模式。如果 Redis 沒有使用 RDB 或 AOF,那么事務的持久化屬性肯定得不到保證。如果 Redis 使用了 RDB 模式,那么,在一個事務執行后,而下一次的 RDB 快照還未執行前,如果發生了實例宕機,這種情況下,事務修改的數據也是不能保證持久化的。如果 Redis 采用了 AOF 模式,因為 AOF 模式的三種配置選項 no、everysec 和 always 都會存在數據丟失的情況,所以,事務的持久性屬性也還是得不到保證。所以,不管 Redis 采用什么持久化模式,事務的持久性屬性是得不到保證的。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容