1. 程式人生 > >跟我一起學Redis之Redis事務簡單瞭解一下

跟我一起學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](https://i.loli.net/2020/11/12/BuxMriNWF1SfneT.png?ynotemdtimestamp=1605226956333) 如上圖所示,當事務開啟時,**事務期間的命令並沒有執行**,而是加入佇列,只有執行EXEC命令時,事務中的命令才會按照順序一一執行,從而事務間就不會導致資料髒讀、不可重複讀、幻讀的問題,因此就**沒有隔離級別**。 **不保證原子性**: ![image-20201112154524168](https://i.loli.net/2020/11/12/ReiCNEyjHkYTzmx.png?ynotemdtimestamp=1605226956333) 如上圖所示,在通過EXEC執行事務時,其中命令執行失敗不會影響到其他命令的執行,並沒有保證同時成功和同時失敗的原子操作,儘管這樣,Redis事務中也沒有提供回滾的支援,官方提供了兩個理由: ![image-20201112160255544](https://i.loli.net/2020/11/12/FQs3HjkzdgTOPyS.png?ynotemdtimestamp=1605226956333) 大概的意思就是: - 使用Redis命令語法錯誤,或是將命令運用在錯誤的資料型別鍵上(如對字串進行加減乘除等),從而導致業務資料有問題,這種情況認為是程式設計導致的錯誤,應該在開發過程中解決,避免在生產環境中發生; - 由於不用支援回滾功能,Redis內部簡單化,而且還比較快; 在事務命令入隊過程中,發現相關命令邏輯使用錯誤,可以進行放棄該事務;如果使用錯誤的Redis命令,且沒有放棄事務,最終也會導致事務整體執行失敗,這也算是為原子性扳回一局,如下: **放棄事務** ![image-20201112223050653](https://i.loli.net/2020/11/12/ViMpWY32l7Xvnrj.png?ynotemdtimestamp=1605226956333) **命令語法錯誤導致事務執行失敗** ![image-20201112223821761](https://i.loli.net/2020/11/12/M1qDW9ZwN65Avps.png?ynotemdtimestamp=1605226956333) #### 使用WATCH實現樂觀鎖 說到樂觀鎖,就和悲觀鎖一起簡單說說對其的理解: **樂觀鎖**:就是非常樂觀,做什麼事都往好處想; 對於資料庫操作,就認為每次操作資料的時候都認為別的操作不會修改,所以不會加鎖,而是通過一個類似於版本的欄位來標識該資料是否修改過,在執行本次操作前先判斷是否修改過,如果修改過就放棄本次操作重新再來; **悲觀鎖**:就是非常悲觀,做什麼事都覺得不好;對於資料庫操作,每次操作資料資料都會認為別的操作會修改當前資料,說以都要對其進行加鎖,類似於表鎖和行鎖。 WATCH通過監視指定Redis Key,如果沒有改變,就執行成功,如果發現對應值發生改變,事務就會執行失敗,如下圖; ![image-20201112232612965](https://i.loli.net/2020/11/12/djTHLoSAmgMhtFq.png?ynotemdtimestamp=1605226956333) 那會一直監視指定的Key嗎?,答案當然是不會的,以下三種方式可以取消監視: - 事務執行之後,不管是否執行成功還好是失敗,都會取消對應的監視; - 當監視的客戶端斷開連線時,也會取消監視; - 可以手動UNWATCH取消所有Key的監視; #### Redis事務優缺點 **優點**: - 一次性按順序執行多個Redis命令,不受其他客戶端命令請求影響; - 事務中的命令要麼都執行(命令間執行失敗互相不影響),要麼都不執行(比如中間有命令語法錯誤); **缺點**: - 事務執行時,不能保證原子性; - 命令入隊每次都需要和伺服器進行互動,增加頻寬; **注意** - 當事務中命令語法使用錯誤時,最終會導致事務執行不成功,即事務內所有命令都不執行; - 當事務中命令知識邏輯錯誤,就比如給字串做加減乘除操作時,只能在執行過程中發現錯誤,這種事務執行中失敗的命令不影響其他命令的執行。 ### 總結 對於Redis事務,其實用的不是很多,大部分喜歡使用**Lua指令碼**進行批量命令的執行,同時還能保證命令執行的原子性。 那為什麼要說Redis事務呢? 在之前計劃寫這篇文章的時候,和一些朋友簡單溝通過,大家的確用的不多,基本上都是用Lua指令碼;但面試會時不時遇到過Redis事務的問題,最常見的是Redis中的事務和關係型資料庫中的事務有什麼區別,這是從面試角度出發有這篇文章; 其實Redis 2.6版本之前,還不支援Lua指令碼時,Redis事務對於批量按序執行命令的場景也是很用的;就拿當下來說,如果一些業務需批量按序執行命令的,同樣可以使用,並非一定要Lua指令碼。這是從使用角度來說; 最後從學習角度來說,既然學Redis,就應該儘可能的瞭解的多一點。 下一篇說說持久化。 一個被程式搞醜的帥小夥,關注"Code綜藝圈",跟我一起學~~~ ![](https://i.loli.net/2020/11/13/DGNqOgt1UyQBYFC.png)