跟我一起學Redis之Redis事務簡單了解一下

前言

關係數據庫中的事務,小夥伴們應該是不陌生了,不管是在開發還是在面試過程中,總有兩個問題逃不掉:

  • 說說事務的特性;
  • 事務隔離級別是怎麼一回事?

事務處理不好,數據就可能不準確,最終就會導致業務出問題;藉此機會簡單回顧一下事務特性及其隔離級別,就當是複習了;

事務特性(ACID)

  • 原子性(Atomicity)

    指事務內所有操作要麼一起執行成功,要麼都一起失敗(或者說是回滾);如事務經典轉賬案例:A給B轉賬,A把錢扣了,但B沒有收到;可見這種錯誤是不能接受的,最終會回滾,這也是原子性的重要性。

  • 一致性(Consistency)

    指事務執行前後的狀態一致,如事務經典轉賬案例:A給B互相轉賬,不管怎麼轉,最終兩者錢的總和還是不變;

  • 持久性(Durability)

    指事務一旦提交,數據就已經永久保存了,不能再回滾;

  • 隔離性(Isolation)

    指多個並發事務之間的操作互不干擾,但是事務的並發可能會導致數據臟讀、不可重複讀、幻讀問題,根據業務情況,採用事務隔離級別進行對應數據讀問題處理。

事務隔離級別

  • 讀未提交(Read uncommitted)

    指一個事務讀取到其他未提交事務的數據。可能導致數據臟讀

    轉賬案例:A正在給B轉賬,本來轉的1000,A多輸入了個0,變成10000,但此事務還未提交,但此時B查詢到轉入的是10000,但A取消事務回滾之後,B又查詢不到轉入的數據。這種情況就是臟讀

  • 讀已提交(Read committed)

    指一個事務只能讀取到其他事務已提交的數據,從而解決了臟讀的問題。但可能導致數據不可重複讀

    轉賬案例:A要給B轉賬1000,A先查看了一下餘額,有1000,然後開始給B轉錢,但此時A家裡電費通過開啟的自動繳費功能,自動從A賬戶扣除200繳納電費,並提交;當A轉賬準備提交,再次確認餘額時,錢少了200。這樣就導致同一個事務中多次查詢的結果不一致,這種情況就是不可重複讀

  • 可重複讀(Repeatable read)

    指事務只要一開啟,就不允許其他事務進行修改操作,從而解決了不可重複讀問題。但可能導致數據幻讀

    轉賬案例:A經常給B轉賬,到年底了,需要查賬,然後開啟了一個事務進行查詢統計,剛開始查詢只是10條轉賬記錄,正準備統計時,因為緊急情況A需要給B轉一筆錢應急,從而新增了一條新記錄,並提交;而查賬事務正在統計中,最後發現轉賬額和看到的10條轉賬記錄不匹配。這種情況就是幻讀

  • 序列化(Serializable )

    指事務之間只能串行話執行,就像隊列一樣,排隊進行,這樣就解決了幻讀的問題,但是這種級別的並發性能不高,非特殊需求,這種級別一般不用。

正文

轉入正題,結合關係型數據庫的事務來看看Redis中事務有什麼不同;

Redis事務是指將多條命令加入隊列,一次批量執行多條命令,每條命令會按順序執行,事務執行過程中不會受客戶端傳入的命令請求影響。

Redis事務的相關命令如下:

  • MULTI:標識一個事務的開啟,即開啟事務;
  • EXEC:執行事務中的所有命令,即提交;
  • DISCARD:放棄事務;和回滾不一樣,Redis事務不支持回滾。
  • WATCH:監視Key改變,用於實現樂觀鎖。如果監視的Key的值改變,事務最終會執行失敗。
  • UNWATCH:放棄監視。

Redis事務和關係型數據庫的事務不太一樣,它不保證原子性,也沒有隔離級別的概念。來,結合命令演示,實戰說明一切:

沒有隔離級別

image-20201112153132140

如上圖所示,當事務開啟時,事務期間的命令並沒有執行,而是加入隊列,只有執行EXEC命令時,事務中的命令才會按照順序一一執行,從而事務間就不會導致數據臟讀、不可重複讀、幻讀的問題,因此就沒有隔離級別

不保證原子性

image-20201112154524168

如上圖所示,在通過EXEC執行事務時,其中命令執行失敗不會影響到其他命令的執行,並沒有保證同時成功和同時失敗的原子操作,儘管這樣,Redis事務中也沒有提供回滾的支持,官方提供了兩個理由:

image-20201112160255544

大概的意思就是:

  • 使用Redis命令語法錯誤,或是將命令運用在錯誤的數據類型鍵上(如對字符串進行加減乘除等),從而導致業務數據有問題,這種情況認為是編程導致的錯誤,應該在開發過程中解決,避免在生產環境中發生;
  • 由於不用支持回滾功能,Redis內部簡單化,而且還比較快;

在事務命令入隊過程中,發現相關命令邏輯使用錯誤,可以進行放棄該事務;如果使用錯誤的Redis命令,且沒有放棄事務,最終也會導致事務整體執行失敗,這也算是為原子性扳回一局,如下:

放棄事務

image-20201112223050653

命令語法錯誤導致事務執行失敗

image-20201112223821761

使用WATCH實現樂觀鎖

說到樂觀鎖,就和悲觀鎖一起簡單說說對其的理解:

樂觀鎖:就是非常樂觀,做什麼事都往好處想; 對於數據庫操作,就認為每次操作數據的時候都認為別的操作不會修改,所以不會加鎖,而是通過一個類似於版本的字段來標識該數據是否修改過,在執行本次操作前先判斷是否修改過,如果修改過就放棄本次操作重新再來;

悲觀鎖:就是非常悲觀,做什麼事都覺得不好;對於數據庫操作,每次操作數據數據都會認為別的操作會修改當前數據,說以都要對其進行加鎖,類似於表鎖和行鎖。

WATCH通過監視指定Redis Key,如果沒有改變,就執行成功,如果發現對應值發生改變,事務就會執行失敗,如下圖;

image-20201112232612965

那會一直監視指定的Key嗎?,答案當然是不會的,以下三種方式可以取消監視:

  • 事務執行之後,不管是否執行成功還好是失敗,都會取消對應的監視;
  • 當監視的客戶端斷開連接時,也會取消監視;
  • 可以手動UNWATCH取消所有Key的監視;

Redis事務優缺點

優點

  • 一次性按順序執行多個Redis命令,不受其他客戶端命令請求影響;
  • 事務中的命令要麼都執行(命令間執行失敗互相不影響),要麼都不執行(比如中間有命令語法錯誤);

缺點

  • 事務執行時,不能保證原子性;
  • 命令入隊每次都需要和服務器進行交互,增加帶寬;

注意

  • 當事務中命令語法使用錯誤時,最終會導致事務執行不成功,即事務內所有命令都不執行;
  • 當事務中命令知識邏輯錯誤,就比如給字符串做加減乘除操作時,只能在執行過程中發現錯誤,這種事務執行中失敗的命令不影響其他命令的執行。

總結

對於Redis事務,其實用的不是很多,大部分喜歡使用Lua腳本進行批量命令的執行,同時還能保證命令執行的原子性。

那為什麼要說Redis事務呢?

在之前計劃寫這篇文章的時候,和一些朋友簡單溝通過,大家的確用的不多,基本上都是用Lua腳本;但面試會時不時遇到過Redis事務的問題,最常見的是Redis中的事務和關係型數據庫中的事務有什麼區別,這是從面試角度出發有這篇文章;

其實Redis 2.6版本之前,還不支持Lua腳本時,Redis事務對於批量按序執行命令的場景也是很用的;就拿當下來說,如果一些業務需批量按序執行命令的,同樣可以使用,並非一定要Lua腳本。這是從使用角度來說;

最後從學習角度來說,既然學Redis,就應該儘可能的了解的多一點。 下一篇說說持久化。

一個被程序搞丑的帥小伙,關注”Code綜藝圈”,跟我一起學~~~