MySQL8.0.12 · 引擎特性 · LOB Partial Update優化
在之前,筆者介紹過InnoDB對於lob列的更新優化,即允許對lob型別的列資料進行部分更新。由於undo log page本身的限制(例如無法儲存過長的資料),對於大列更新,舊版本被留在資料檔案中,在MVCC讀時,直接從中讀舊版本即可。然而對於超長lob列資料,標記刪除舊版本再插入完整新資料的開銷太大了,尤其是對於json列,通常我們只需要修改其中極少部分的資料。 為了解決這個問題,InnoDB在8.0版本中實現了partial update的概念,將更新的範圍縮小到page單位,並對lob Page輔助以索引,每個索引項可以維持一個lob page的多個版本(For MVCC)
ofollow,noindex" target="_blank">WL#11328 認為可以對部分更新操作做進一步的優化, 舉個簡單的例子,一個Page內可能只修改了幾十個位元組,卻需要建立一個新的page,這依然會產生不少的開銷,因此在SQL/">MySQL8.0.12中,對這部分邏輯進行了進一步的優化:當更新少於某個閾值時,採用Undo來記錄老的lob資料修改。在需要讀資料時,將這部分修改apply到lob列中。根據官方部落格中的測試,最多帶來了接近三倍的TPS提升,還是相當理想的。
本文主要記錄下涉及到的相關程式碼, 基於MySQL8.0.12。
update
計算更新的位元組數
MySQL Server層實際上已經記錄了Lob diff,對欄位的修改產生的diff維護在 Binary_diff_vector
中,vector中每個元素型別為 Binary_diff
,代表對列上的一部分的修改。對一列的更新可能產生多個binary diff。
InnoDB據此資訊,去定位到對應的lob資料,InnoDB當前hardcode了一個值LOB_SMALL_CHANGE_THRESHOLD,預設為100位元組,當更新的位元組數( upd_t::get_total_modified_bytes()
)小於這個閾值時,走新的邏輯,否則走之前的邏輯(產生一個新的lob page,並遞增版本)
寫undo
由於在undo中記錄的是部分更新,而不是全部Lob資料,undo log的格式需要做一些改動(這意味著升級到8.0.12之後將無法降級到之前的版本), 主要如下:
- 增加一個flag TRX_UNDO_MODIFY_BLOB, 表示Undo log支援 lob partial update。
- 新擴充套件一個byte,用於未來使用
- 將Binary diffz中儲存的老資料(以及對應lob index entry資訊)記錄入undo log
一個典型的Undo log包含(取自官方部落格):
ref: trx_undo_page_report_modify
新的修改在記錄update vector這裡做了擴充套件,下圖取自官方部落格:
入口函式: trx_undo_report_blob_update
lob::get_affected_index_entries
更新記錄
在寫完undo之後,需要去更新索引記錄,對於Lob列,呼叫函式 lob::update
replace_inline() lob::replace()
相關堆疊
lob::update() |--> replace() |--> first_page_t::replace_inline() |--> data_page_t::replace_inline()
Read
根據worklog的描述,新的多去LOB多版本的邏輯變成了如下 (quoted from wl#11328):
1. Let clust_rec point to the latest clustered index record. 2. Using rollptr obtain the undo log record. 3. Construct the update vector from undo log record. Save the update vector (in a queue) related to BLOBs for later use. 4. Using clust_rec and update vector, build older version of clustered index record. 5. Let clust_rec point to this version of clustered index record. 6. Check if clust_rec is the version needed. If yes, goto (7), otherwise goto (2). 7. Now fetch the BLOBs for clust_rec. Apply the update vectors matching the LOB version from the queue.
簡而言之,主要是兩個步驟:
- 從Undo log裡讀取binary diff資訊,併產生update vector
trx_undo_prev_version_build |-->trx_undo_update_rec_get_update |-->trx_undo_read_blob_update
這一步會將讀到的資料存到一個 lob::undo_vers_t
中
- 如果獲得了正確的版本,在返回資料前,將update vector 應用到獲得的記錄中
row_sel_store_mysql_field_func |-->lob::undo_vers_t::apply() |-->lob::undo_seq_t::apply() |-->lob::undo_data_t::apply()