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