DB提供兩種機制來保證事務的ACID(原子性,一致性,隔離性和持久性)特性,日誌預寫(write-ahead loging)和鎖(lock),前者用於保證原子性、一致性,後者用於保證隔離性。
事務在沒有提交前的一系列修改都不能持久化,所以這一系列的操做都是依賴兩種log來實現,redo-log和undo-log;修改前的數據由undo-log記錄,修改後的數據由redo-log記錄,事務提交成功則執行redo-log,失敗則執行undo-log。node
DB提供的事務隔離級別以下:
#1 未提交讀,READ UNCOMMITTED
執行讀操做時不加任何鎖,執行寫操做時添加行級共享鎖,直到事務結束。多個事務能夠並行讀取某數據,可是一旦某個事務執行了寫操做後,其餘事務仍舊能夠讀數據,但須要等待鎖釋放後才能夠執行寫操做 。
問題:不能解決髒讀,一個事務可能讀取到另一個事務最終沒有提交的數據。sql
#2 已提交讀,READ COMMITTED
執行讀操做時添加行級共享鎖,讀完以後就釋放鎖;執行寫操做時添加行級排他鎖,直到事務結束。多個事務能夠並行讀取某數據,可是一旦某個事務執行了寫操做後,其餘事務就再也不容許進行任何讀寫操做,所以能夠解決當前事務讀取的數據都是最終提交的。
問題:不能解決可重複讀,另外一個事務數據更新操做可能讓當前事務連續兩次讀取的數據不一致。數據庫
#3 可重複讀,REPEATED READ
執行讀操做時添加行級共享鎖,直到事務結束;執行寫操做時添加行級排他鎖,直到事務結束。多個事務能夠並行讀取某數據,但事務僅當數據上沒有任何鎖的時候才能添加排他鎖進行寫操做,直到當前事務結束後,其餘事務才能嘗試添加拍他所進行寫操做。所以一旦某數據被讀取以後,其餘事務不能對其進行修改。
問題:不能解決幻讀,另外一個事務的新數據插入操做可能讓當前事務連續兩次讀取的數據不一致。緩存
#4 序列化讀,SERIALIZABLE
執行讀操做時添加表級共享鎖,直到事務結束;執行寫操做時添加表級排他鎖,直到事務結束。所以涉及到表內某行數據的讀寫都串行發生。
問題:全部事務都串行發生,訪問性能極大下降。session
Racing Process中關於鎖的機制架構
就用途而言,鎖包含悲觀鎖、樂觀鎖、獨佔鎖、共享鎖、(非)公平鎖、分佈式鎖和自旋鎖。併發
獨佔鎖(Exclusive Lock):
也叫作寫鎖(X鎖,排它鎖)資源只容許當前事務讀寫,其餘事務的任何操做都不容許,添加以後一直到事務結束時才釋放;當資源上已經有其餘鎖的時候沒法添加獨佔鎖;插入、更新和刪除操做自動添加獨佔鎖。mvc
共享鎖(Shared Lock):分佈式
也叫作讀鎖(S鎖)資源容許當前事務讀寫,容許其餘事務的讀操做,不容許其餘事務的寫操做,讀操做結束後共享鎖就當即釋放;查詢操做自動添加共享鎖。性能
悲觀鎖(Pessimistic Lock):
事務認爲數據處理過程當中都須要鎖定數據,防止其餘racing process的修改,通常都是依靠數據庫鎖機制實現,包含表鎖、行鎖等,一旦觸發確定會引發線程的阻塞等待
樂觀鎖(Optimistic Lock):
事務認爲數據處理過程當中不須要鎖定數據;通常經過數據庫表的版本列實現,爲數據庫表添加一列version,讀取數據的時候同時讀取version數值;以後寫入的時候對version + 1並與當前DB中的version數值比較,若是大於數據庫表當前的版本號則進行更新,不然認爲是過時數據而不進行更新,其實也就是多版本併發控制(Multi-Version Cuoncurrency Controll)。
在Mysql的Innodb引擎中,數據庫表會額外添加一列version字段,僅在事務級別爲未提交讀和已提交讀的時候會開啓mvcc,在保證數據一致性的前提下提供最大程度的併發。每個racing process在讀取數據的時候會在原始數據的基礎上生成一條新的臨時數據,同時version +1,這條數據在事務提交前對其餘事務都是不可見的;一旦事務結束提交的時候會對比臨時數據跟原始數據的version版本號,若是臨時數據的version小於原始數據的version,則說明臨時數據已通過期,放棄或者從新讀取數據執行當前的操做。
分佈式鎖(Distributed Lock):
同一個進程內的多個線程之間能夠共享內存,所以基於共享內存的synchnorized或者lock機制均可以實現鎖,對於進程之間或者多臺server之間基於共享內存方式實現的鎖就再也不能知足要求。分佈式環境的鎖須要提供一個全部server均可以訪問到的存儲介質(DB或者內存),最原始的分佈式鎖設計源於DB,多個業務系統之間的同步經過向同一張DB table中插入一條具備惟一性鍵值的數據,成功插入數據的業務系統得到鎖,處理完本身的業務數據以後將DB table中的數據刪除,其餘業務系統就能夠繼續插入數據從而獲取鎖;能夠經過timer task解決死鎖問題,能夠在table中增長業務系統標識column解決鎖重入的問題;在併發量不是特別大,而且能保證DB系統足夠穩定的前提下,DB based的分佈式鎖能夠解決分佈式鎖的問題。
隨着併發業務量的上升和訪問延遲要求的提升,緩存鎖逐漸替代了數據庫鎖,以Redis RedLock和ZooKeeper Mutex爲表明的基於內存數據操做架構能夠保證多業務系統對同一項操做的併發執行,最終只會有一個業務系統操做成功,或者有一個前後順序(競爭排隊);RedLock的核心利用的是Redis的命令set NX EX,NX屬性表示僅當某個key不存在的時候才能成功set這個key,這樣racing process同時對同一個key執行setnx,最終只有一個process能夠設置成功,也表示獲取鎖成功;EX表示key值的過時時間,防止死鎖的狀況。
分佈式鎖主要用於處理在分佈式環境中多個racing process對同一個資源進行訪問時候的數據一致性問題,所以分佈式鎖須要知足五個特性:
#1 保證同一個方法在在同一時間點只能被一臺機器上的一個線程執行:
#2 保證鎖能夠正常的釋放:避免因加鎖的racing process服務下線形成鎖不能釋放的場景;DB based實現能夠經過timer task刪除過時的record,Redis based實現能夠經過setnx上設置key過時時間自動刪除鍵值,ZooKeeper based實現經過斷定session connection斷開自動刪除建立的節點。
#3 保證鎖的實現爲阻塞鎖:racing process須要等待獲取鎖以後才繼續往下執行業務;DB based實現和Redis based實現均可以經過while(true)循環實現等待(自旋鎖),但不夠優雅,ZooKeeper based實現經過event-watcher機制自動通知racing process鎖已經獲取。
#4 保證鎖是可重入的:已經得到了鎖的racing process能夠訪問被加了同一把鎖的其餘資源,避免自身死鎖;DB based實現可在record上增長一列lock holder info來辨識當前獲取鎖的racing process,Redis based實現可將value設置成lock holder info來辨識,ZooKeeper based實現能夠在建立的node上記錄lock holder info。
#5 保證鎖的獲取和釋放服務具備高可用性能:避免單點故障問題,DB,Redis和ZooKeeper均可以集羣的方式提供分佈式鎖服務。
自旋鎖(Self-Spin Lock):racing process經過互斥同步實現訪問一致性的時候,掛起和恢復線程都須要轉到系統內核模式完成,頻繁的切換會給併發性能帶來壓力;同時,大多數共享數據的鎖定狀態都只須要很短的時間,業務的開銷甚至比系統切換開銷還低,因此racing process能夠暫時不用切換狀態,而只須要執行一段時間的忙循環(也就是繼續佔用CPU時間片),從而等待共享數據的鎖被釋放,這樣的策略就是自旋鎖。