鎖住餘額,為何還會更新異常?

来源:https://www.cnblogs.com/goodAndyxublog/archive/2019/04/05/10660434.html
-Advertisement-
Play Games

背景 現有一個交易系統,每次交易都會更新餘額。出賬扣減餘額,入賬增加餘額。為了保證資金安全,餘額發生扣減時,需要比較現有餘額與扣減金額大小,若扣減金額大於現有餘額,扣減餘額不足,扣減失敗。 餘額表( 省去其他欄位 )結構如下: sql CREATE TABLE ( bigint(20) NOT NU ...


背景

現有一個交易系統,每次交易都會更新餘額。出賬扣減餘額,入賬增加餘額。為了保證資金安全,餘額發生扣減時,需要比較現有餘額與扣減金額大小,若扣減金額大於現有餘額,扣減餘額不足,扣減失敗。

餘額表(省去其他欄位)結構如下:


CREATE TABLE `account`
(
  `id`      bigint(20) NOT NULL,
  `balance` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_bin;

更新餘額方法語序如下:

更新餘額 sql 語序

由於存在併發更新餘額的情況,在 t3 時刻,使用寫鎖鎖住該行記錄。這樣就能保證事務執行期間不會有其他事務提交變更。

現在我們假設有兩個事務正在發執行該語序,執行順序如圖所示。

併發語序

假設 id=1 記錄 balance=1000,事務隔離等級為 RR。小伙伴們可以根據這個執行時序可以先思考下 t3,t5,t6,t7 結果。

註: 以上時序,順序執行。但是事務 1 執行到 t3 時刻,t4 時刻,事務 2 執行時將會被阻塞,後續無法執行。
t4 時刻之後,只能先將 事務 1 語序執行,事務提交完成後,才能執行 事務 2 剩餘語句。

下麵放出問題的答案。

t3 (1,1000)

t5 (1,1000)

t4 (1,900)

t6 (1,1000)

有沒有跟你結果的不太一樣?

事務 1 查詢結果基本沒什麼問題,事務 2 同一個事務內查詢結果卻不同。

現在我們先帶著疑問,看完下麵 MySQL 的相關原理,你就會明白一切。

相關原理

MVCC

假設在 RR 下,下圖 id=1 balance=1000

更新時序

上圖時序順序可以執行

事務 1 將 id=1 記錄 balance 更新為 900。然後 t5 查詢結果肯定還是 id=1 balance=1000,不然就讀取到臟數據,不符合當前事務隔離級別。

從上面例子可以看到 id=1 的記錄存在兩個版本,一個為 balance=1000 ,一個為 balance=900

MySQL 使用 MVCC 實現該功能。

MVCC:Multiversion concurrency control,多版本併發控制。摘錄一段淘寶資料庫月報的解釋:

多版本控制: 指的是一種提高併發的技術。最早的資料庫系統,只有讀讀之間可以併發,讀寫,寫讀,寫寫都要阻塞。引入多版本之後,只有寫寫之間相互阻塞,其他三種操作都可以並行,這樣大幅度提高了InnoDB的併發度。在內部實現中,與Postgres在數據行上實現多版本不同,InnoDB是在undolog中實現的,通過undolog可以找回數據的歷史版本。找回的數據歷史版本可以提供給用戶讀(按照隔離級別的定義,有些讀請求只能看到比較老的數據版本),也可以在回滾的時候覆蓋數據頁上的數據。在InnoDB內部中,會記錄一個全局的活躍讀寫事務數組,其主要用來判斷事務的可見性。

可以看到 MVCC 主要用來提高併發,還可以用來讀取老版本數據。下麵介紹 MVCC 實現的原理。

首先我們先看下 MySQL 記錄結構。

行記錄

可以看到 MySQL 行記錄除了真實數據以外,還會存在三個隱藏欄位,用來記錄額外信息。

DB_TRX_ID:事務id。
DB_ROLL_PTR: 回滾指針,指向 undolog。
ROW_ID:行 id,與此次無關。

具體行記錄結構,可以參考掘金的小冊『 MySQL 是怎樣運行的:從根兒上理解 MySQL』,說實話小冊寫的真的很好,收益頗豐。哈哈。

MySQL 通過 DB_ROLL_PTR 找到 undolog,而 undolog 記錄數據的變更。這樣 MySQL 就能推導出變更之前記錄內容。

查找過程如下:

查找過程

若需要知道 V1 版本記錄,首先根據當前版本 V3 的 DB_ROLL_PTR 找到 undolog,然後根據 undolog 內容,計算出上一個版本 V2。以此類推,最終找到 V1 這個版本記錄。

V1,V2 並不是物理記錄,沒有真實存在,僅僅具有邏輯意義。

一行數據記錄可能同時存在多個版本,但並不是所有記錄都能對當前事務可見。不然上面 t5 就可能查詢到最新的數據。所以查找數據版本時候 MySQL 必須判斷數據版本是否對當前事務可見。

一致性視圖

MySQL 會在事務開始後建立一個一致性視圖(並不是立刻建立),在這個視圖中,會保存所有活躍的事務(還未提交的事務)。

假設當前事務創建活躍事務數組為如下圖。

視圖數組

判斷記錄版本對於當前事務是否可見時,基於以下規則判斷:

  1. 若記錄版本事務 id 小於當前活躍事務 id 數組最小值,如 id 為 40,小於 45,代表當前記錄版本的事務已提交,當前記錄對於當前事務可見。
  2. 若記錄版本事務 id 大於當前活躍事務數組的最大值,如記錄版本事務 id 為 100, 大於數組最大事務 id 90。說明瞭這個記錄版本是當前事務創建之後生成,所以記錄對於當前事務不可見。
  3. 若記錄版本事務 id 是當前活躍數組事務之一,如記錄數據版本事務 id 為 56。代表記錄版本所屬事務還未提交,所以記錄對於當前事務不可見。
  4. 若記錄版本事務 id 不是當前活躍數組事務之一,但是事務 id 位於數組最小值與最大值之一。如記錄事務 ID 57。代表當前記錄事務已提交,所以記錄對於當前事務可見。
  5. 若記錄版本事務 id 為當前事務 id,代表該行數據是當前事務變更的,當然得可見。

4 這個規則可能比較繞,結合上面圖片比較好理解。

以上判斷規則可能比較抽象,我們將其總結下麵幾句話。

  1. 未提交事務生成的記錄版本,不可見。
  2. 視圖生成前,提交事務生成記錄版本可見。
  3. 視圖生成後,提交事務生成記錄版本不可見。
  4. 自身事務更新永遠可見。

一致性視圖只會在 RR 與 RC 下才會生成,對於 RR 來說,一致性視圖會在第一個查詢語句的時候生成。而對於 RC 來說,每個查詢語句都會重新生成視圖。

當前讀與快照讀

MySQL 使用 MVCC 機制,可以 讀取之前版本數據。這些舊版本記錄不會且也無法再去修改,就像快照一樣。所以我們將這種查詢稱為快照讀。

當然並不是所有查詢都是快照讀,select .... for update/ in share mode 這類加鎖查詢只會查詢當前記錄最新版本數據。我們將這種查詢稱為當前讀。

問題分析

講完原理之後,我們回過頭分析一下上面查詢結果的原因。

這裡我們將上面答案再貼過來。

答案

事務隔離級別為 RR,t1,t2 時刻兩個事務由於查詢語句,分別建立了一致性視圖。

t3 時刻,由於事務 1 使用 select.. for update 為 id=1 這一行上了一把寫鎖,然後獲取到最新結果。而 t4 時刻,由於該行已被上鎖,事務 2 必須等待事務 1 釋放鎖才能繼續。

t5 時刻根據一致性視圖,不能讀取到其他事務提交的版本,所以數據沒變。t7 時刻餘額扣減 100,t8 時刻提交事務。

此時最新版本記錄為 id=1 balance=900

由於事務 1 事務提交,行鎖被釋放,t4 獲取到寫鎖。由於 t4 是當前讀,所以查詢的結果為最新版本數據(1,900)。

重點來了。t6 查詢時,id=1 這條記錄最新版本數據為 (1,900)。但是最新版本事務 id,屬於事務 2創建之後未提交的事務,位於活躍事務數組中。所以最新記錄版本對於事務2 是不可見的。沒辦法只能根據 undolog 去讀取上一版本記錄 (1,1000) 。這個版本記錄剛好對於事務 2 可見。

若當前事務隔離級別修改成 RC,那麼結果就與 RR 不同。各位讀者自行分析一下。

下麵貼一下 RC 答案。

RC 下結果

幫助文檔

mysql mvcc
淘寶月報
innodb 相關實現
consistent-read
極客時間- MySQL 專欄--事務到底是隔離的還是不隔離的


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

-Advertisement-
Play Games
更多相關文章
  • 原文地址:http://www.entityframeworktutorial.net/code-first/table-dataannotations-attribute-in-code-first.aspx Table特性可以應用於一個領域類上面,用來在資料庫中生成相應名稱的數據表。它重寫了EF ...
  • System.Objec時C#中所有類型的基類,也就是萬類之源。 一、值類型 值類型都繼承自System.ValueType(派生自System.Objec),繼承自System.ValueType的類型在CLR中具有特殊的行為,值類型變數直接包含它們的值。對於值類型變數,沒有單獨的堆分配或垃圾回收 ...
  • 日常編程中經常用到++i與i++,知識點雖然很小,但有時候會犯迷糊,在這裡小小的記錄一下。 ++i 即前遞增,顧名思義也就是先自增後傳值; 舉個慄子 此時i的值為6,j的值也為6。 i++即後遞增,顧名思義也就是先傳值後自增 舉個慄子 此時i的值為6,j的值為5。 ...
  • 一丶前言 看過一些描述關於AOP切麵編程的文章,寫的太概念化讓人很難理解,下麵是我自己的理解,希望能幫到新人,如有錯誤歡迎指正。 二丶AOP是什麼,它的應用場景是什麼? AOP也跟IOC,OOP這些思想一樣它只是一種編程思想。Autofac、Spring.Net、Castle這些組件實現了AOP切麵 ...
  • [TOC] 一、進程相關的概念 進程需要瞭解 進程,父進程,進程組,會話和控制終端的相關概念。 1. 進程和父進程:每個進程都有父進程,而所有的進程以init進程為根,形成一個樹狀結構 2. 進程組:每個進程都會屬於一個進程組(process group),每個進程組中可以包含多個進程。進程組會有一 ...
  • SSH服務與tcp wrappers實驗 實驗環境: 一臺linux(ssh client) 一臺linux(ssh server) 實驗步驟: 1.配置IP,測試連通性 2.在客戶端創建用戶yuzly1,登錄創建的用戶,用公鑰生成工具生成公鑰,#註意記得輸入私鑰密語,不設置預設為空 3.查看生成的 ...
  • FTP是有兩種數據連接模式的,主動模式和被動模式。 PORT(主動)方式:客戶端向伺服器的FTP埠(預設是21)發送連接請求,伺服器接受連接,建立一條命令鏈路。當需要傳送數據時,客戶端在命令鏈路上用PORT命令告訴伺服器:“我打開了XXXX埠,你過來連接我”。於是伺服器從20埠向客戶端的XXX ...
  • Laravel 提供了很多 輔助函數,有時候我們也需要創建自己的輔助函數。 這裡介紹了 tinker,一個laravel內置的php互動式控制台,方便調試php代碼 php artisan tinker 我們把所有的『自定義輔助函數』存放於 bootstrap/helpers.php 文件中 首先 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...