mysql從0到0.1系列:SQL是怎麼執行的(下)

1.update/insert 語句的執行流程

1.1 流程

1.1.1 insert 流程

在上一篇咱們已經說過,一條SELECT語句執行須要的幾個步驟,實際上update/insert語句也差很少,不一樣的是多出了redo logbin log兩個重要的部分。 這裏再寫一下整個流程:html

  1. 建立鏈接

經過TCP/IP鏈接Mysqlmysql

  1. 解析器

這裏mysql經過詞法和語法解析會知道這裏是update語句。sql

  1. 優化器

生成相應的執行計劃,選擇最優的執行計劃數據庫

  1. 執行器

在這一步會去open table,若是該table上有MDL寫鎖,則等待。若是沒有,則加在該表上加MDL讀鎖。緩存

ps: 這裏涉及到一個參數open_tables若是open_tables接近table_cache而且Opened_tables在增長,就表明mysql打開新表的時候會從磁盤讀取,沒法從緩存拿,也就是會重複的打開.frm文件markdown

以上是mysql server層的執行步驟,咱們主要關注引擎層作的事情。併發

  1. 獲取鎖信息

經過元數據信息(open_table的時候獲取到的),去lock info裏查出是否會有相關的鎖信息,並把這條insert語句鎖信息寫入到lock info裏oop

innodb1.png 2. 判斷數據頁是否在innodb buffer中 不在的話須要從磁盤中加載到innodb buffer中性能

  1. 分配undo段,記錄undo log優化

  2. 記錄undo log 產生的redo log

這裏產生的redo log 其實就是undo segment的改動

  1. 對inndb buffer中的數據頁進行更新

這裏是直接在內存中更新而不是磁盤。

  1. 記錄redolog

inndb buffer中的修改記錄到redo log buffer裏

  1. 插入binlog cache

修改的信息,會按照event的格式,記錄到binlog cache中。

  1. 插入change buffer

只有涉及到非聚簇索引的惟一索引而且數據頁不在innodb_buffer中才會插入change buffer

  1. 二階段提交過程

此時在sql中能夠當作已經commit

  • prepare

將binlog_cache裏的進行flush以及sync操做

  • commit

因爲以前該事務產生的redo log已經sync到磁盤了(這裏根據innodb_flush_log_at_trx_commit 設置的不一樣狀況會不同)。因此這步只是在redo log裏標記commit。

以上就是整個插入流程,這裏僅在mysql 5.6以及innodb引擎的環境下

1.1.2 update 流程

這裏update 的流程和insert流程基本同樣,暫不贅述,若是有疑問能夠留言討論。

1.1.3 總體流程圖

innodb-插入流程 [2].png

2 change buffer

從1.0.x 的版本innodb 引入了change buffer 在以前的版本也稱爲insert buffer,change buffer 能夠看做insert buffer 的升級。

2.1 做用

change buffer 最主要的功能就是加速非聚簇索引的操做,若是咱們以前對數據庫有了解就知道,其實影響數據庫性能的很關鍵的一個因素就是磁盤的隨機讀,而mysql中大部分的優化其實都是在減小磁盤的隨機讀,這裏的change buffer 也是這樣。以一個插入過程爲例,涉及到兩個部分,聚簇索引的插入和非聚簇索引的插入

  • 聚簇索引

這裏通常來講若是聚簇索引是自增主鍵的話,這裏插入就是一個順序寫,就不會涉及到磁盤的隨機讀,所以很快的。固然若是你這裏不設置爲自增主鍵(UUID)就會產生磁盤的隨機讀,指定值也是。

  • 非彙集索引

非聚簇索引這裏也涉及了兩種狀況,惟一索引和非惟一索引。

  • 惟一索引

惟一索引會回表判斷是否衝突,因此這裏必定是一個隨機讀的操做。

  • 非惟一索引

因爲數據頁的存放實際上是按主鍵順序存放的,因此當我插入非惟一索引的時候,其實也會產生隨機讀。不過有時候非惟一索引也能夠是順序的。

在某些狀況下,輔助索引的插入依然是順序的,或者說是比較順序的,好比用戶購買表中的時間字段。在一般狀況下,用戶購買時間是一個輔助索引,用來根據時間條件進行查詢。可是在插入時倒是根據時間的遞增而插入的,所以插入也是"較爲"順序的。——《MySQL 技術內幕 innodb 引擎》

這裏咱們不妨思考一下,哪一個步驟產生的隨機讀能夠被優化呢,惟一索引因爲須要判斷是否衝突須要回表,好像並不能優化,而非惟一索引只是由於B+樹的特性致使會產生隨機讀,這裏也許能夠優化。 change buffer 正是這樣作的,對於非惟一索引的操做實際上是先放在change buffer中而不是直接進入數據頁。這裏的過程是這樣的。判斷目標頁是否在innodb buffer中,若是在,直接操做innodb buffer,不然就直接放在change buffer 中,這樣就不須要從磁盤隨機讀頁數據到innodb buffer中。在下次查詢須要訪問這個數據頁的時候,將數據頁讀入內存,而後執行 change buffer 中與這個頁有關的操做。

2.2 原理

首先,咱們須要知道的是,change buffer 的結構其實是一顆B+樹,而且是存放在共享表空間ibdata1中,這裏有個容易混淆的概念,change buffer 也是一個數據頁,因此也會被加載到inndb_buffer中,而且持久化在ibdata1,因此會存在一部分數據在inndb_buffer中的狀況,同時這個change buffer頁的改動記錄在redo log裏。

2.2.1 結構

因爲change buffer 是一個B+樹的結構,因此這裏也區分了葉子節點和非葉子節點。

  • 子節點

由space,marker,offset三個字段組成

  1. space

記錄的是表空間id,每一個表都會有一個惟一的space。

  1. marker

保留字段,兼容老版本的insert buffer。

  1. offset

數據因此在頁的偏移量。

change_buffer1.png

  • 葉子節點

除了非葉子節點的三個字段以外,還多了metadata,以及實際插入的字段。

  1. metadata

保存了三個字段分別是:

IBUF_REC_OFFSET_COUNTER:數器,用來排序記錄,以進入insert buffer的順序

IBUF_REC_OFFSET_TYPE:操做類型(ibuf_op_t)

IBUF_REC_OFFSET_FLAGS:標誌位,當前只有IBUF_REC_COMPACT

change_buffer2.png

2.2.2 merge過程

那麼經過上面的內容咱們能夠知道,change buffer,其實就至關於一個緩衝池的概念,那麼既然是緩衝池就有一個向數據頁merge的過程,也就是合併到真正的非聚簇索引中來。 通常來講,觸發merge過程主要有這幾種狀況:

  1. 加載非聚簇索引頁到innodb buffer中
  2. Master Thread定時merge
  3. buffer bitmap 判斷輔助索引頁空間不足1/32

其中第三點涉及到一個buffer bitmap的概念,這是用來標記輔助索引頁空間的。建議你們能夠直接去看看《MySQL 技術內幕 innodb 引擎》,這裏不贅述了。

2.3 一些問題

2.3.1 如何設置 change buffer

change buffer 的大小,能夠經過參數 innodb_change_buffer_max_size 來動態設置。這個參數設置爲50的時候,表示 change buffer 的大小最多隻能佔用innodb buffer的50%。

2.3.2 change buffer的應用場景

因爲讀操做會觸發change buffer的merge過程,因此當讀不少的時候,change buffer的效果不會很明顯。而且會產生change buffer 的維護代價。所以,對於寫多讀少的業務來講,頁面在寫完之後立刻被訪問到的機率比較小,此時 change buffer 的使用效果最好。這種業務模型常見的就是帳單類、日誌類的系統。

3 redo log

前面咱們屢次提到redo log,能夠發現,基本上因此會寫磁盤的操做都會先寫redo log,這是由於,若是每一次的更新操做都須要寫進磁盤,而後磁盤也要找到對應的那條記錄,而後再更新,整個過程 IO 成本、查找成本都很高。

IO成本就是尋址時間和上線文切換所須要的時間,最主要是用戶態和內核態的上下文切換。咱們知道用戶態是沒法直接訪問磁盤等硬件上的數據的,只能經過操做系統去調內核態的接口,用內核態的線程去訪問。 這裏的上下文切換指的是同進程的線程上下文切換,所謂上下文就是線程運行須要的環境信息。 首先,用戶態線程須要一些中間計算結果保存CPU寄存器,保存CPU指令的地址到程序計數器(執行順序保證),還要保存棧的信息等一些線程私有的信息。 而後切換到內核態的線程執行,就須要把線程的私有信息從寄存器,程序計數器裏讀出來,而後執行讀磁盤上的數據。讀完後返回,又要把線程的信息寫進寄存器和程序計數器。 切換到用戶態後,用戶態線程又要讀以前保存的線程執行的環境信息出來,恢復執行。這個過程主要是消耗時間資源。

3.1 原理

redo log由兩部分組成:

  1. redo log buffer
  2. redo log file

其中redo log buffer 是在內存中的,若是redo log 在沒有寫入file中的時候斷電,其實redo log buffer的數據就會丟失。可是這種狀況其實極少發生,redo log buffer是接近實時地寫入磁盤,當會話發出commit語句時,會實時執行redo log buffer寫操做。(注意,這裏並非說把更新的數據寫入磁盤了,而是把redo log 寫入了)

當有一條記錄須要更新的時候,InnoDB 引擎就會先把記錄寫到 redo log裏面,並更新內存,這個時候更新就算完成了。同時,InnoDB 引擎會在適當的時候,將這個操做記錄更新到磁盤裏面。

因爲redo log是固定大小的,因此實際上在redo log 快要寫滿時也會對前面的數據進行刷盤。而且 redo log 是循環寫的,因此是一個向後寫入,向前刷盤的過程。這個也稱做checkpoint 技術。

3.2 配置

innodb_flush_log_at_trx_commit={0|1|2} , 指定什麼時候將事務日誌刷到磁盤,默認爲1。

  • 0表示每秒將"redo log buffer"同步到"os buffer"且從"os buffer"刷到磁盤日誌文件中。
  • 1表示每事務提交都將"redo log buffer"同步到"os buffer"且從"os buffer"刷到磁盤日誌文件中。
  • 2表示每事務提交都將"redo log buffer"同步到"os buffer"但每秒才從"os buffer"刷到磁盤日誌文件中。

3.3 WAL

WAL 的全稱是 Write-Ahead Logging,它的關鍵點就是先寫日誌,再寫磁盤。不只僅是MySQL,不少涉及到寫磁盤的系統都會使用這一技術包括,zookeeper,ES等。

3.3.1 WAL的優勢

  1. WAL 的優勢讀和寫能夠徹底地併發執行,不會互相阻塞(可是寫之間仍然不能併發)。
  2. WAL 在大多數狀況下,擁有更好的性能(由於無需每次寫入時都要寫兩個文件)。
  3. 磁盤 I/O 行爲更容易被預測。使用更少的 fsync()操做,減小系統脆弱的問題。提高性能

4 undo log

上面咱們說到的redo log 能夠稱爲重作日誌,由於redo log 是記錄數據頁的改動,因此能夠根據redo log 重作數據頁。而undo log則是回滾日誌,當一個事務失敗以後,就能夠經過undo log 進行回滾。在事務篇中再詳細說。

5 bin log

除了redo log 以外,mysql還有一個比較重要的日誌,bin log(歸檔日誌)。 redo log與bin log的不一樣點:

  1. redo log 是innodb特有的日誌,而bin log 是mysql server層的日誌。
  2. redo log 記錄的是數據頁的修改,而bin log 記錄的是SQL 的邏輯。
  3. redo log 是循環寫,而bin log 是追加寫。

binlog有兩種模式,statement 格式的話是記sql語句, row格式會記錄行的內容,記兩條,更新前和更新後都有。 

6 二階段提交

兩階段提交主要是爲了保證crash-safe,若是不使用「兩階段提交」,那麼數據庫的狀態就有可能和用它的日誌恢復出來的庫的狀態不一致。


我的博客

西西弗的石頭

做者水平有限,如有錯誤遺漏,請指出。

參考文章

1.詳細分析MySQL事務日誌(redo log和undo log)

2.MySQL 45講

參考書籍

  1. 《MySQL 技術內幕 innodb存儲引擎》
相關文章
相關標籤/搜索