MySQL是如何實現事務的隔離級別

来源:https://www.cnblogs.com/yousheng/archive/2020/05/23/12944218.html
-Advertisement-
Play Games

摘要 本文旨在瞭解MySQL InnoDB引擎如何支持事務的隔離級別。 文章主要內容分兩個部分。 第一部分闡述資料庫的併發問題以及為之產生的ANSI SQL 標準隔離級別。 第二部分根據 MySQL 官方文檔解釋 InnoDB 是如何支持這些隔離級別的。 資料庫事務的併發問題 ANSI SQL 隔離 ...


摘要

本文旨在瞭解MySQL InnoDB引擎如何支持事務的隔離級別。
文章主要內容分兩個部分。
第一部分闡述資料庫的併發問題以及為之產生的ANSI SQL 標準隔離級別。
第二部分根據 MySQL 官方文檔解釋 InnoDB 是如何支持這些隔離級別的。

資料庫事務的併發問題

ANSI SQL 隔離級別的定義是來源於三個異象問題,ANSI SQL在權衡系統的可靠性和性能之間定義了不同的級別。所以這裡先介紹主流的三個併發問題是什麼。

讀異象 (read phenomena)

  • 臟讀 (dirty read)
    • 一個事務讀取到了另一個事務更新但未提交的數據。當另一個事務回滾或者再次修改,就會讀取到臟數據
  • 不可重覆讀 (non-repeatable read / Fuzzy Read)
    • 現象為一個事務多次同樣讀取數據的操作其結果不一致。例如讀取時有其他事務提交了修改
  • 幻讀 (phantom)
    • 在多次同樣的查詢操作下,後面的查詢出現了新行的數據。
    • 例如在執行多次查詢的時候,其他事務插入了一個新行或者修改了某行數據使得能匹配上Where條件,那麼後一次查詢必然將查詢到這個新數據(也就感覺出現了幻覺,莫名其妙多出了一行)
    • 不可重覆讀和幻讀其實類似,但是幻讀偏向於查詢新增數據(所以專門弄了間隙鎖來防止幻讀),不可重覆讀則是修改數據。

    Def: The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.

    • 註:幻象 (Phantom) ,幻讀 (Phantom Read) ,幻象問題 (Phantom Problem) 都是一個表述。
  • 總結:上面介紹的三個讀併發問題,其本質都是一個事務在讀其他事務修改過的數據。

標準事務隔離級別

  • 因為事務在併發執行的過程中會存在相互干擾,需要有隔離性的保障,故引入事務隔離級別規範,以平衡操作的性能、可靠和一致

ANSI SQL下規定的隔離級別(1992 - 很老的標準了)

  1. 未提交讀 - Read Uncommited
    • 風險挺高,但是如果只是存粹的讀操作可以推薦使用(MyISAM也挺香呀)
  2. 已提交讀 - Read Commited(互聯網主流預設使用的隔離級別)
    • 事務無法看見其他未提交事務的修改
  3. 可重覆讀 - Repeatable Read (MySQL 預設)
    • 只讀事務開始時的快照數據
  4. 可序列化 - Serializable
  • ANSI SQL規定的隔離級別其實還是根據主流存在的資料庫併發問題來定義的,每個隔離級別來解決不同的問題。
    如果細分繼續細分不同的併發問題,隔離級別還能更加細化。
  • ANSI SQL Isolation Levels Defined in terms of the Three Original Phenomena

MySQL是如何支持不同隔離級別的

知識準備(術語解釋)

  • 一致性讀取 (consistent read)
    • 事務在進行讀操作時,使用的是事務開始時的行快照數據,這樣就不用擔心讀到其他其他事務修改的數據。
    • 在[可重覆讀]下,事務快照是基於第一次讀操作的快照(通過undo log 回溯)
    • 在[可提交讀]下,每一次一致性讀操作都會重置快照
    • 優點:不上鎖,允許其他事務進行修改
  • 半一致性讀取 (semi-consistent read)
    • UPDATE語句中的讀/匹配操作,當UPDATE語句執行的時候,InnoDB會取最後一次提交到MySQL的數據來進行 Where 子句中的匹配。
      • 如果匹配上了(也就是要更新),那就重讀該行並加鎖(或等待加鎖)
        • todo 為什麼要重讀該行不是很理解,直接用那條數據不就好了
    • 僅用於[可提交讀]隔離級別
    • 個人理解其實就是一致性讀取在[可提交讀]隔離級別下 UPDATE的表現
  • 鎖定讀 (locking read)
    • 即加鎖的查詢語句
    • E.g SELECT ... FOR UPDATE | SELECT ... FOR SHARE
  • 行鎖 (record lock) : 即鎖定索引記錄的鎖,即使沒有索引也會找到對應行記錄鎖主鍵哦
  • 間隙鎖 (gap lock) : 鎖在了兩條索引記錄之間的鎖,或者(無窮小,某索引)/(某索引,無窮大),他們鎖住的是一個範圍,且不同的間隙鎖不互斥,他們排斥的只是在鎖範圍內的插入操作
    • mark: R.C 隔離級別下是被禁用的
  • next-key lock : 行鎖和間隙鎖的組合實現

未提交讀 - Read Uncommited

  • SELECT 語句採用的無鎖策略,也就更容易產生臟讀

已提交讀 - Read Commited

  • 該隔離級別下的一致性讀取,讀的都是最新版的快照,每次讀快照都會被重置成最新

    Each consistent read, even within the same transaction, sets and reads its own fresh snapshot.

  • 對於Locking Reads、UPDATE、DELETE 語句,InnoDB的鎖策略是只鎖索引記錄,並沒有用間隙鎖來鎖範圍,所以容易產生幻讀問題

特別的對於 R.C 隔離級別有以下變動

  • 對於UPDATE語句,如果行已經被加鎖了,InnoDB會使用 “semi-consistent”read,來讀取快照中最新的值(而不是最原始的快照)來進行 WHERE 匹配更新。(也就是說 UPDATE 也採用 R.C 特供版一致性讀取)
  • 對於UPDATE或 DELETE語句,InnoDB僅對其更新或刪除的行持有鎖。MySQL評估WHERE條件後,將釋放不匹配行的記錄鎖。這大大降低了死鎖的可能性。
    • 比如掃表過程中,掃到這行不是要修改的,那麼就釋放鎖,要改的就一直加鎖直到事務結束
    • R.R 級別下則不會釋放

關於這個“semi-consistent” read

  • 他讀取的是最後一次提交到MySQL的版本,而不是快照中最早讀的那個版本
  • 優點:MySQL可以判斷當前的更新操作是否符合Where條件,如果不匹配就不用更新了,也就節省了一次寫操作,如果匹配就重新讀取嘗試加鎖
  • 缺點:如果都採用一致性讀consistent read,讀取都是最早事務的快照,就不會產生幻象問題了(Phantom Problem);但是採用了半一致性,兩次查詢的結果可能會不一致。

可重覆讀 - Repeatable Read

  • 採用一致性讀取,同事務中的每次讀取都取第一次讀的快照。(R.R 版一致性讀取)

    Consistent reads within the same transaction read the snapshot established by the first read.

  • 對於 locking reads,UPDATE,DELETE ,其加鎖策略取決於是否是唯一索引唯一條件查詢
    • 唯一索引配合唯一查詢條件,引擎只鎖定那條索引記錄,不鎖間隙
    • 其他場景,引擎使用Gap Lock 或 Next-Record Lock來鎖定掃描的索引範圍,以阻止其他事務插入新行到該間隙

串列化 - Serializable

  • InnoDB 默默的把所有純 SELECT 語句都轉成了 SELECT ... FOR SHARE ,也就預設都加讀鎖

小結

  • InnoDB 是依賴於不同的鎖策略實現了不同隔離級別的要求
  • R.C 隔離級別使用了當前讀 (R.C 下的一致性讀取) 且 禁用了間隙鎖
  • 理論上 R.R 隔離級別下因為使用了一致性讀和間隙鎖是不會產生的幻讀問題的,所以標準可能有點老了
    • todo MySQL在介紹幻讀的時候說 R.R 下麵會有幻讀,但又沒有實際的例子,很難讓人信服。(除非這個也算:兩次查詢前一次是普通讀,後一次是鎖定讀)

擴展

  • 在介紹 ANSI 隔離級別標準的時候,提到了ANSI標準是根據主流的三個併發問題來對症下藥的,但其實仍然有一些併發問題被 ANSI 標準給預設忽略或者說就不關心了,比如典型的臟讀和丟失修改。更詳細的可以參考下圖(1995)
    • Isolation Types Characterized by Possible Anomalies Allowed.

參考文獻


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

-Advertisement-
Play Games
更多相關文章
  • 存儲過程概述 存儲過程是完成特定功能的一組SQL語句,它是資料庫的一種可編程對象,類似於函數 其快速、靈活、安全 系統存儲過程 SQL sever 內置的存儲過程,存儲在master庫中,主要用於執行SQL sever的某些功能、顯示有關資料庫和用戶的信息。系統存儲過程名以sp_開頭,可以在任何數據 ...
  • List<Recipe> randomRecipe(@Param("str")String[] strs,@Param("num")int num); 功能:隨機查詢num個不含strs[]的的Recipe 註:str:["蘋果","甘藍"] num:5 <select id="randomReci ...
  • T-SQL 基本語句 註釋:單行註釋: -- 多行註釋:/* ..... */ 定義批處理結束語句:go 輸出語句:print(表達式) 數據類型 變數 運算符 函數 4.轉換函數 cast(表達式 as 數據類型):將某種數據類型的表達式顯示轉換為另一種數據類型 convert(數據類型[(長度) ...
  • sysdatabases表 sysdatabases是在master資料庫中的表,是一個只讀的表,裡面包含所有資料庫的信息 在創建資料庫是進行存在性檢測 use master go if exists(select * from sysdatabases where name='資料庫名') dro ...
  • 視圖:只有結構沒有數據 視圖是在數據表基礎上定義的一個虛擬表,在打開視圖是從數據表提取查詢結果 一個視圖是從特定的角度查看資料庫的方式、 限定不同用戶能看到的資料庫的範圍 維護數據安全的一種方式 視圖可以修改資料庫里的數據 視圖中只負責存儲select命令,不保存數據,不占據物理物理空間 創建視圖 ...
  • 將查詢結果生成新表 在同一資料庫中 select <表達式列表> into <新表名> form 原來的表 select * into 新表名 form 原來的表 基於多表查詢的DML update 表名 set 欄位=‘’ where 子查詢update 表名 set 欄位=‘’ from 表1 ...
  • 基本數據檢索:單表 複雜數據檢索:多表:連接查詢、子查詢(嵌套查詢)、集合運算 基本select語句: select <檢索欄位> from <表> where <檢索條件> group by<分類> having<檢索條件> order by <排序欄位> 操縱列: 1.輸出所有列:select ...
  • 事務的使用方式 事務的錯誤處理 WATCH命令 生存時間 緩存策略 Redis中的事務(transaction)是一組命令的集合。事務同命令一樣都是Redis的最小執行單位,一個事務中的命令要麼都執行,要麼都不執行。 事務的原理是先將屬於一個事務的命令發送給Redis,然後再讓Redis依次執行這些 ...
一周排行
    -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中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...