『淺入淺出』MySQL 和 InnoDB

純潔的微笑發表於2019-01-25

作為一名開發人員,在日常的工作中會難以避免地接觸到資料庫,無論是基於檔案的 sqlite 還是工程上使用非常廣泛的 MySQL、PostgreSQL,但是一直以來也沒有對資料庫有一個非常清晰並且成體系的認知,所以最近兩個月的時間看了幾本資料庫相關的書籍並且閱讀了 MySQL 的官方文件,希望對各位瞭解資料庫的、不瞭解資料庫的有所幫助。

『淺入淺出』MySQL 和 InnoDB

本文中對於資料庫的介紹以及研究都是在 MySQL 上進行的,如果涉及到了其他資料庫的內容或者實現會在文中單獨指出。

資料庫的定義

很多開發者在最開始時其實都對資料庫有一個比較模糊的認識,覺得資料庫就是一堆資料的集合,但是實際卻比這複雜的多,資料庫領域中有兩個詞非常容易混淆,也就是資料庫例項

  • 資料庫:物理操作檔案系統或其他形式檔案型別的集合;

  • 例項:MySQL 資料庫由後臺執行緒以及一個共享記憶體區組成;

對於資料庫和例項的定義都來自於 MySQL 技術內幕:InnoDB 儲存引擎 一書,想要了解 InnoDB 儲存引擎的讀者可以閱讀這本書籍。

資料庫和例項

在 MySQL 中,例項和資料庫往往都是一一對應的,而我們也無法直接運算元據庫,而是要透過資料庫例項來運算元據庫檔案,可以理解為資料庫例項是資料庫為上層提供的一個專門用於操作的介面。

『淺入淺出』MySQL 和 InnoDB

在 Unix 上,啟動一個 MySQL 例項往往會產生兩個程式,mysqld 就是真正的資料庫服務守護程式,而 mysqld_safe 是一個用於檢查和設定 mysqld 啟動的控制程式,它負責監控 MySQL 程式的執行,當 mysqld 發生錯誤時,mysqld_safe 會對其狀態進行檢查並在合適的條件下重啟。

MySQL 的架構

MySQL 從第一個版本釋出到現在已經有了 20 多年的歷史,在這麼多年的發展和演變中,整個應用的體系結構變得越來越複雜:

『淺入淺出』MySQL 和 InnoDB

最上層用於連線、執行緒處理的部分並不是 MySQL 『發明』的,很多服務都有類似的組成部分;第二層中包含了大多數 MySQL 的核心服務,包括了對 SQL 的解析、分析、最佳化和快取等功能,儲存過程、觸發器和檢視都是在這裡實現的;而第三層就是 MySQL 中真正負責資料的儲存和提取的儲存引擎,例如:InnoDB、MyISAM 等,文中對儲存引擎的介紹都是對 InnoDB 實現的分析。

資料的儲存

在整個資料庫體系結構中,我們可以使用不同的儲存引擎來儲存資料,而絕大多數儲存引擎都以二進位制的形式儲存資料;這一節會介紹 InnoDB 中對資料是如何儲存的。

在 InnoDB 儲存引擎中,所有的資料都被邏輯地存放在表空間中,表空間(tablespace)是儲存引擎中最高的儲存邏輯單位,在表空間的下面又包括段(segment)、區(extent)、頁(page):

『淺入淺出』MySQL 和 InnoDB

同一個資料庫例項的所有表空間都有相同的頁大小;預設情況下,表空間中的頁大小都為 16KB,當然也可以透過改變 innodb_page_size 選項對預設大小進行修改,需要注意的是不同的頁大小最終也會導致區大小的不同:

『淺入淺出』MySQL 和 InnoDB

從圖中可以看出,在 InnoDB 儲存引擎中,一個區的大小最小為 1MB,頁的數量最少為 64 個。

如何儲存表

MySQL 使用 InnoDB 儲存表時,會將表的定義資料索引等資訊分開儲存,其中前者儲存在 .frm 檔案中,後者儲存在 .ibd 檔案中,這一節就會對這兩種不同的檔案分別進行介紹。

『淺入淺出』MySQL 和 InnoDB

.frm 檔案

無論在 MySQL 中選擇了哪個儲存引擎,所有的 MySQL 表都會在硬碟上建立一個 .frm 檔案用來描述表的格式或者說定義;.frm 檔案的格式在不同的平臺上都是相同的。

CREATE TABLE test_frm(
    column1 CHAR(5), 
    column2 INTEGER
);

當我們使用上面的程式碼建立表時,會在磁碟上的 datadir 資料夾中生成一個 test_frm.frm 的檔案,這個檔案中就包含了表結構相關的資訊:

『淺入淺出』MySQL 和 InnoDB

MySQL 官方文件中的 11.1 MySQL .frm File Format 一文對於 .frm 檔案格式中的二進位制的內容有著非常詳細的表述,在這裡就不展開介紹了。

.ibd 檔案

InnoDB 中用於儲存資料的檔案總共有兩個部分,一是系統表空間檔案,包括 ibdata1ibdata2 等檔案,其中儲存了 InnoDB 系統資訊和使用者資料庫表資料和索引,是所有表公用的。

當開啟 innodb_file_per_table 選項時,.ibd 檔案就是每一個表獨有的表空間,檔案儲存了當前表的資料和相關的索引資料。

如何儲存記錄

與現有的大多數儲存引擎一樣,InnoDB 使用頁作為磁碟管理的最小單位;資料在 InnoDB 儲存引擎中都是按行儲存的,每個 16KB 大小的頁中可以存放 2-200 行的記錄。

當 InnoDB 儲存資料時,它可以使用不同的行格式進行儲存;MySQL 5.7 版本支援以下格式的行儲存方式:

『淺入淺出』MySQL 和 InnoDB

Antelope 是 InnoDB 最開始支援的檔案格式,它包含兩種行格式 Compact 和 Redundant,它最開始並沒有名字;Antelope 的名字是在新的檔案格式 Barracuda 出現後才起的,Barracuda 的出現引入了兩種新的行格式 Compressed 和 Dynamic;InnoDB 對於檔案格式都會向前相容,而官方文件中也對之後會出現的新檔案格式預先定義好了名字:Cheetah、Dragon、Elk 等等。

兩種行記錄格式 Compact 和 Redundant 在磁碟上按照以下方式儲存:

『淺入淺出』MySQL 和 InnoDB

Compact 和 Redundant 格式最大的不同就是記錄格式的第一個部分;在 Compact 中,行記錄的第一部分倒序存放了一行資料中列的長度(Length),而 Redundant 中存的是每一列的偏移量(Offset),從總體上上看,Compact 行記錄格式相比 Redundant 格式能夠減少 20% 的儲存空間。

行溢位資料

當 InnoDB 使用 Compact 或者 Redundant 格式儲存極長的 VARCHAR 或者 BLOB 這類大物件時,我們並不會直接將所有的內容都存放在資料頁節點中,而是將行資料中的前 768 個位元組儲存在資料頁中,後面會透過偏移量指向溢位頁。

『淺入淺出』MySQL 和 InnoDB

但是當我們使用新的行記錄格式 Compressed 或者 Dynamic 時都只會在行記錄中儲存 20 個位元組的指標,實際的資料都會存放在溢位頁面中。

『淺入淺出』MySQL 和 InnoDB

當然在實際儲存中,可能會對不同長度的 TEXT 和 BLOB 列進行最佳化,不過這就不是本文關注的重點了。

想要了解更多與 InnoDB 儲存引擎中記錄的資料格式的相關資訊,可以閱讀 InnoDB Record Structure

資料頁結構

頁是 InnoDB 儲存引擎管理資料的最小磁碟單位,而 B-Tree 節點就是實際存放表中資料的頁面,我們在這裡將要介紹頁是如何組織和儲存記錄的;首先,一個 InnoDB 頁有以下七個部分:

『淺入淺出』MySQL 和 InnoDB

每一個頁中包含了兩對 header/trailer:內部的 Page Header/Page Directory 關心的是頁的狀態資訊,而 Fil Header/Fil Trailer 關心的是記錄頁的頭資訊。

在頁的頭部和尾部之間就是使用者記錄和空閒空間了,每一個資料頁中都包含 Infimum 和 Supremum 這兩個虛擬的記錄(可以理解為佔位符),Infimum 記錄是比該頁中任何主鍵值都要小的值,Supremum 是該頁中的最大值:

『淺入淺出』MySQL 和 InnoDB

User Records 就是整個頁面中真正用於存放行記錄的部分,而 Free Space 就是空餘空間了,它是一個連結串列的資料結構,為了保證插入和刪除的效率,整個頁面並不會按照主鍵順序對所有記錄進行排序,它會自動從左側向右尋找空白節點進行插入,行記錄在物理儲存上並不是按照順序的,它們之間的順序是由 next_record 這一指標控制的。

B+ 樹在查詢對應的記錄時,並不會直接從樹中找出對應的行記錄,它只能獲取記錄所在的頁,將整個頁載入到記憶體中,再透過 Page Directory 中儲存的稀疏索引和 n_ownednext_record 屬性取出對應的記錄,不過因為這一操作是在記憶體中進行的,所以通常會忽略這部分查詢的耗時。

InnoDB 儲存引擎中對資料的儲存是一個非常複雜的話題,這一節中也只是對錶、行記錄以及頁面的儲存進行一定的分析和介紹,雖然作者相信這部分知識對於大部分開發者已經足夠了,但是想要真正消化這部分內容還需要很多的努力和實踐。

索引

索引是資料庫中非常非常重要的概念,它是儲存引擎能夠快速定位記錄的秘密武器,對於提升資料庫的效能、減輕資料庫伺服器的負擔有著非常重要的作用;索引最佳化是對查詢效能最佳化的最有效手段,它能夠輕鬆地將查詢的效能提高几個數量級。

索引的資料結構

在上一節中,我們談了行記錄的儲存和頁的儲存,在這裡我們就要從更高的層面看 InnoDB 中對於資料是如何儲存的;InnoDB 儲存引擎在絕大多數情況下使用 B+ 樹建立索引,這是關係型資料庫中查詢最為常用和有效的索引,但是 B+ 樹索引並不能找到一個給定鍵對應的具體值,它只能找到資料行對應的頁,然後正如上一節所提到的,資料庫把整個頁讀入到記憶體中,並在記憶體中查詢具體的資料行。

『淺入淺出』MySQL 和 InnoDB

B+ 樹是平衡樹,它查詢任意節點所耗費的時間都是完全相同的,比較的次數就是 B+ 樹的高度;在這裡,我們並不會深入分析或者動手實現一個 B+ 樹,只是對它的特性進行簡單的介紹。

聚集索引和輔助索引

資料庫中的 B+ 樹索引可以分為聚集索引(clustered index)和輔助索引(secondary index),它們之間的最大區別就是,聚集索引中存放著一條行記錄的全部資訊,而輔助索引中只包含索引列和一個用於查詢對應行記錄的『書籤』。

聚集索引

InnoDB 儲存引擎中的表都是使用索引組織的,也就是按照鍵的順序存放;聚集索引就是按照表中主鍵的順序構建一顆 B+ 樹,並在葉節點中存放表中的行記錄資料。

CREATE TABLE users(
    id INT NOT NULL,
    first_name VARCHAR(20NOT NULL,
    last_name VARCHAR(20NOT NULL,
    age INT NOT NULL,
    PRIMARY KEY(id),
    KEY(last_name, first_name, age)
    KEY(first_name)
);

如果使用上面的 SQL 在資料庫中建立一張表,B+ 樹就會使用 id 作為索引的鍵,並在葉子節點中儲存一條記錄中的所有資訊。

『淺入淺出』MySQL 和 InnoDB

圖中對 B+ 樹的描述與真實情況下 B+ 樹中的資料結構有一些差別,不過這裡想要表達的主要意思是:聚集索引葉節點中儲存的是整條行記錄,而不是其中的一部分。

聚集索引與表的物理儲存方式有著非常密切的關係,所有正常的表應該有且僅有一個聚集索引(絕大多數情況下都是主鍵),表中的所有行記錄資料都是按照聚集索引的順序存放的。

當我們使用聚集索引對錶中的資料進行檢索時,可以直接獲得聚集索引所對應的整條行記錄資料所在的頁,不需要進行第二次操作。

輔助索引

資料庫將所有的非聚集索引都劃分為輔助索引,但是這個概念對我們理解輔助索引並沒有什麼幫助;輔助索引也是透過 B+ 樹實現的,但是它的葉節點並不包含行記錄的全部資料,僅包含索引中的所有鍵和一個用於查詢對應行記錄的『書籤』,在 InnoDB 中這個書籤就是當前記錄的主鍵。

輔助索引的存在並不會影響聚集索引,因為聚集索引構成的 B+ 樹是資料實際儲存的形式,而輔助索引只用於加速資料的查詢,所以一張表上往往有多個輔助索引以此來提升資料庫的效能。

一張表一定包含一個聚集索引構成的 B+ 樹以及若干輔助索引的構成的 B+ 樹。

『淺入淺出』MySQL 和 InnoDB

如果在表 users 中存在一個輔助索引 (first_name, age),那麼它構成的 B+ 樹大致就是上圖這樣,按照 (first_name, age) 的字母順序對錶中的資料進行排序,當查詢到主鍵時,再透過聚集索引獲取到整條行記錄。

『淺入淺出』MySQL 和 InnoDB

上圖展示了一個使用輔助索引查詢一條表記錄的過程:透過輔助索引查詢到對應的主鍵,最後在聚集索引中使用主鍵獲取對應的行記錄,這也是通常情況下行記錄的查詢方式。

索引的設計

索引的設計其實是一個非常重要的內容,同時也是一個非常複雜的內容;索引的設計與建立對於提升資料庫的查詢效能至關重要,不過這不是本文想要介紹的內容,有關索引的設計與最佳化可以閱讀 資料庫索引設計與最佳化 一書,書中提供了一種非常科學合理的方法能夠幫助我們在資料庫中建立最適合的索引,當然作者也可能會在之後的文章中對索引的設計進行簡單的介紹和分析。

我們都知道鎖的種類一般分為樂觀鎖和悲觀鎖兩種,InnoDB 儲存引擎中使用的就是悲觀鎖,而按照鎖的粒度劃分,也可以分成行鎖和表鎖。

併發控制機制

樂觀鎖和悲觀鎖其實都是併發控制的機制,同時它們在原理上就有著本質的差別;

  • 樂觀鎖是一種思想,它其實並不是一種真正的『鎖』,它會先嚐試對資源進行修改,在寫回時判斷資源是否進行了改變,如果沒有發生改變就會寫回,否則就會進行重試,在整個的執行過程中其實都沒有對資料庫進行加鎖

  • 悲觀鎖就是一種真正的鎖了,它會在獲取資源前對資源進行加鎖,確保同一時刻只有有限的執行緒能夠訪問該資源,其他想要嘗試獲取資源的操作都會進入等待狀態,直到該執行緒完成了對資源的操作並且釋放了鎖後,其他執行緒才能重新操作資源;

雖然樂觀鎖和悲觀鎖在本質上並不是同一種東西,一個是一種思想,另一個是一種真正的鎖,但是它們都是一種併發控制機制。

『淺入淺出』MySQL 和 InnoDB

樂觀鎖不會存在死鎖的問題,但是由於更新後驗證,所以當衝突頻率重試成本較高時更推薦使用悲觀鎖,而需要非常高的響應速度並且併發量非常大的時候使用樂觀鎖就能較好的解決問題,在這時使用悲觀鎖就可能出現嚴重的效能問題;在選擇併發控制機制時,需要綜合考慮上面的四個方面(衝突頻率、重試成本、響應速度和併發量)進行選擇。

鎖的種類

對資料的操作其實只有兩種,也就是讀和寫,而資料庫在實現鎖時,也會對這兩種操作使用不同的鎖;InnoDB 實現了標準的行級鎖,也就是共享鎖(Shared Lock)和互斥鎖(Exclusive Lock);共享鎖和互斥鎖的作用其實非常好理解:

  • 共享鎖(讀鎖):允許事務對一條行資料進行讀取;

  • 互斥鎖(寫鎖):允許事務對一條行資料進行刪除或更新;

而它們的名字也暗示著各自的另外一個特性,共享鎖之間是相容的,而互斥鎖與其他任意鎖都不相容:

『淺入淺出』MySQL 和 InnoDB

稍微對它們的使用進行思考就能想明白它們為什麼要這麼設計,因為共享鎖代表了讀操作、互斥鎖代表了寫操作,所以我們可以在資料庫中並行讀,但是隻能序列寫,只有這樣才能保證不會發生執行緒競爭,實現執行緒安全。

鎖的粒度

無論是共享鎖還是互斥鎖其實都只是對某一個資料行進行加鎖,InnoDB 支援多種粒度的鎖,也就是行鎖和表鎖;為了支援多粒度鎖定,InnoDB 儲存引擎引入了意向鎖(Intention Lock),意向鎖就是一種表級鎖。

與上一節中提到的兩種鎖的種類相似的是,意向鎖也分為兩種:

  • 意向共享鎖:事務想要在獲得表中某些記錄的共享鎖,需要在表上先加意向共享鎖;

  • 意向互斥鎖:事務想要在獲得表中某些記錄的互斥鎖,需要在表上先加意向互斥鎖;

隨著意向鎖的加入,鎖型別之間的相容矩陣也變得愈加複雜:

『淺入淺出』MySQL 和 InnoDB

意向鎖其實不會阻塞全表掃描之外的任何請求,它們的主要目的是為了表示是否有人請求鎖定表中的某一行資料

有的人可能會對意向鎖的目的並不是完全的理解,我們在這裡可以舉一個例子:如果沒有意向鎖,當已經有人使用行鎖對錶中的某一行進行修改時,如果另外一個請求要對全表進行修改,那麼就需要對所有的行是否被鎖定進行掃描,在這種情況下,效率是非常低的;不過,在引入意向鎖之後,當有人使用行鎖對錶中的某一行進行修改之前,會先為表新增意向互斥鎖(IX),再為行記錄新增互斥鎖(X),在這時如果有人嘗試對全表進行修改就不需要判斷表中的每一行資料是否被加鎖了,只需要透過等待意向互斥鎖被釋放就可以了。

鎖的演算法

到目前為止已經對 InnoDB 中鎖的粒度有一定的瞭解,也清楚了在對資料庫進行讀寫時會獲取不同的鎖,在這一小節將介紹鎖是如何新增到對應的資料行上的,我們會分別介紹三種鎖的演算法:Record Lock、Gap Lock 和 Next-Key Lock。

Record Lock

記錄鎖(Record Lock)是加到索引記錄上的鎖,假設我們存在下面的一張表 users

CREATE TABLE users(
    id INT NOT NULL AUTO_INCREMENT,
    last_name VARCHAR(255NOT NULL,
    first_name VARCHAR(255),
    age INT,
    PRIMARY KEY(id),
    KEY(last_name),
    KEY(age)
);

如果我們使用 id 或者 last_name 作為 SQL 中 WHERE 語句的過濾條件,那麼 InnoDB 就可以透過索引建立的 B+ 樹找到行記錄並新增索引,但是如果使用 first_name 作為過濾條件時,由於 InnoDB 不知道待修改的記錄具體存放的位置,也無法對將要修改哪條記錄提前做出判斷就會鎖定整個表。

Gap Lock

記錄鎖是在儲存引擎中最為常見的鎖,除了記錄鎖之外,InnoDB 中還存在間隙鎖(Gap Lock),間隙鎖是對索引記錄中的一段連續區域的鎖;當使用類似 SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE; 的 SQL 語句時,就會阻止其他事務向表中插入 id = 15 的記錄,因為整個範圍都被間隙鎖鎖定了。

間隙鎖是儲存引擎對於效能和併發做出的權衡,並且只用於某些事務隔離級別。

雖然間隙鎖中也分為共享鎖和互斥鎖,不過它們之間並不是互斥的,也就是不同的事務可以同時持有一段相同範圍的共享鎖和互斥鎖,它唯一阻止的就是其他事務向這個範圍中新增新的記錄

Next-Key Lock

Next-Key 鎖相比前兩者就稍微有一些複雜,它是記錄鎖和記錄前的間隙鎖的結合,在 users 表中有以下記錄:

+------+-------------+--------------+-------+
|   id | last_name   | first_name   |   age |
|------+-------------+--------------+-------|
|    4 | stark       | tony         |    21 |
|    1 | tom         | hiddleston   |    30 |
|    3 | morgan      | freeman      |    40 |
|    5 | jeff        | dean         |    50 |
|    2 | donald      | trump        |    80 |
+------+-------------+--------------+-------+

如果使用 Next-Key 鎖,那麼 Next-Key 鎖就可以在需要的時候鎖定以下的範圍:

(-∞, 21]
(21, 30]
(30, 40]
(40, 50]
(50, 80]
(80, ∞)

既然叫 Next-Key 鎖,鎖定的應該是當前值和後面的範圍,但是實際上卻不是,Next-Key 鎖鎖定的是當前值和前面的範圍。

當我們更新一條記錄,比如 SELECT * FROM users WHERE age = 30 FOR UPDATE;,InnoDB 不僅會在範圍 (21, 30] 上加 Next-Key 鎖,還會在這條記錄後面的範圍 (30, 40] 加間隙鎖,所以插入 (21, 40] 範圍內的記錄都會被鎖定。

Next-Key 鎖的作用其實是為了解決幻讀的問題,我們會在下一節談事務的時候具體介紹。

死鎖的發生

既然 InnoDB 中實現的鎖是悲觀的,那麼不同事務之間就可能會互相等待對方釋放鎖造成死鎖,最終導致事務發生錯誤;想要在 MySQL 中製造死鎖的問題其實非常容易:

『淺入淺出』MySQL 和 InnoDB

兩個會話都持有一個鎖,並且嘗試獲取對方的鎖時就會發生死鎖,不過 MySQL 也能在發生死鎖時及時發現問題,並保證其中的一個事務能夠正常工作,這對我們來說也是一個好訊息。

事務與隔離級別

在介紹了鎖之後,我們再來談談資料庫中一個非常重要的概念 —— 事務;相信只要是一個合格的軟體工程師就對事務的特性有所瞭解,其中被人經常提起的就是事務的原子性,在資料提交工作時,要麼保證所有的修改都能夠提交,要麼就所有的修改全部回滾。

但是事務還遵循包括原子性在內的 ACID 四大特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和永續性(Durability);文章不會對這四大特性全部展開進行介紹,相信你能夠透過 Google 和資料庫相關的書籍輕鬆獲得有關它們的概念,本文最後要介紹的就是事務的四種隔離級別。

幾種隔離級別

事務的隔離性是資料庫處理資料的幾大基礎之一,而隔離級別其實就是提供給使用者用於在效能和可靠性做出選擇和權衡的配置項。

ISO 和 ANIS SQL 標準制定了四種事務隔離級別,而 InnoDB 遵循了 SQL:1992 標準中的四種隔離級別:READ UNCOMMITEDREAD COMMITEDREPEATABLE READSERIALIZABLE;每個事務的隔離級別其實都比上一級多解決了一個問題:

  • RAED UNCOMMITED:使用查詢語句不會加鎖,可能會讀到未提交的行(Dirty Read);

  • READ COMMITED:只對記錄加記錄鎖,而不會在記錄之間加間隙鎖,所以允許新的記錄插入到被鎖定記錄的附近,所以再多次使用查詢語句時,可能得到不同的結果(Non-Repeatable Read);

  • REPEATABLE READ:多次讀取同一範圍的資料會返回第一次查詢的快照,不會返回不同的資料行,但是可能發生幻讀(Phantom Read);

  • SERIALIZABLE:InnoDB 隱式地將全部的查詢語句加上共享鎖,解決了幻讀的問題;

MySQL 中預設的事務隔離級別就是 REPEATABLE READ,但是它透過 Next-Key 鎖也能夠在某種程度上解決幻讀的問題。

『淺入淺出』MySQL 和 InnoDB

接下來,我們將資料庫中建立如下的表並透過個例子來展示在不同的事務隔離級別之下,會發生什麼樣的問題:

CREATE TABLE test(
    id INT NOT NULL,
    UNIQUE(id)
);

髒讀

當事務的隔離級別為 READ UNCOMMITED 時,我們在 SESSION 2 中插入的未提交資料在 SESSION 1 中是可以訪問的。

『淺入淺出』MySQL 和 InnoDB

不可重複讀

當事務的隔離級別為 READ COMMITED 時,雖然解決了髒讀的問題,但是如果在 SESSION 1 先查詢了一個範圍的資料,在這之後 SESSION 2 中插入一條資料並且提交了修改,在這時,如果 SESSION 1 中再次使用相同的查詢語句,就會發現兩次查詢的結果不一樣。

『淺入淺出』MySQL 和 InnoDB

不可重複讀的原因就是,在 READ COMMITED 的隔離級別下,儲存引擎不會在查詢記錄時新增間隙鎖,鎖定 id < 5 這個範圍。

幻讀

重新開啟了兩個會話 SESSION 1SESSION 2,在 SESSION 1 中我們查詢全表的資訊,沒有得到任何記錄;在 SESSION 2 中向表中插入一條資料並提交;由於 REPEATABLE READ 的原因,再次查詢全表的資料時,我們獲得到的仍然是空集,但是在向表中插入同樣的資料卻出現了錯誤。

『淺入淺出』MySQL 和 InnoDB

這種現象在資料庫中就被稱作幻讀,雖然我們使用查詢語句得到了一個空的集合,但是插入資料時卻得到了錯誤,好像之前的查詢是幻覺一樣。

在標準的事務隔離級別中,幻讀是由更高的隔離級別 SERIALIZABLE 解決的,但是它也可以透過 MySQL 提供的 Next-Key 鎖解決:

『淺入淺出』MySQL 和 InnoDB

REPERATABLE READREAD UNCOMMITED 其實是矛盾的,如果保證了前者就看不到已經提交的事務,如果保證了後者,就會導致兩次查詢的結果不同,MySQL 為我們提供了一種折中的方式,能夠在 REPERATABLE READ 模式下加鎖訪問已經提交的資料,其本身並不能解決幻讀的問題,而是透過文章前面提到的 Next-Key 鎖來解決。

總結

文章中的內容大都來自於 高效能 MySQL、MySQL 技術內幕:InnoDB 儲存引擎、資料庫索引設計與最佳化 以及 MySQL 的 官方文件。

由於篇幅所限僅能對資料庫中一些重要內容進行簡單的介紹和總結,文中內容難免有所疏漏,如果對文章內容的有疑問,可以在部落格下面評論留言(評論系統使用 Disqus,需要翻牆)。

Reference

  • mysqld_safe version different than mysqld?

  • File Space Management

  • Externally Stored Fields in InnoDB

  • InnoDB Record Structure

  • InnoDB Page Structure

  • Difference between clustered and nonclustered index

  • InnoDB Locking

  • 樂觀鎖與悲觀鎖的區別

  • Optimistic concurrency control

  • MySQL 四種事務隔離級的說明

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31499124/viewspace-2564787/,如需轉載,請註明出處,否則將追究法律責任。

相關文章