1. 程式人生 > 實用技巧 >MySQL-技術專題-innodb儲存引擎

MySQL-技術專題-innodb儲存引擎

1. change buffer

  可以看做是Insert buffer升級版,當需要插入或更新一個數據頁時,對於非聚簇索引,如果該資料頁不在記憶體當中,Innodb在不影響

致性的前提下,會將更新操作快取在change buffer中,可以省去從磁碟讀取該頁的操作,在以一定的頻率和情況下與索引頁進行

merge合並,這時通常可以把多個更新頁的操作合併為一次磁碟讀寫,這樣就大大提高了插入效能。

什麼條件下可以使用change buffer?

  對於唯一索引,所有的插入和更新操作都要判斷其唯一性,要判斷就需要把資料頁載入到記憶體當中,這是可以直接更新,自然也就不

需要用change buffer,因此,只有非唯一的二級索引可以使用change buffer。

  雖然名字是buffer,但change buffer不僅有記憶體副本,也是會寫入磁碟空間的。

2. double write

  二次寫,通俗的說,就是為了防止在寫入的過程中發生部分寫失敗(partial page write)而造成的資料丟失,所以在寫資料頁之前,先將資料寫入共享表空間(ibdata),然後再寫資料頁,如果出現數據頁損壞,可以通過該頁的副本來還原該頁,然後在進行redo log重做,這個機制就叫做double write。

  double write 由兩部分組成,一部分是記憶體中2M的doublewrite buffer,另一部分是磁碟上共享表空間中連續的128個頁(兩個區)也是2M。執行順序是 載入髒頁資訊到double write buffer中->分兩次,每次1M寫入共享表空間->呼叫fsync同步磁碟。

3. 自適應雜湊索引

  Innodb的索引是B+樹,如果樹高為3-4層,那麼就需要3-4次io操作來進行檢索,而Hash查詢的時間複雜度為O(1),即一般只進行一次查詢就能定位資料。Innodb會對錶上的索引進行監控,如果發現建立雜湊索引可以提升效能,則會為這些熱點頁建立雜湊索引。

  自適應雜湊索引不需要開發人員或者DBA介入,完全由Innodb自動調整。對建立的頁有一個要求,就是連續一定次數訪問模式必須是一樣的,如果是聯合索引的不同使用方式,則不能觸發自適應雜湊索引。例如a,b的聯合索引,按a查詢和按a,b查詢雖然使用的都是同一索引,但視為不同使用方式。

  雜湊索引只能用於等值查詢,無法進行範圍查詢。

4. 重新整理相鄰頁

  Innodb在重新整理一個髒頁時,會檢測該頁所在區的所有頁,如果是髒頁,則會一起進行刷盤。這樣的好處是可以合併IO操作,提高效能。缺點是會將不怎麼髒的頁一起刷入,但是這些頁可能很快又會變髒,所以機械硬碟更需要這個特性,固態硬碟的IOPS超高,建議關閉此特性。

二、Innodb的索引

1. B+樹和索引

  B+樹是一種平衡多路樹,所有的記錄節點都會按鍵值大小的順序存放在同一層的葉子節點上,各葉子節點中用指標形成雙向連結串列(實際上鍊表是基於頁)。

  B+樹一般被作為索引使用,分為聚簇索引和二級索引,聚簇索引一般可以認為是主鍵索引,二級索引是普通索引。

  • 聚簇索引:非葉子節點儲存主鍵值,葉子節點儲存整張表的行記錄,也把聚簇索引的葉子節點稱為資料頁。
  • 二級索引:非葉子節點儲存索引值,葉子節點儲存主鍵值。

主鍵索引儘量使用自增ID,佔用空間小,可以在一個頁中儲存更多的主鍵記錄,減少B+樹層次,減少IO查詢次數。

如果二級索引中包含全部需要返回的列,則被稱為覆蓋索引。例如select age from table where age = 27其中age欄位包含二級索引。

使用二級索引查詢時,在索引樹上查詢到主鍵ID,還需要在聚簇索引的索引樹上再次查詢獲取資料頁,這個行為被稱為回表,如果是覆蓋索引查詢的話,則可以省略回表的過程,從而提高效能。

索引屬性Cardinality,代表了這個索引上不重複值的數量,實際應用中,Cardinality應該儘可能的接近於行數,如果這個值太小,說明索引欄位的值重複的非常多,考慮是否可以去掉該索引。

Cardinality的統計是一個取樣估計值而非準確值(隨機取八個葉子節點,所包含的記錄數量平均值乘以葉子節點總數),如果差別很多,可以使用analysis table命令進行更新。

聯合索引按照由左到右順序進行匹配

2. 索引的優化

  Multi-Range Read優化

  MMR優化目的是為了減少磁碟的隨機訪問。在查詢二級索引時,先對查詢到的結果按照主鍵進行排序,再去聚簇索引進行回表。

MMR優化還會對範圍查詢進行拆分,並且會在執行計劃中的Extra列顯示Using MMR(P224)

Index Condition Pushdown優化

  ICP優化在進行索引查詢,在取出索引的同時,判斷是否能進行where條件過濾,換一種說法就是講where部分放到了儲存引擎層。之

前的MySQL版本(5.6之前)需要首先根據索引查出記錄,再進行where過濾。

當使用ICP優化是,執行計劃中列Extra看到Using index condition提示。

三、鎖

latch與lock

latch和lock最大的區別在於,latch是為了保護執行緒併發過程的資源,lock則是用來保護事務的鎖。這裡主要討論後者。

1 Innodb的鎖

1.1 鎖的型別

行級鎖

  Innodb儲存引擎有兩種行級鎖

  • 共享鎖(S Lock):又稱讀鎖
  • 排它鎖(X Lock):又稱寫鎖

意向鎖

  • 意向共享鎖(IS Lock):事務想要獲得一張表中某幾行的共享鎖。
  • 意向排它鎖(IX Lock):事務想要獲得一張表中某幾行的排他鎖。

意向鎖是表級鎖,意向鎖之間不會發生衝突,並且不會阻塞除全表掃以外的任何請求。

以上的排它共享指的是表鎖,意向鎖不會與行級鎖互斥。

意向鎖的作用

  當一個事務要求加表鎖時,需要掃描全表去發現有無被鎖定的行,但是有了意向鎖,可以省略很多掃描過程,直接發現互斥關係。

1.2 快照讀和當前讀

快照讀

  快照讀又叫一致性非鎖定讀(consistent nonlocking read)是指InnoDB儲存引擎通過多版本控制的方式來讀取當前執行時間資料庫中行的資料。並不是所有事務隔離級別下都會採用快照讀,僅在事務隔離級別為RR和RC時起作用。

快照讀在RR和RC下表現不同,RR事務隔離級別下,快照讀總是讀取事務開始時的資料版本。而在RC級別下,快照讀總是會讀取被鎖定行的最新一份快照資料。

快照讀是不需要上鎖的,因此併發效能很好。

快照讀使用undo log實現

當前讀

  當前讀又被稱為一致性鎖定讀(consistent locking read),顧名思義,是一種加鎖讀取的模式。適用於以下兩個語句。

select …… for update

select …… lock in share mode

  select …… for update會對行記錄加上X鎖,其他事務不能對已鎖定的行記錄加上任何鎖。

  select …… lock in share mode會對行記錄加上S鎖,其他事務可以對鎖定航加S鎖,但加X鎖會被阻塞。

1.3 行鎖的3種表現形式

InnoDB的行鎖有3中演算法,分別是:

  • Record Lock:單行記錄上的鎖。
  • Gap Lock:間隙鎖,鎖定一個範圍,不包括記錄本身。
  • Next-Key Lock:鄰鍵鎖,可以視為行鎖+間隙鎖,鎖定記錄本身並鎖定一個範圍,範圍為左開右閉。

Next-Key Lock可以看做是加鎖的基本單位,查詢中訪問到的元素都會加上前開後閉的Next-Key Lock,但又以下兩種情況下會進行優化。

  • 當在索引上查詢條件為等值時,給唯一索引加鎖時next-key lock會退化為行鎖。
  • 當在索引上查詢條件為等值時,向右遍歷時遇到最後一個值不滿足等值條件時,next-key lock退化為gap lock。

解決幻讀問題

幻讀問題是指在同一事務下,連續執行兩次同樣的SQL語句可能導致不同的結果,第二次的SQL可能返回之前不存在的行。

幻讀演示

表t由1、2、5三行記錄組成,會話A在一次事務查詢過程中,會話B插入一條資料a=5並提交,導致會話A兩次查詢的結果不一致,違反了事務的隔離性,及當前事務可以看到其他事務的結果。

在RR的事務隔離級別下,InnoDB引入了Gap Lock和Next-Key Lock來解決幻讀問題。對於上述的sql語句select * from t where a>2 for update;,鎖住的不是5這個單個值,而是對(2,+∞)這個範圍加了X鎖,因此任何對於這個範圍的插入都是不被允許的,從而避免幻讀問題。因此要展示幻讀問題,需要將事務隔離級別設為RC。

四、Innodb事務的實現

  事務的隔離性由鎖來實現。原子性、一致性、永續性通過資料庫的redo log和undo log來完成。

  undo並不是redo的逆過程,兩者都可看做是一個恢復操作,redo恢復提交事務修改的頁操作,而undo回滾行記錄到某個特定版本。

redo通常是物理日誌,記錄的是頁的物理修改操作。

undo是邏輯日誌,根據每行記錄進行記錄。

1)redo

1. 基本概念

  redo log用來實現事務永續性,由兩部分組成,一部分是記憶體中的重做日誌緩衝,是易失的,另一部分是重做日誌檔案,是持久的。

  Force Log at Commit

  當事務提交(commit)時,必須先將該事務的所有日誌寫入到重做日誌檔案進行持久化,待事務的commit操作完成才算完成。這裡的日

志指的是重做日誌,在Innodb儲存引擎當中,由redo log和undo log兩部分組成。redo log用來保證事務的永續性,undo log用來幫助

回滾事務及實現MVCC功能。

redo log基本都是順序寫,資料庫執行時不需要讀取redo log。而undo log時需要隨機讀寫的。

引數 innodb_flush_log_at_trx_commit 用來控制重做日誌重新整理到磁碟的策略。

1(預設),表示事務提交時必須呼叫一次fsync操作。還可設為0和2,0表示事務提交時不進行寫入重做日誌操作,這個操作僅

在master thread中完成,而在master thread中每1秒會進行一次重做日誌檔案的fsync操作。

2表示事務提交時將重做日誌寫入重做日誌檔案,但僅寫入檔案系統的快取中,不進行fsync操作。設為0和2可以提高效

率,但是失去了事務ACID的特性。

binlog和重做日誌的區別

  1. 首先,重做日誌是在引擎層產生,而binlog是在mysql server層產生,而且二進位制檔案不僅僅針對與Innodb引擎,任何儲存引擎對於資料庫的修改都會產生binlog。
  2. 其次,兩種日誌記錄的內容形式不同。MySQL資料庫的binlog是一種邏輯日誌,其記錄的是對應的sql語句。而Innodb的重做日誌是物理格式,記錄的是每個頁的修改。
  3. 此外,兩種日誌寫入磁碟的時間點不同,binlog只在事務提交完成後進行一次寫入。而重做日誌在事務進行中不斷被寫入,表現為日誌並不是隨事務的提交順序進行寫入的

2. log block

  在Innodb中,重做日誌都是以512位元組進行儲存的。這意味著重做日誌檔案和重做日誌快取都是以塊(block)為單位進行儲存的,稱之

為重做日誌塊(redo log block),每塊的大小為512位元組。

  如果一個頁產生的日誌數量大於512位元組,那麼需要分割為多個日誌塊進行儲存。

  由於日誌塊和磁碟扇區大小一樣,都是512位元組,因此不需要double write就可以保證原子性。

3. log group

log group 為重做日誌組,其中有多個重做日誌檔案。log group 儲存的是之前在 log buffer 中儲存的 log block,因此也是根據塊來進行物理儲存管理,每個塊也是512位元組。在InnoDB儲存引擎執行過程中,log buffer 根據一定的規則將記憶體中的 log block 重新整理到磁碟。這個規則是:

  • 事務提交時
  • 當 log buffer 中有一半的記憶體空間已經被使用時
  • log checkpoint 時

redo log file被寫滿時,會接著寫入下一個redo log file,其使用方式為 round-robin.

每個redo log file的前2k空間部分不儲存東西,對於每個log group的第一個redo log file的前2k空間儲存了四個512位元組大小的塊

4. LSN

LSN是 Log Sequence Number的縮寫,其代表的是日誌序列號。在InnoDB中,LSN佔用8位元組,並且單調遞增。LSN表示的含義有:

  • 重做日誌的總量
  • checkpoint的位置
  • 頁的版本

LSN不僅存在於重做日誌中,還存在於每個頁中。在每個頁的頭部,有一個值FIL_PAGE_LSN記錄了改業的LSN。在頁中,LSN表示改業最後重新整理時LSN的大小。因為重做日誌記錄的是每個頁的日誌,因此頁中的LSN用來判斷頁是否需要進行回覆操作。例如,頁P1的LSN是10000,而資料庫啟動時,InnoDB檢測到寫入重做日誌中的LSN為13000,並且該事務已提交,name資料庫需要進行恢復操作,將重做日誌應用到P1頁中。同樣的對於重做日誌中LSN曉玉P1頁的LSN,不需要進行重做,因為P1頁中的LSN表示頁已經被重新整理到該位置。

使用者可以通過show engine innodb status檢視LSN的情況

...
---
LOG
---
Log sequence number 1133058903319
Log flushed up to 1133058888909
Pages flushed up to 1133058887714
Last checkpoint at 1133058887714
0 pending log writes, 0 pending chkp writes
61923710 log i/o's done, 4.61 log i/o's/second
...

2)undo

1. 基本概念

  在資料庫進行修改時,InnoDB儲存引擎不但會產生redo,還會產生一定量的undo。這樣需要回滾時可以利用undo資訊將資料回滾到

修改之前的亞子。

  undo與redo不同,不存放在重做日誌檔案中,而是存放在資料庫內部的一個特殊段中,被稱為undo段(undo segment)。undo段位

於共享表空間當中。

  undo並不是把資料庫物理的恢復到以前的樣子,undo是一個邏輯過程,例如對於insert就是用一條delete,update會使用一個相反的

update,將修改前的行放回去。

  除了回滾操作,undo的另一個作用就是MVCC,即在InnoDB儲存引擎中MVCC的實現是通過undo來完成。當用戶讀取一行記錄時,

若該記錄已經被其他事務佔用,當前事務可以通過讀取之前的行版本資訊,以此實現非鎖定讀取。

  最後一點,undo也會生成redo資訊,也就是undo log的產生會伴隨著redo log產生,這是因為undo log也需要永續性的保護。

2. undo儲存管理

  InnoDB儲存引擎對undo的管理同樣適用段的方式,但這個段和以前的段有所不同。首先InnoDB有rollback segment,每個回滾段中

記錄了1024個undo log segment,而在每個undo log segment段中進行undo頁的申請。每個undo log segment可以支援一個快照讀事務。

InnoDB 1.1之前只有一個rollback segment,所以只支援1024個同時線上的事務,1.1版本開始支援最大128個rollback segment。1.2版本

開始,可以通過引數來配置rollback segment檔案所在路徑、rollback segment個數等。

需要特別注意的是,事務在undo log segment分配頁並寫入undo log的這個過程同樣需要寫入重做日誌。當事務提交時,InnoDB儲存引擎

會做兩件事情:

  • 將undo log放入列表中,以供之後的purge操作
  • 判斷undo log所在的頁是否可以重用,若可以分配給下個事務使用

事務提交後並不能馬上刪除undo log及undo log所在的頁,因為可能有其他事務需要通過undo log來得到行記錄之前的版本。所以事務提交時將undo log放入一個連結串列中,是否可以最終刪除undo log及undo log所在頁由purge執行緒來判斷。

insert的undo log可以在事務結束時直接刪除,而不需要加入連結串列中等待purge,因為由於事務隔離性的要求,只有本事務可以看到。

delete並沒有真正刪除,只是增加了刪除標記,等待purge時最終刪除。

update更新非主鍵時,增加一條更新的undo log,更新主鍵時,其實分兩步進行,先將原記錄標記為已刪除,之後插入一條新的記錄,因此會對應兩條undo log。

3)purge

如上一小節所述,delete和update操作可能並不直接刪除原有的資料。purge操作用來最終完成delete和update操作。這樣設計是因為InnoDB儲存引擎支援MVCC,所以記錄不能在事務提交時立即進行處理。因為這時其他事務可能正在引用這一行,所以InnoDB儲存引擎需要儲存記錄之前的版本。而是否可以刪除該條記錄通過purge來進行判斷。如果該行記錄已不被任何其他事務引用,那麼就可以進行真正的delete操作。

4)group commit

若事務為非只讀事務,則每次事務提交時需要進行一次fsync操作,以此保證重做日誌都已經寫入磁碟。然而磁碟的fsync效能是有限的,為了提高磁碟的fsync效率,當前資料庫都提供了group commit功能,即以此fsync可以重新整理確保多個事務日誌被寫入檔案。對於InnoDB儲存引擎來說,事務提交時會進行兩個階段的操作:

  1. 修改記憶體中事務對應的資訊,並且將日誌寫入重做日誌緩衝。
  2. 呼叫fsync將確保日誌都從重做日誌緩衝寫入磁碟。

步驟2 相對於步驟1 是一個緩慢的過程,因為儲存引擎要和磁碟打交道。擔當由事務進行這個過程時,其他事務可以進行步驟1 的操作,正在提交的事務完成提交操作後,再次進行步驟2 ,可以建多個事務的重做日誌通過一次fsync重新整理到磁碟,這樣就大大減少了磁碟的壓力,從而提高了資料庫的整體效能,對於寫入更新頻繁的操作,group commit的效果尤為明顯。

在InnoDB1.2之前,開啟binlog之後,InnoDB的group commit會失效,導致效能下降,從而導致效能下降。

MySQL 5.6 採用BLGC(Binary Log Group Commit)來解決這個問題,不但重做日誌進行組提交,binlog也採用了組提交,此外還移除了原先的鎖prepare_commit_mutex,大大提高了整體效能。

多個併發提交的事務在寫redo log或binlog前會被加入到一個佇列中,佇列頭部的事務所在的執行緒稱為leader執行緒,其它事務所在的執行緒稱為follower執行緒:

  • Flush階段:leader執行緒負責為佇列中所有的事務進行寫binlog操作(寫入快取),此時,所有的follower執行緒處於等待狀態,
  • Sync階段:leader執行緒呼叫一次fsync操作,將binlog持久化
  • Commit階段:通知follower執行緒可以繼續往下執行(通知InnoDB把redo log刷盤)。

可以通過

  • binlog_group_commit_sync_delay=N:在等待N 微秒後,進行binlog刷盤操作
  • binlog_group_commit_sync_no_delay_count=N:如果佇列中的事務數達到N個,就忽視binlog_group_commit_sync_delay的設定,直接開始刷盤

來進行配置。

事務的隔離級別

SQL標準定義的四個隔離級別

Read Uncommitted

Read Committed

Repeartable Read

Serializable

InnoDB預設的隔離級別是Repeatable Read,在RR隔離級別下,InnoDB使用Next-Key Lock來避免幻讀。因此,InnoDB的RR級別其實已經實現了SQL標準Serializable級別的要求。

在RC隔離級別下,InnoDB不會使用Gap Lock鎖演算法。