天天看點

萬字字詳解InnoDB 原理!

萬字字詳解InnoDB 原理!

mysql innodb 引擎現在廣為使用,它提供了事務,行鎖,日志等一系列特性,本文分析下 innodb 的内部實作機制,mysql 版本為 5.7.24,作業系統為 debian 9。

萬字字詳解InnoDB 原理!

innodb 架構圖

innodb 的架構分為兩塊:記憶體中的結構和磁盤上的結構。innodb 使用日志先行政策,将資料修改先在記憶體中完成,并且将事務記錄成重做日志(redo log),轉換為順序io高效的送出事務。

這裡日志先行,說的是日志記錄到資料庫以後,對應的事務就可以傳回給使用者,表示事務完成。但是實際上,這個資料可能還隻在記憶體中修改完,并沒有刷到磁盤上去。記憶體是易失的,如果在資料落地前,機器挂了,那麼這部分資料就丢失了。

innodb 通過 redo 日志來保證資料的一緻性。如果儲存所有的重做日志,顯然可以在系統崩潰時根據日志重建資料。

當然記錄所有的重做日志不太現實,是以 innodb 引入了檢查點機制。即定期檢查,保證檢查點之前的日志都已經寫到磁盤,則下次恢複隻需要從檢查點開始。

記憶體中的結構主要包括 buffer pool,change buffer、adaptive hash index以及 log buffer 四部分。

如果從記憶體上來看,change buffer 和 adaptive hash index 占用的記憶體都屬于 buffer pool,log buffer占用的記憶體與 buffer pool獨立。

緩沖池緩存的資料包括page cache、change buffer、data dictionary cache等,通常 mysql 伺服器的 80% 的實體記憶體會配置設定給 buffer pool。

基于效率考慮,innodb中資料管理的最小機關為頁,預設每頁大小為16kb,每頁包含若幹行資料。

為了提高緩存管理效率,innodb的緩存池通過一個頁連結清單實作,很少通路的頁會通過緩存池的 lru 算法淘汰出去。

innodb 的緩沖池頁連結清單分為兩部分:new sublist(預設占5/8緩存池) 和 old sublist(預設占3/8緩存池,可以通過 innodb_old_blocks_pct修改,預設值為 37),其中新讀取的頁會加入到 old sublist的頭部,而 old sublist中的頁如果被通路,則會移到 new sublist的頭部。

緩沖池的使用情況可以通過 <code>show engine innodb status</code> 指令檢視。其中一些主要資訊如下:

通常來說,innodb輔助索引不同于聚集索引的順序插入,如果每次修改二級索引都直接寫入磁盤,則會有大量頻繁的随機io。change buffer 的主要目的是将對 非唯一 輔助索引頁的操作緩存下來,以此減少輔助索引的随機io,并達到操作合并的效果。它會占用部分buffer pool 的記憶體空間。

在 mysql5.5 之前 change buffer其實叫 insert buffer,最初隻支援 insert 操作的緩存,随着支援操作類型的增加,改名為 change buffer。

如果輔助索引頁已經在緩沖區了,則直接修改即可;如果不在,則先将修改儲存到 change buffer。change buffer的資料在對應輔助索引頁讀取到緩沖區時合并到真正的輔助索引頁中。change buffer 内部實作也是使用的 b+ 樹。

可以通過 <code>innodb_change_buffering</code> 配置是否緩存輔助索引頁的修改,預設為 all,即緩存 insert/delete-mark/purge 操作(注:mysql 删除資料通常分為兩步,第一步是delete-mark,即隻标記,而purge才是真正的删除資料)。

萬字字詳解InnoDB 原理!

檢視change buffer 資訊也可以通過 <code>show engine innodb status</code> 指令。更多資訊見

自适應哈希索引(ahi)查詢非常快,一般時間複雜度為 o(1),相比 b+ 樹通常要查詢 3~4次,效率會有很大提升。innodb 通過觀察索引頁上的查詢次數,如果發現建立哈希索引可以提升查詢效率,則會自動建立哈希索引,稱之為自适應哈希索引,不需要人工幹預,可以通過 <code>innodb_adaptive_hash_index</code> 開啟,mysql5.7 預設開啟。

考慮到不同系統的差異,有些系統開啟自适應哈希索引可能會導緻性能提升不明顯,而且為監控索引頁查詢次數增加了多餘的性能損耗, mysql5.7 更改了 ahi 實作機制,每個 ahi 都配置設定了專門分區,通過 <code>innodb_adaptive_hash_index_parts</code>配置分區數目,預設是8個,如前一節指令列出所示。

log buffer是 重做日志在記憶體中的緩沖區,大小由 <code>innodb_log_buffer_size</code> 定義,預設是 16m。一個大的 log buffer可以讓大事務在送出前不必将日志中途刷到磁盤,可以提高效率。如果你的系統有很多修改很多行記錄的大事務,可以增大該值。

配置項 <code>innodb_flush_log_at_trx_commit</code> 用于控制 log buffer 如何寫入和刷到磁盤。注意,除了 mysql 的緩沖區,作業系統本身也有核心緩沖區。

預設為1,表示每次事務送出都會将 log buffer 寫入作業系統緩存,并調用配置的 “flush” 方法将資料寫到磁盤。

設定為 1 因為頻繁刷磁盤效率會偏低,但是安全性高,最多丢失 1個 事務資料。

而設定為 0 和 2 則可能丢失 1秒以上 的事務資料。

為 0 則表示每秒才将 log buffer 寫入核心緩沖區并調用 “flush” 方法将資料寫到磁盤。

為 2 則是每次事務送出都将 log buffer寫入核心緩沖區,但是每秒才調用 “flush” 将核心緩沖區的資料刷到磁盤。

<code>innodb_flush_log_at_timeout</code> 可以配置重新整理日志緩存到磁盤的頻率,預設是1秒。注意刷磁盤的頻率并不保證就正好是這個時間,可能因為mysql的一些操作導緻推遲或提前。

而這個 “flush” 方法并不是c标準庫的 fflush 方法(fflush是将c标準庫的緩沖寫到核心緩沖區,并不保證刷到磁盤),它通過 innodb_flush_method 配置的,預設是 fsync,即日志和資料都通過 fsync 系統調用刷到磁盤。

可以發現,innodb 基本每秒都會将 log buffer落盤。而innodb中使用的 redo log 和 undo log,它們是分開存儲的。

redo log在記憶體中有log buffer,在磁盤對應ib_logfile檔案。而undo log是記錄在表空間ibd檔案中的,innodb為undo log會生成undo頁,對undo log本身的操作(比如向undo log插入一條記錄),也會記錄redo log,是以undo log并不需要馬上落盤。而 redo log 則通常會配置設定一塊連續的磁盤空間,然後先寫到log buffer,并每秒刷一次磁盤。

redo log 必須在資料落盤前先落盤(write ahead log),進而保證資料持久性和一緻性。而資料本身的修改可以先駐留在記憶體緩沖池中,再根據特定的政策定期刷到磁盤。

表空間:

分為系統表空間(mysql 目錄的 ibdata1 檔案),臨時表空間,正常表空間,undo 表空間以及 file-per-table 表空間(mysql5.7預設打開file_per_table 配置)。

系統表空間又包括了innodb資料字典,雙寫緩沖區(doublewrite buffer),修改緩存(change buffer),undo日志等。

redo日志:

存儲的就是 log buffer 刷到磁盤的資料。

為了後面測試友善,我們先建立一個測試資料庫 test,然後建立一個測試表 t。

建立完成後,可以在 mysql 目錄中看到 test 資料庫目錄,然後裡面有 db.opt, t.frm 和 t.ibd 3個檔案。其中 db.opt 儲存了資料庫test的預設字元集 utf8mb4 和校驗方法 utf8mb4_general_ci,t.frm 是表的資料字典資訊(innodb資料字典資訊主要是存儲在系統表空間ibdata1檔案中,由于曆史原因才在 t.frm 多保留了一份),t.ibd是表的資料和索引。

innodb 與 myisam 不同,它在系統表空間存儲資料字典資訊,是以它的表不能像 myisam 那樣直接拷貝資料表檔案移動。mysql5.7 采用的檔案格式是 barracuda,它支援 compact 和 dynamic 這兩種新的行記錄格式。

建立表時可以通過 <code>row_format</code> 指定行記錄格式,預設是 dynamic。可以通過指令 <code>show table status</code> 檢視表資訊,此外,也可使用 <code>select * from information_schema.innodb_sys_tables where name='test/t'</code> 檢視。

innodb表使用上有一些限制,如一個表最多隻能有64個輔助索引,一行大小不能超過65535等,組合索引不能超過16個字段等,一般應該不會突破限制,詳細見 innodb-restrictions。

表空間根據類型可以分為系統表空間,file-per-table 表空間,正常表空間,undo表空間,臨時表空間等。本節分析 file-per-table 表空間。

系統表空間:

包含内容有資料字典,雙寫緩沖,修改緩沖以及undo日志,以及在系統表空間建立的表的資料和索引。

正常表空間:

類似系統表空間,也是一種共享的表空間,可以通過 <code>create tablespace</code> 建立正常表空間,多個表可共享一個正常表空間,也可以修改表的表空間。

注意:必須删除正常表空間中的表後才能删除正常表空間。

file-per-table表空間:

mysql innodb新版本提供了 innodb_file_per_table 選項,每個表可以有單獨的表空間資料檔案(.ibd),而不是全部放到系統表空間資料檔案 ibdata1 中。

在 mysql5.7 中該選項預設開啟。

其他表空間:

其他表空間中undo表空間存儲的是undo日志。

除了存儲在系統表空間外,undo日志也可以存儲在單獨的undo表空間中。

臨時表空間則是非壓縮的臨時表的存儲空間,預設是資料目錄的 ibtmp1 檔案,所有臨時表共享,壓縮的臨時表用的是 file-per-table 表空間。

表空間檔案結構上分為段、區、頁。

萬字字詳解InnoDB 原理!

段(segment)分為索引段,資料段,復原段等。其中索引段就是非葉子結點部分,而資料段就是葉子結點部分,復原段用于資料的復原和多版本控制。一個段包含256個區(256m大小)。

區是頁的集合,一個區包含64個連續的頁,預設大小為 1mb (64*16k)。

頁是 innodb 管理的最小機關,常見的有 fsp_hdr,inode, index 等類型。所有頁的結構都是一樣的,分為檔案頭(前38位元組),頁資料和檔案尾(後8位元組)。頁資料根據頁的類型不同而不一樣。

file_space_header 頁:用于存儲區的元資訊。ibd檔案的第一頁 fsp_hdr 頁通常就用于存儲區的元資訊,裡面的256個 xdes(extent descriptors) 項存儲了256個區的元資訊,包括區的使用情況和區裡面頁的使用情況。

ibuf_bitmap 頁:用于記錄 change buffer的使用情況。

inode 頁:用于記錄檔案段(fseg)的資訊,每頁有85個inode entry,每個inode entry占用192位元組,用于描述一個檔案段。每個inode entry包括檔案段id、屬于該段的區的資訊以及碎片頁數組。區資訊包括 free(完全空閑的區), not_full(至少使用了一個頁的區), full(沒空閑頁的區)三種類型的區的list base node(包含連結清單長度和頭尾頁号和偏移的結構體)。碎片頁數組則是不同于配置設定整個區的單獨配置設定的32個頁。

index 頁:索引頁的葉子結點的data就是資料,如聚集索引存儲的行資料,輔助索引存儲的主鍵值。

采用 file-per-table 的優缺點如下:

優點:

可以友善回收删除表所占的磁盤空間。

如果使用系統表空間的話,删除表後空閑空間隻能被 innodb 資料使用。

truncate table 操作會更快。

可以單獨拷貝表空間資料到其他資料庫(使用 transportable tablespace 特性),可以更友善的觀測每個表空間資料的大小。

缺點:

fsync 操作需要作用的多個表空間檔案,比隻對系統表空間這一個檔案進行fsync操作會多一些 io 操作。

此外,mysqld需要維護更多的檔案描述符。

innodb 表空間檔案 .ibd 初始大小為 96k,而innodb預設頁大小為 16k,頁大小也可以通過 <code>innodb_page_size</code> 配置為 4k, 8k…64k 等。在ibd檔案中,0-16kb偏移量即為0号資料頁,16kb-32kb的為1号資料頁,以此類推。頁的頭尾除了一些元資訊外,還有checksum校驗值,這些校驗值在寫入磁盤前計算得到,當從磁盤中讀取時,重新計算校驗值并與資料頁中存儲的對比,如果發現不同,則會導緻 mysql 崩潰。

ibd檔案存儲結構如下所示:

萬字字詳解InnoDB 原理!

ibd檔案存儲結構

innodb頁分為index頁、undo頁、系統頁,ibuf_bitmap頁, inode頁等多種。

第0頁是 fsp_hdr 頁,主要用于跟蹤表空間,空閑連結清單、碎片頁以及區等資訊。

第1頁是 ibuf_bitmap 頁,儲存change buffer的位圖。

第2頁是 inode 頁,用于存儲區和單獨配置設定的碎片頁資訊,包括full、free、not_full 等頁清單的基礎結點資訊(基礎結點資訊記錄了清單的起始和結束頁号和偏移等),這些結點指向的是 fsp_hdr 頁中的項,用于記錄頁的使用情況,它們之間關系如下圖所示。

第3頁開始是索引頁 index(b-tree node),從 0xc000(每頁16k) 開始,後面還有些配置設定的未使用的頁。

可以在 <code>innodb_sys_tables</code> 表中查到表t的表空間id為28,然後可以在 innodb_buffer_page查到所有頁資訊,一共4個頁。分别是 fsp_hdr, ibuf_bitmap, inode, index。

innodb引擎索引頁的結構如下圖,可以用 hexdump檢視 t.ibd 檔案,然後對照innodb頁的結構分析下各個頁的字段。

萬字字詳解InnoDB 原理!

索引頁結構

fil header(38位元組): 記錄檔案頭資訊。前4位元組 <code>95 45 82 8a</code> 是 checksum,接着 <code>00 00 00 03</code> 是頁偏移值 3,即這是第3頁。接着 4 位元組是上一頁偏移值,因為隻有一個資料頁,是以這裡為 <code>ff ff ff ff</code>,接着 4 位元組是下一頁偏移值 <code>ff ff ff ff</code>。然後 8 位元組 <code>00 00 00 00 00 28 85 7c 是日志序列号 lsn。随後的 2 位元組</code>45 bf<code>是頁類型,代表是 index 頁。接着 8 位元組</code>00 00 00 00 00 00 00 00<code>表示被更新到的lsn,在 file-per-table 表空間中都是0。然後 4 位元組</code>00 00 00 1c` 表示該資料頁屬于的表t的表空間id是 0x1c(28)。

index header(36位元組): 記錄的是 index 頁的狀态資訊。前2位元組 00 02 表示頁目錄的 slot 數目為2;接着2位元組 00 b0 是頁中第一個記錄的指針。80 04是這頁的格式為dynamic和記錄數4(包括2條system records我們插入的2條記錄)。接着 00 00是可重用空間首指針,再後面2位元組00 00是已删除記錄數;00 9a是最後插入記錄的位置偏移,即最後插入位置是 0xc09a,即第2條記錄開始位址。00 02 是最後插入的方向,2 表示 page_direction_right,即自增長方式插入。00 01 指一個方向連續插入的數量,這裡為1。接着的00 02是 index 頁中的真實記錄數,我們隻有2條記錄。然後8位元組00…00為修改該頁的最大事務id,這個值隻在輔助索引中存在,這裡為0。接着2位元組00 00為頁在索引樹的層級,0表示葉子結點。最後8個位元組 00…2f為索引id 47(索引id可以在information_schema.innodb_sys_indexes 中查詢,可以确認 47 正好是表 t 的主索引)。

fseg header:這是index頁中的根結點才有的,非根結點的為0。前10位元組 00 00 00 1c 00 00 00 02 00 f2 是葉子結點所在段的segment header,分别記錄了葉子結點的表空間id 0x1c,inode頁的頁号 2 和 inode項偏移 0xf2。而後10位元組 00 00 00 1c 00 00 00 02 00 32 是非葉子結點所在段的segment header,偏移分别是0xf2 和 0x32,即inode頁的前2個entry,檔案段id分别是1和2。fseg header中存儲了該 index 頁的inode項,inode項裡面則記錄了該頁存儲所在的檔案段以及檔案段頁的使用情況。對于 file-per-table情況下,每個單獨的表空間檔案的 fsp_hdr 頁負責管理頁使用情況。

萬字字詳解InnoDB 原理!

fseg結構關系圖

system records(26位元組): 每個 index 頁都有兩條虛拟記錄 infimum 和 supremum,用于限定記錄的邊界,各占 13 個位元組。其中記錄頭的5個位元組分别辨別了擁有記錄的數目和類型(擁有記錄數目是即後面頁目錄部分的owned值,目前頁目錄隻有兩個槽,infimum擁有記錄數隻有它自己為1,而supremum擁有我們插入的2條記錄和它自己,故為3)、下一條記錄的偏移 0x1c,即位置是 0xc07f,這就是我們實際記錄開始位置。後面8個位元組為 infimum + 空值,supremum類似,隻是它下一條記錄偏移為0。

user records: 接下來是2條我們插入的記錄。第1條記錄前面7位元組是記錄頭(record header),其中前面的 1位元組記錄的是可變變量的長度03,因為我們記錄中c的值是 abc。然後1位元組記錄的是可為null的變量是否是null,這裡不為 null,故為0。接着的5位元組記錄了插入順序2(infimum插入順序固定是0,supremum插入順序是1,其他記錄則是從2開始),下一個記錄的偏移 0x1b(即下一個記錄開始位置是0xc078+0x1b=0xc093),删除标記等。後面就是記錄内容。第2條記錄同理。這裡的事務id可以通過 select * from information_schema.innodb_trx 進行驗證。

b+樹頁詳細結構

page directory(4位元組):因為頁目錄的slot隻有2個,每個slot占2位元組,故頁目錄為 00 70 00 63 這4位元組,存儲的是相對于最初行的位置。其中 0xc063 正好是 infimum 記錄的開始位置,而 0xc070 正好是 supremum 記錄的開始位置。使用頁目錄進行二分查找,可以加速查詢,詳細見後面分析。

fil tail (8位元組): 最後8位元組為 95 45 82 8a 00 28 85 7c,其中 95 45 82 8a 為 checknum,跟 fil header的checksum一樣。後4位元組00 28 85 7c 與 fil header的lsn的後4個位元組一緻。

當然,我們也可以通過 innodb_ruby 工具來分析表空間檔案。

innodb資料檔案本身就是索引檔案,其索引分聚集索引和輔助索引,聚集索引的葉節點包含了完整的資料記錄,輔助索引葉節點資料部分是主鍵的值,除了空間索引外,innodb的索引實作基本都是 b+ 樹,如圖所示。

其中非葉子結點存儲的是子頁的最小的鍵值和子頁的頁号,葉子結點存儲的是資料,資料按照索引鍵排序。同一層的頁之間用雙向連結清單連接配接(前面提到的fil header中prev page 和 next page),同一頁内的記錄用單向連結清單連接配接(record header中記錄了下一條記錄的偏移)。每一頁設定了兩個虛拟記錄infimum和supremum用于辨別頁的開始和結束。

萬字字詳解InnoDB 原理!

索引結構

在innodb中根據輔助索引查詢,如果除了主鍵外還有其他字段,則需要查詢兩遍,先根據輔助索引查詢主鍵的值,然後再到主索引中查詢得到記錄。此外,因為輔助索引的資料部分是主鍵值,主鍵不能過大,否則會導緻輔助索引占用空間變大,用自增id做主鍵是個不錯的選擇。

建立一個新的測試表 t2,有主索引 id 和 輔助索引 ch,分析 t2.ibd 檔案可驗證:

對比表t,表t2多一個index頁,用于存儲輔助索引的根結點。

輔助索引的index頁也有兩個系統記錄 infimum 和 supremum。

而使用者記錄内容格式跟前面分析基本一緻,内容為輔助索引 ch 列的值 ab 和 主鍵值1。

前面提到index頁内的記錄是通過單向連結清單連接配接在一起的,周遊清單性能會比較差,而index頁的頁目錄就是為了加速記錄搜尋。表 t2 中的頁目錄隻有兩項,分别是 0x63 和 0x70,即 99 和 112。

下面的ownedkey為這個頁目錄槽擁有的小于等于它的記錄數目,顯然 infimum 的ownedkey為 1,即隻有它自己,沒有key會比infimum小。而 supremum 的owned是3,分别是我們插入的兩條記錄和它自己。

每個頁目錄槽最少要包含4個記錄,最多包含8個記錄(包括它自己)。如果我們在表 t2 中另外插入 7 條記錄,則會增加一個新的slot,即 id 為 4 的記錄,如下:

下圖是頁目錄結構圖,可以通過頁目錄的二分查找提高頁内資料的查詢性能。

萬字字詳解InnoDB 原理!

頁目錄結構

系統表空間包含内容有:資料字典,雙寫緩沖,修改緩沖,undo日志,以及在系統表空間建立的表的資料和索引。可以看到,除了配置設定未使用的頁外, undo_log,sys, index 頁占據了不少的空間。

undo_log 頁存儲的是undo log,sys 頁存儲的是資料字典、復原段、修改緩存等資訊,index 是索引頁,trx_sys 頁用于innodb的事務系統。資料字典就是資料表的元資訊,修改緩沖前面提到是為了提高io性能也不再贅述,這裡主要分析下 undo 日志和雙寫緩沖。

mysql的mvcc(多版本并發控制)依賴undo log實作。mysql的表空間檔案 t.ibd 存儲的是記錄最新值,每個記錄都有一個復原指針(見前面圖中的roll ptr),指向該記錄的最近一條undo記錄,而每條undo記錄都會指向它的前一條undo記錄,如下圖所示。預設情況下 undo log存儲在系統表空間 ibdata1 中。

萬字字詳解InnoDB 原理!

undo log示意圖

插入一條資料後,可以發現目前 t3.ibd 檔案中的記錄是 (1, ‘a’),而 undo log此時有一條 insert 的記錄。如下:

執行後面的update語句,可以看到 undo log如下:

需要注意的是,undo log 在事務執行過程中就會産生,事務送出後才會持久化,如果事務復原了則undo log也會删除。

另外,删除記錄并不會立即在表空間中删除該記錄,而隻是做個标記(delete-mark),真正的删除則是等由背景運作的 purge 程序處理。除了每條記錄有undo log的清單外,整個資料庫也會有一個曆史清單,purge 程序會根據該曆史清單真正删除已經沒有再被其他事務使用的 delete-mark 的記錄。purge 程序會删除該記錄以及該記錄的 undo log。

先回顧下innodb的記錄更新流程:先在buffer pool中更新,并将更新記錄到 redo log 檔案中,buffer pool中的記錄會标記為髒資料并定期刷到磁盤。由于innodb預設page大小是16kb,而磁盤通常以扇區為機關寫入,每次預設隻能寫入512個位元組,無法保證16k資料可以原子的寫入。

如果寫入過程發生故障(比如機器掉電或者作業系統崩潰),會出現頁的部分寫入(partial page writes),導緻難以恢複。因為 mysql 的重做日志采用的是實體邏輯日志,即頁間是實體資訊,而頁内是邏輯資訊,在發生頁部分寫入時,無法确認資料頁的具體修改而導緻難以恢複。

mysql 的資料頁在真正寫入到表空間檔案前,會先寫到系統表空間檔案的一段連續區域雙寫緩沖(double-write buffer,預設大小為 2mb,128個頁)并 fsync 落盤,等雙寫緩沖寫入成功後才會将資料頁寫到實際表空間的位置。

因為雙寫緩沖和資料頁的寫入時機不一緻,如果在寫入雙寫緩沖出錯,可以直接丢棄該緩沖頁,而如果是寫入資料頁時出錯,則可以根據雙寫緩沖區資料恢複表空間檔案。

innodb的多版本并發控制是基于事務隔離級别實作的,而事務隔離級别則是依托前面提到的 undo log 實作的。當讀取一個資料記錄時,每個事務會使用一個讀視圖(read view),讀視圖用于控制事務能讀取到的記錄的版本。

innodb的事務隔離級别分為:read uncommitted,read committed,repeatable read以及serializable。其中serializable是基于鎖實作的串行化方式,嚴格來說不是事務可見性範疇。

read uncommitted:

未送出讀也稱為髒讀,它讀取的是目前最新修改的記錄,即便這個修改最後并未生效。

read committed:

送出讀。

它基于的是目前事務内的語句開始執行時的最大的事務id。

如果其他事務修改同一個記錄,在沒有送出前,則該語句讀取的記錄還是不會變。

但是這種情況會産生不可重複讀,即一個事務内多次讀取同一條記錄可能得到不同的結果(該記錄被其他事務修改并送出了)。

repeatable read:

可重複讀。

它基于的是事務開始時的讀視圖,直到事務結束。

不讀取其他新的事務對該記錄的修改,保證同一個事務内的可重複讀取。

innodb提供了 next-key lock來解決幻讀問題,不過在一些特殊場景下,可重複讀還是可能出現幻讀的情況。

在實際開發中影響不大,就不贅述了。

事務有 acid 四個屬性, innodb 是支援事務的,它實作 acid 的機制如下:

innodb的原子性主要是通過提供的事務機制實作,與原子性相關的特性有:

autocommit 設定。

commit 和 rollback 語句(通過 undo log實作)。

innodb的一緻性主要是指保護資料不受系統崩潰影響,相關特性包括:

innodb 的雙寫緩沖區(doublewrite buffer)。

innodb 的故障恢複機制(crash recovery)。

isolation

innodb的隔離性也是主要通過事務機制實作,特别是為事務提供的多種隔離級别,相關特性包括:

autocommit設定。

set isolation level 語句。

innodb 鎖機制。

innodb的持久性相關特性:

redo log。

雙寫緩沖功能。

可以通過配置項 innodb_doublewrite 開啟或者關閉。

配置 innodb_flush_log_at_trx_commit。

用于配置innodb如何寫入和重新整理 redo 日志緩存到磁盤。

預設為1,表示每次事務送出都會将日志緩存寫入并刷到磁盤。

innodb_flush_log_at_timeout 可以配置重新整理日志緩存到磁盤的頻率,預設是1秒。

配置 sync_binlog。

用于設定同步 binlog 到磁盤的頻率,為0表示禁止mysql同步binlog到磁盤,binlog刷到磁盤的頻率由作業系統決定,性能最好但是最不安全。

為1表示每次事務送出前同步到磁盤,性能最差但是最安全。

mysql文檔推薦是 sync_binlog 和 innodb_flush_log_at_trx_commit 都設定為 1。

作業系統的 fsync 系統調用。

ups裝置和備份政策等。

mysql innodb 的實作非常複雜,本文隻是總結了一些皮毛。有什麼問題,留言一起交流吧。。。