如何實現資料庫讀一致性

来源:https://www.cnblogs.com/Jcloud/archive/2022/09/27/16731569.html
-Advertisement-
Play Games

1 導讀 數據的一致性是數據準確的重要指標,那如何實現數據的一致性呢?本文從事務特性和事務級別的角度和大家一起學習如何實現數據的讀寫一致性。 2 一致性 1.數據的一致性:通常指關聯數據之間的邏輯關係是否正確和完整。 舉個例子:某系統實現讀寫分離,讀資料庫是寫資料庫的備份庫,小李在系統中之前錄入的學 ...


1 導讀

數據的一致性是數據準確的重要指標,那如何實現數據的一致性呢?本文從事務特性和事務級別的角度和大家一起學習如何實現數據的讀寫一致性。

2 一致性

1.數據的一致性:通常指關聯數據之間的邏輯關係是否正確和完整。

舉個例子:某系統實現讀寫分離,讀資料庫是寫資料庫的備份庫,小李在系統中之前錄入的學歷信息是高中,經過小李努力學習,成功獲得了本科學位。小李及時把信息變成成了本科,可是由於今天系統備份時間較長,小李變更信息時,數據已經開始備份。公司的HR通過系統查詢小李信息時,發現還是本科,小李的申請被駁回。這就是數據不一致問題。

2.資料庫的一致性:是指資料庫從一個一致性狀態變到另一個一致性狀態。這是事務的一致性的定義。

舉個例子:倉庫中商品A有100件,門店中商品A有10件。上午10點,倉庫發送商品A50件到門店,最後倉庫中有商品A50件,門店有商品A60件,這樣商品的總是是不變的。不能門店收到貨後,倉庫的商品A還是100件,這樣就出現資料庫不一致問題。倉庫和門店商品A的總數是110才是正確的,這就是資料庫的一致性。

3 資料庫事務

資料庫事務( transaction)是訪問並可能操作各種數據項的一個資料庫操作序列,這些操作要麼全部執行,要麼全部不執行,是一個不可分割的工作單位。事務由事務開始與事務結束之間執行的全部資料庫操作組成。

事務的性質:

  • 原子性(Atomicity):事務中的全部操作在資料庫中是不可分割的,要麼全部完成,要麼全部不執行。
  • 一致性(Consistency):幾個並行執行的事務,其執行結果必須與按某一順序 串列執行的結果相一致。
  • 隔離性(Isolation):事務的執行不受其他事務的干擾,事務執行的中間結果對其他事務必須是透明的。
  • 持久性(Durability):對於任意已提交事務,系統必須保證該事務對資料庫的改變不被丟失,即使資料庫出現故障

4 併發問題

資料庫在併發環境下會出現臟讀、重覆讀和幻讀問題。

1.臟讀

事務A讀取了事務B未提交的數據,如果事務B回滾了,事務A讀取的數據就是髒的。
舉例:訂單A需要商品A20件,訂單B需要商品A10件。倉庫中有商品A庫存是20件。訂單B先查詢,發現庫存夠,進行扣減。在扣減的過程中,訂單A進行查詢,發現庫存只有10個不夠訂單數量,拋出異常。這時候訂單B提交失敗了。庫存數量又變成20了。這時候,倉庫人員去查庫存,發現數量是20,可是訂單A卻說庫存不足,這就讓人很奇怪。

2.不可重覆讀

復讀指的是在一個事務內,最開始讀到的數據和事務結束前的任意時刻讀到的同一批數據出現不一致的情況。
舉例:庫房管理員查詢商品A的數量,讀取結果是20件。這是訂單A出庫,扣減了商品10件。這時管理員再去查商品A時,發現商品A的數量時10件和第一此查詢的結果不同了。

3.幻讀

事務A在執行讀取操作,需要兩次統計數據的總量,前一次查詢數據總量後,此時事務B執行了新增數據的操作並提交後,這個時候事務A讀取的數據總量和之前統計的不一樣,就像產生了幻覺一樣,平白無故的多了幾條數據,成為幻讀。
舉例:操作員查詢可生產單量10個,調用介面下發10個訂單,事務A增加10個訂單。操作員獲取10個訂單落庫,查詢 發現變成30個訂單。

5 事務隔離級別

Read Uncommitted(未提交讀)
一個事務可以讀取到其他事務未提交的數據,會出現臟讀,所以叫做 RU,它沒有解決任何的問題。

Read Committed(已提交讀)
一個事務只能讀取到其他事務已提交的數據,不能讀取到其他事務未提交的數據,它解決了臟讀的問題,但是會出現不可重覆讀的問題。

Repeatable Read(可重覆讀)
它解決了不可重覆讀的問題,也就是在同一個事務裡面多次讀取同樣的數據結果是一樣的,但是在這個級別下,沒有定義解決幻讀的問題。

Serializable(串列化)
在這個隔離級別裡面,所有的事務都是串列執行的,也就是對數據的操作需要排隊,已經不存在事務的併發操作了,所以它解決了所有的問題。

6 解決數據讀一致性

有兩個方案可以解決讀一致性問題:基於鎖的併發操作(LBCC)和基於多版本的併發操作(MVCC)

6.1 LBCC

既然要保證前後兩次讀取數據一致,那麼讀取數據的時候,鎖定我要操作的數據,不允許其他的事務修改就行了。這種方案叫做基於鎖的併發控制 Lock Based Concurrency Control(LBCC)。

LBCC是通過悲觀鎖來實現併發控制的。

如果事務A對數據進行加鎖,在鎖釋放前,其他事務就不能對數據進行讀寫操作。這樣併發調用,改成了順序調用。對目前的大多數系統來說,性能完全不能滿足要求。

6.2 MVCC

要讓一個事務前後兩次讀取的數據保持一致,那麼我們可以在修改數據的時候給它建立一個備份或者叫快照,後面再來讀取這個快照就行了。不管事務執行多長時間,事務內部看到的數據是不受其它事務影響的,根據事務開始的時間不同,每個事務對同一張表,同一時刻看到的數據可能是不一樣的。這種方案我們叫做多版本的併發控制 Multi Version Concurrency Control(MVCC)。

MVCC是基於樂觀鎖的。

在 InnoDB 中,MVCC 是通過Undo log中的版本鏈和Read-View一致性視圖來實現的。

6.2.1 Undo log

undo log是innodb引擎的一種日誌,在事務的修改記錄之前,會把該記錄的原值先保存起來再做修改,以便修改過程中出錯能夠恢複原值或者其他的事務讀取。undo log是一種用於撤銷回退的日誌,在事務沒提交之前,MySQL會先記錄更新前的數據到 undo log日誌文件裡面,當事務回滾時或者資料庫崩潰時,可以利用 undo log來進行回退。

對數據變更的操作不同,undo log記錄的內容也不同:

  • 新增一條記錄的時候,在創建對應undo日誌時,只需要把這條記錄的主鍵值記錄下來,如果要回滾插入操作,只需要根據對應的主鍵值對記錄進行刪除操作。
  • 刪除一條記錄的時候,在創建對應undo日誌時,需要把這條數據的所有內容都記錄下來,如果要回滾刪除語句,需要把記錄的數據內容生產相應的insert語句,並插入到資料庫中。
  • 更新一條記錄的時候,如果沒有更新主鍵,在創建對應undo日誌時,如果要回滾更新語句,需要把變更前的內容記錄下來,如果要回滾更新語句,需要根據主鍵,把記錄的數據更新回去。
  • 更新一條記錄的時候,如果有更新主鍵,在創建對應undo日誌時,需要把數據的所有內容都記錄下來,如果要回滾更新語句,先把變更後的數據刪掉,再執行插入語句,把備份的數據插入到資料庫中。

undo log版本鏈

每條數據有兩個隱藏欄位,trx_id 和 roll_pointer,trx_id表示最近一次事務的id,roll_pointer表示指向你更新這個事務之前生成的undo log。
事務ID:MySQL維護一個全局變數,當需要為某個事務分配事務ID時,將該變數的值作為事務id分配給事務,然後將變數自增1。

舉例:

  • 事務A id是1 插入一條數據X,這條數據的trx_id =1 ,roll_pointer 是空(第一次插入)。
  • 事務B id 是2 對這條數據進行了更新,這條數據的 trx_id =2 ,roll_pointer 指向 事務A的undo log.
  • 事務C id 是3 又對數據進行了更新操作,這條數據的trx_id =3,roll_pointer 指向 事務B的undo log.

所以當多個事務串列執行的時候,每個事務修改了一行數據,都會更新隱藏欄位trx_id 和 roll_pointer,同時多個事務的undo log會通過roll_pointer指針串聯起來,形成undo log版本鏈。

6.2.2 Read-View一致性視圖

InnoDB為每個事務維護了一個數組,這個數組用來保存這個事務啟動的瞬間,當前活躍的事務ID。這個數組裡有兩個水位值: 低水位(事務ID 最小值)和 高水位(事務ID 最大值 + 1);這兩個水位值就構成了當前事務的一致性視圖(Read-View)

ReadView中主要包含4個比較重要的內容:

  • m_ids:表示在生成ReadView時當前系統中活躍的讀寫事務的事務id列表。
  • min_trx_id:表示在生成ReadView時當前系統中活躍的讀寫事務中最小的事務id,也就是m_ids中的最小值。
  • max_trx_id:表示生成ReadView時系統中應該分配給下一個事務的id值。
  • creator_trx_id:表示生成該ReadView的事務的事務id。

有了這些信息,這樣在訪問某條記錄時,只需要按照下邊的步驟判斷記錄的某個版本是否可見:

  • 如果被訪問版本的trx_id屬性值與ReadView中的creator_trx_id值相同,意味著當前事務在訪問它自己修改過的記錄,所以該版本可以被當前事務訪問。
  • 如果被訪問版本的trx_id屬性值小於ReadView中的min_trx_id值,表明生成該版本的事務在當前事務生成ReadView前已經提交,所以該版本可以被當前事務訪問。
  • 如果被訪問版本的trx_id屬性值大於ReadView中的max_trx_id值,表明生成該版本的事務在當前事務生成ReadView後才開啟,所以該版本不可以被當前事務訪問。
  • 如果被訪問版本的trx_id屬性值在ReadView的min_trx_id和max_trx_id之間,那就需要判斷一下trx_id屬性值是不是在m_ids列表中,如果在,說明創建ReadView時生成該版本的事務還是活躍的,該版本不可以被訪問;如不在,說明創建ReadView時生成該版本的事務已經被提交,該版本可以被訪問。
  • 如果某個版本的數據對當前事務不可見的話,那就順著版本鏈找到下一個版本的數據,繼續按照上邊的步驟判斷可見性,依此類推,直到版本鏈中的最後一個版本。如果最後一個版本也不可見的話,那麼就意味著該條記錄對該事務完全不可見,查詢結果就不包含該記錄。
6.2.3 數據的查找方式
1.快照讀

快照讀又叫一致性讀,讀取的是歷史版本的數據。不加鎖的簡單的SELECT都屬於快照讀,即不加鎖的非阻塞讀,只能查找創建時間小於等於當前事務ID的數據或者刪除時間大於當前事務ID的行(或未刪除)。

2.當前讀

當前讀查找的是記錄的最新數據。加鎖的SELECT、對數據進行增刪改都會進行當前讀。

6.2.4 數據舉例

如圖所示:

事務A id =1 初始化了數據
事務B id=2 進行了查詢操作(MVCC只讀取創建時間小於當前事務ID的數據或者刪除時間大於當前事務ID的行)
事務B的結果是 (商品A:10,商品B:5)

事務C id =3 插入了商品C
事務B id=2 進行了查詢操作(MVCC只讀取創建時間小於當前事務ID的數據或者刪除時間大於當前事務ID的行)
事務B的結果是 (商品A:10,商品B:5)

事務D id =4 刪除商品B
事務B id=2 進行了查詢操作(MVCC只讀取創建時間小於當前事務ID的數據或者刪除時間大於當前事務ID的行)
事務B的結果是 (商品A:10,商品B:5)

事務E id =4 修改商品A的數量
事務B id=2 進行了查詢操作(MVCC只讀取創建時間小於當前事務ID的數據或者刪除時間大於當前事務ID的行)
事務B的結果是 (商品A:10,商品B:5)

所以當事務E提交後,當前讀獲取的數據和事務B讀取的快照數據明顯不同。

6.2.5 可解決問題

MVCC可以很好的解決讀一致問題,只能看到這個時間點之前事務提交更新的結果,而不能看到這個時間點之後事務提交的更新結果。而且降低了死鎖的概率和解決讀寫之間堵塞問題。

7 小結

LBCC和MVCC都可以解決讀一致問題,具體使用哪種方式,要結合業務場景選擇最合適的方式,MVCC和鎖也可以結合使用,沒有最好只有更好。

作者:陳昌浩

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • tomcat 一、tomcat是什麼 Tomcat 伺服器是一個免費的開放源代碼的Web 應用伺服器,屬於輕量級應用伺服器,在中小型系統和併發訪問用戶不是很多的場合下被普遍使用,是開發和調試JSP 程式的首選。對於一個初學者來說,可以這樣認為,當在一臺機器上配置好Apache 伺服器,可利用它響應H ...
  • haproxy 部署haproxy haproxy源碼包下載網站地址 | 主機名稱 | IP地址 | 需要安裝的應用 | 系統版本 | | | | | | | LB | 192.168.111.141 | haproxy | centos 8 | | RS1 | 192.168.111.142 | ...
  • Redis Desktop Manager for Mac是Mac平臺上一款非常實用的Redis可視化工具。RDM支持SSL / TLS加密,SSH隧道,基於SSH隧道的TLS,為您提供了一個易於使用的GUI,可以訪問您的Redis資料庫並執行一些基本操作:將鍵視為樹,CRUD鍵,通過shell執行 ...
  • 如何能打開閱讀chm格式文件?使用CHM Reader for Mac即可直接狀態欄打開即可使用,非常方便。 詳情:iCHM Reader for Mac(chm格式文件閱讀器) iCHM Reader中文版是最終CHM(編譯的HTML幫助)文件閱讀器。可以讓你閱讀較大的CHM文檔,在mac系統中使 ...
  • 晶體結構軟體CrystalMaker for mac創建、顯示和操作各種晶體和分子結構 ,CrystalMaker Mac版便捷、靈活,能夠容易的載入結構數據並產生壯觀的,相片型的圖形,戴上紅/藍眼鏡,還可以感受立體三維畫面,親臨分子結構當中。 詳情:CrystalMaker for Mac(晶體結 ...
  • 電阻種類很多,常用的有貼片電阻、插件電阻、熱敏電阻、壓敏電阻、光敏電阻、水泥電阻、可調電阻。 可調電阻在成品的PCBA中很少見,也大多用於電路調試中試用,等電路調試完成後再換成固定阻值的電阻,起到電路參數調節的作用。水泥電阻則在調試的時候會用到更多,當做假負載來使用。 這裡說下假負載,假負載並不是電 ...
  • 準備工作 一臺Linux(Centos7為例)伺服器。 安裝Docker服務。 安裝並啟動SqlServer容器服務。 編寫Shell文件 給出一個備份的範例 #!/bin/bash #設置mssql備份目錄 folder=/var/opt/mssql/data/databack/ day=`dat ...
  • 風裡雨里,我在深圳機場等你,口說無憑,上圖! 這是一段很長的故事!以前倒也不曾提過~ 銀河證券和騰訊雲資料庫長久以來並肩作戰,情比金堅,我們的故事日前在深圳寶安機場上映 他說:做好國產化分散式改造,就用騰訊雲資料庫。 我說:做國產資料庫,我是認真的。 誕生於2007年的騰訊雲資料庫現已歷經十四年的錘 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...