為何Redis用樂觀鎖,而MySQL資料庫卻沒有?
想知道為何Redis用樂觀鎖,而MySQL資料庫卻沒有用。樂觀鎖說白了並不是鎖,而只是版本號檢查而已,為何Redis不像MySQL那樣用真正的鎖機制呢?
簡單來說,Redis使用樂觀鎖,相對於悲觀鎖,在實現中更加簡單,在某些場景中的性能也更好。Redis作為一個輕量級的、快速的緩存引擎,而不是一個全功能的關係型資料庫,既沒有使用悲觀鎖的必要,也難以承受使用悲觀鎖的成本。
詳細來說,要深入到Redis和MySQL的事務處理機制。Redis關於事務的文檔見此:
Transactions(事務)
Redis對於事務只提供了非常有限的支持,其實更多地是試圖繞過問題。
首先,Redis對於同一事務中的一組操作,而不是立即執行,而是放入一個queue中,當執行到EXEC時,再一起執行。事務執行是全局獨佔的,也就是同一時間只有一個事務被執行,中途不能被其它事務打斷。Redis用這種最簡單的、也是性能最差的方式避免了race
condition。
其次,在Redis的事務中,如果有一個或多個操作失敗,其它操作仍然會成功,也就是說它根本沒有回滾機制。
這種方式會帶來很多嚴重的問題,其中之一是,無法先讀取某個數值後再進行依賴這個值的操作,因為放在一個事務里會被在同一個瞬間執行,不放在同一個事務里又會導致race
condition。解決方法是使用WATCH,它會監視一個或多個變數,如果變數的值在調用WATCH以後和事務提交之前被別的事務修改過了,整個事務都會失敗。這類似於操作系統中的CAS(Compare
and Set)。我不知道WATCH具體是怎麼實現的,但是我推測它監控了指定變數的版本號。
即使有了WATCH,Redis的事務也是受到嚴重限制的。第一,它沒有實現讀數據時的一致性,因為WATCH對於讀操作不起作用。第二,它不支持回滾。第三,在對同一變數存在大量並發寫操作時,性能會非常差,因為每次提交事務時,WATCH監控的變數都已經被修改了,導致事務將多次提交失敗。但是,Redis本來就是一個KV類型的緩存引擎,要處理的是大量讀少量寫的場景,對一致性也沒有要求。
MySQL就完全不一樣了,作為一個典型的關係型資料庫,它需要完整地實現ACID,所以Redis的方式是解決不了它的問題的。
MySQL中的MVCC機制(Oracle的也是),通過undo
日誌來獲取某個行記錄的歷史快照,從而實現了所謂的讀一致性。它的目的是讀取某個時間點上的歷史數據(而不是可能已經被修改了的數據),而不是避免悲觀鎖的使用。嚴格地說這不能稱之為樂觀鎖,因為它既不Compare當前版本和歷史版本,也不進行Set。事實上,在讀取記錄的歷史快照時,當前記錄有可能(由於並發的寫操作)已經被加上獨佔鎖。
進一步的問題是:有沒有可能使用樂觀鎖來實現RDBMS中的寫一致性?有沒有可能使用樂觀鎖實現完整的ACID特性?
回答是可以。例如,MS
SQL SERVER的Hekaton引擎通過一套基於時間戳的多版本管理系統,實現了不使用了悲觀鎖的ACID。
但是,這並不意味著樂觀鎖必然優於悲觀鎖。除了維護多版本的開銷以外,樂觀鎖無法避免的一個問題是,當多個寫操作試圖更新同一個對象時,只有第一個操作可以成功,其它的操作都會在Compare時失敗然後回滾,從而造成極大的性能問題。在這種情況下,樂觀鎖的性能會低於悲觀鎖。
目前的趨勢似乎是,大規模的分散式資料庫更傾向於使用樂觀鎖來達到所謂的external
consistency,因為基於傳統悲觀鎖的分散式鎖在集群大到一定程度以後(從幾百台擴展到成千上萬台時),性能開銷就大得無法接受了。Google的Spanner就是基於樂觀鎖。當然這完全是另外一個問題了。
mysql也有悲觀鎖,樂觀鎖
redis的樂觀鎖又不是版本號檢查,只是使用完watch命令後在服務端將監控鍵放入一個字典里,當前事務過程中,一旦有被監控鍵被修改,將會在watch表內寫上dirty標誌,從而給客戶端返回事務失敗。
同樣MySQL是可以做樂觀鎖的,表上加欄位用於存版本時間戳即可,寫操作前獲得時間戳,寫操作時把之前獲得時間戳作為DML謂詞條件。
另外MVCC跟樂觀鎖關係不太一樣,MVCC基本還是要將前印象保存到undo塊上的,涉及一致讀再構造CR塊mysql怎麼沒有?update xxx set ver=ver+1 where id=$id and ver=$ver.
這個算不
MySQL 有 MVCC 機制,也就是樂觀鎖的一種。
推薦閱讀:
※為什麼我不再看好MariaDB
※MYSQL及MySQL WORKBENCH安裝過程遇到的問題及處理方法
※1.3 Mysql 安裝與使用-基礎配置-NodeJs+Express+Mysql實戰
※R markdown 連接mysql數據