首頁  >  文章  >  資料庫  >  一篇文章帶你徹底搞懂Redis事務

一篇文章帶你徹底搞懂Redis事務

WBOY
WBOY轉載
2022-11-01 13:48:311750瀏覽

這篇文章為大家帶來了關於Redis的相關知識,其中主要介紹了關於事務的相關內容,其本質上是一種命令的集合,事務支持一次執行多個命令,在事務執行過程中,會順序執行隊列中的命令;下面一起來看一下,希望對大家有幫助。

推薦學習:Redis影片教學

#Redis 交易簡介

Redis 只是提供了簡單的事務功能。其本質是一組命令的集合,事務支援一次執行多個命令,在事務執行過程中,會順序執行佇列中的命令,其他客戶端提交的命令請求不會插入到本事務執行命令序列中。命令的執行過程是依序執行的,但不能保證原子性。無法像 MySQL 那樣,有隔離級別,出了問題之後還能回滾資料等高級操作。後面會詳細分析。

Redis 事務基本指令

Redis 提供以下幾個事務相關的基礎指令。

MULTI開啟事務,Redis 會將後續命令加到佇列中,而不真正執行它們,直到後續使用EXEC來原子化的順序執行這些指令EXEC執行所有交易區塊內的指令DISCARD取消事務,放棄執行交易區塊內所有的指令WATCH監視一個或多個key,若交易在執行前,這些key 被其他指令修改,則交易被終端,不會執行交易中的任何指令UNWATCH取消WATCH指令對所有keys 的監視

一般情況下,一個簡單的Redis 交易主要分為以下幾個部分:

執行指令MULTI開啟一個交易。開啟事務之後,執行指令的多個指令會依序被放入一個佇列,放入成功則會傳回QUEUED訊息。執行命令EXEC提交事務,Redis 會依序執行佇列中的命令,並依序傳回所有命令的結果。 (若想放棄提交事務,則執行DISCARD)。

下圖簡單介紹了下Redis 事務執行的過程:

#實例分析

下面我們來透過一些實際具體例子,來體會下Redis 中的事務。前面我們也說到 Redis 的事務不是正真的事務,是無法完全滿足標準事務的ACID特性的。透過下面的例子,我們來看看,Redis 的「破產版」事務到底存在什麼問題。

[A]正常執行提交

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET a 1
QUEUED
127.0.0.1:6379> SET b 2
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
127.0.0.1:6379> GET a
"1"
127.0.0.1:6379> GET b
"2"

開啟交易後,提交的指令都會加入佇列(QUEUED),執行 EXEC 後會逐步執行指令並傳回結果。這個看起來是不是跟我們平常使用 MySQL 的事務操作相似,類似 start transaction 和 commit。

[B]正常取消交易

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET a 1
QUEUED
127.0.0.1:6379> SET b 2
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> 
127.0.0.1:6379> GET a
(nil)
127.0.0.1:6379> GET b
(nil)

開啟事務後,若不想繼續事務,使用 DISCARD 取消,前面提交的指令並不會真正執行,相關的 key 值不變。這個看起來也跟 MySQL 的事務相似,類似 start transaction 和 rollback。

[C]WATCH 監視 key

-- 线程 1 中执行
127.0.0.1:6379> del a
(integer) 1
127.0.0.1:6379> get a
(nil)
127.0.0.1:6379> SET a 0
OK
127.0.0.1:6379> WATCH a
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET a 1
QUEUED
----------------------------------------- 线程 2 中执行
----------------------------------------- 127.0.0.1:6379> SET a 2
----------------------------------------- OK
127.0.0.1:6379> EXEC
(nil)
127.0.0.1:6379> GET a
"2"

在開啟交易之前 WATCH 了 a 的值,隨後再開啟交易。在另一個執行緒中設定了 a 的值(SET a 2),然後再 EXEC 執行事務,結果為 nil,
說明事務沒有執行。因為 a 的值在 WATCH 之後發生了變化,所以交易就取消了。

要注意的是,這裡和開啟交易的時間點沒有關係,與 MULTI 和另一個執行緒設定 a 的值的先後沒有關係。只要是在 WATCH 之後發生了變化。無論事務是否已經開啟,執行事務(EXEC)的時候都會取消。
普通情況下,執行 EXEC 和 DISCARD 指令時,都會預設執行 UNWATCH。

[D]語法錯誤

127.0.0.1:6379> SET a 1
OK
127.0.0.1:6379> SET b 2
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET a 11
QUEUED
127.0.0.1:6379> SETS b 22
(error) ERR unknown command 'SETS'
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> GET a
"1"
127.0.0.1:6379> GET b
"2"

當 Redis 開啟一個交易後,若新增的指令中有語法錯誤,會導致交易提交失敗。這種情況下事務佇列中的命令都不會被執行。如上例中 a 和 b 的值都是原有的值。
這類在EXEC 之前產生的錯誤,如命令名稱錯誤,命令參數錯誤等,會在EXEC 執行之前被檢測出來,所以在發生這些錯誤的時候,事務會被取消,事務中的所有命令都不會執行。 (這種情況看起來是不是有點像回滾了)

[E]運行時錯誤

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET a 1
QUEUED
127.0.0.1:6379> SET b hello
QUEUED
127.0.0.1:6379> INCR b
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
3) (error) ERR value is not an integer or out of range
127.0.0.1:6379> GET a
"1"
127.0.0.1:6379> GET b
"hello"

当 Redis 开启一个事务后,添加的命令没有出现前面说的语法错误,但是在运行时检测到了类型错误,导致事务最提交失败(说未完全成功可能更准确点)。此时事务并不会回滚,而是跳过错误命令继续执行。
如上面的例子,未报错的命令值已经修改,a 被设置成了 1,b 被设置为了 hello,但是报错的值未被修改,即 INCR b 类型错误,并未执行,b 的值也没有被再更新。

Redis 事务与 ACID

通过上面的例子,我们已经知道 Redis 的事务和我们通常接触的 MySQL 等关系数据库的事务还有有一定差异的。它不保证原子性。同时 Redis 事务也没有事务隔离级别的概念。下面我们来具体看下 Redis 在 ACID 四个特性中,那些是满足的,那些是不满足的。
事务执行可以分为命令入队(EXEC 执行前)和命令实际执行(EXEC 执行之后)两个阶段。下面我们在分析的时候,很多时候都会分这两种情况来分析。

原子性(A)

上面的实例分析中,[A],[B],[C]三种正常的情况,我们可以很明显的看出,是保证了原子性的。
但是一些异常情况下,是不满足原子性的。

如 [D] 所示的情况,客户端发送的命令有语法错误,在命令入队列时 Redis 就判断出来了。等到执行 EXEC 命令时,Redis 就会拒绝执行所有提交的命令,返回事务失败的结果。此种情况下,事务中的所有命令都不会被执行了,是保证了原子性的。 如 [E] 所示的情况,事务操作入队时,命令和操作类型不匹配,此时 Redis 没有检查出错误(这类错误是运行时错误)。等到执行 EXEC 命令后,Redis 实际执行这些命令操作时,就会报错。需要注意的是,虽然 Redis 会对错误的命令报错不执行,但是其余正确的命令会依次执行完。此种情况下,是无法保证原子性的。 在执行事务的 EXEC 命令时,Redis 实例发生了故障,导致事务执行失败。此时,如果开启了 AOF 日志,那么只会有部分事务操作被记录到 AOF 日志中。使用redis-check-aof工具检测 AOF 日志文件,可以把未完成的事务操作从 AOF 文件中去除。这样一来,使用 AOF 文件恢复实例后,事务操作不会被再执行,从而保证了原子性。若使用的 RDB 模式,最新的 RDB 快照是在 EXEC 执行之前生成的,使用快照恢复之后,事务中的命令也都没有执行,从而保证了原子性。若 Redis 没有开启持久化,则重启后内存中的数据全部丢失,也就谈不上原子性了。 一致性(C)

一致性指的是事务执行前后,数据符合数据库的定义和要求。这点在 Redis 事务中是满足的,不论是发生语法错误还是运行时错误,错误的命令均不会被执行。

EXEC 执行之前,入队报错(实例分析中的语法错误)

事务会放弃执行,故可以保证一致性。

EXEC 执行之后,实际执行时报错(实例分析中的运行时错误)

错误的命令不会被执行,正确的命令被执行,一致性可以保证。

EXEC 执行时,实例宕机

若 Redis 没有开启持久化,实例宕机重启后,数据都没有了,数据是一致的。
若配置了 RDB 方式,RDB 快照不会在事务执行时执行。所以,若事务执行到一半,实例发生了故障,此时上一次 RDB 快照中不会包含事务所做的修改,而下一次 RDB 快照还没有执行,实例重启后,事务修改的数据会丢失,数据是一致的。若事务已经完成,但新一次的 RDB 快照还没有生成,那事务修改的数据也会丢失,数据也是一致的。
若配置了 AOF 方式。当事务操作还没被记录到 AOF 日志时,实例就发生故障了,使用 AOF 日志恢复后数据是一致的。若事务中的只有部分操作被记录到 AOF 日志,可以使用 redis-check-aof清除事务中已经完成的操作,数据库恢复后数据也是一致的。

隔离性(I) 并发操作在 EXEC 执行前,隔离性需要通过 WATCH 机制来保证 并发操作在 EXEC 命令之后,隔离性可以保证

情况 a 可以参考前面的实例分析 WATCH 命令的使用。
情况 b,由于 Redis 是单线程执行命令,EXEC 命令执行后,Redis 会保证先把事务队列中的所有命令执行完之后再执行之后的命令。

持久性(D)

若 Redis 沒有開啟持久化,那麼就是所有資料都儲存在記憶體中,一旦重啟,資料就會遺失,因此此時交易的持久性是肯定無法得到保證的。
若 Redis 開啟了持久化,當實例宕機重啟,還是會有可能遺失數據,因此也並能完全保證持久性。
因此,我們可以說 Redis 事務無法一定保證持久性,僅在特殊的情況下,可以保證持久性。

關於 Redis 在開啟持久化之後,為啥還會丟失數據,筆者會單獨整理一篇 Redis 持久化與主從相關的文章來介紹,此處簡單說下。
如果配置了RDB 模式,在一個事務執行後,下一次RDB 快照還未執行前,Redis 實例發生了宕機,資料就會遺失、
如果配置了AOF 模式,而AOF 模式的三種配置選項no,everysec,always 也都可能產生資料遺失的情況。

總結一下,Redis 交易對ACID 的支持情況:

具備一定的原子性,但不支持回滾滿足一致性滿足隔離性無法保證持久性Redis 事務為什麼不支持回滾

看官網的說明:

What about rollbacks?
Redis does not support rollbacks of transactions since supporting rollbacks would have a significant impact on the simplicity and performance of Redis.

大部分需要交易回滾的情況是程式錯誤導致的,這種情況一般是開發環境,生產環境不應該出現這種錯誤。
對於邏輯錯誤,例如應該加 1,結果寫成加 2,這種情況無法透過回滾來解決了。
Redis 追求的是簡單高效,而傳統事務的實作相對複雜很多,這和 Redis 的設計思想是違背的。當我們享受 Redis 的快速時,也就無法再要求它更多。

推薦學習:Redis影片教學

以上是一篇文章帶你徹底搞懂Redis事務的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:jb51.net。如有侵權,請聯絡admin@php.cn刪除