日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

mysql update column_MySQL8.0 新特性:Partial Update of LOB Column

發布時間:2024/8/23 数据库 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 mysql update column_MySQL8.0 新特性:Partial Update of LOB Column 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

摘要: MySQL8.0對json進行了比較完善的支持, 我們知道json具有比較特殊的存儲格式,通常存在多個key value鍵值對,對于類似更新操作通常不會更新整個json列,而是某些鍵值。 對于某些復雜的應用,json列的數據可能會變的非常龐大,這時候一個突出的問題是:innodb并不識別json類型,對它而言這些存儲統一都是LOB類型,而在之前的版本中Innodb處理LOB更新的方式是標記刪除舊記錄,并插入新記錄,顯然這會帶來一些存儲上的開銷(盡管Purge線程會去后臺清理),而寫入的redo log和Binlog的量也會偏高,對于超大列,可能會嚴重影響到性能。

MySQL8.0對json進行了比較完善的支持, 我們知道json具有比較特殊的存儲格式,通常存在多個key value鍵值對,對于類似更新操作通常不會更新整個json列,而是某些鍵值。

對于某些復雜的應用,json列的數據可能會變的非常龐大,這時候一個突出的問題是:innodb并不識別json類型,對它而言這些存儲統一都是LOB類型,而在之前的版本中Innodb處理LOB更新的方式是標記刪除舊記錄,并插入新記錄,顯然這會帶來一些存儲上的開銷(盡管Purge線程會去后臺清理),而寫入的redo log和Binlog的量也會偏高,對于超大列,可能會嚴重影響到性能。為了解決這個問題,MySQL8.0引入了LOB列部分更新的策略。

本文僅僅是筆者在理解該特性時做的一些簡單的筆記,,記錄的主要目的是用于以后如果涉及到相關的工作可以快速展開,因此比較凌亂

目前partial update需要通過JSON_SET, 或者JSON_REPLACE等特定接口來進行json列的更新,并且不是所有的更新都能夠滿足條件:

沒有增加新的元素

空間足夠大,可以容納替換的新值

但類似數據長度(10 =>更新成7=>更新成9)是允許的

下面以json_set更新json列為例來看看相關的關鍵堆棧

檢查是否支持partial update

如上所述,需要指定的json函數接口才能進行partial update

mysql_execute_command

|--> Sql_cmd_dml::execute

|--> Sql_cmd_dml::prepare

|--> Sql_cmd_update::prepare_inner

|---> prepare_partial_update

|-->Item_json_func::supports_partial_update

這里只是做預檢查,對于json列的更新如果全部是通過json_set/replace/remove進行的,則將其標記為候選partial update的列(TABLE::mark_column_for_partial_update), 存儲在bitmap結構TABLE::m_partial_update_columns

設置partial update

入口函數:TABLE::setup_partial_update()

在滿足某些條件時,需要設置logical diff(用于記錄partial update列的binlog,降低binlog存儲開銷):

binlog_row_value_options設置為"partial_json"

binlog 打開

log_bin_use_v1_row_events關閉

使用row format

然后創建Partial_update_info對象(Table::m_partial_update_info), 用于存儲partial update執行過程中的狀態

Table::m_enabled_logical_diff_columns

TABLE::m_binary_diff_vectors

TABLE::m_logical_diff_vectors

創建更新向量

當讀入一行記錄后,就需要根據sql語句來構建后鏡像,而對于partial update所涉及的json列,會做特殊處理:

Sql_cmd_update::update_single_table

|--> fill_record_n_invoke_before_triggers

|-->fill_record

|--> Item::save_in_field

|--> Item_func::save_possibly_as_json

|--> Item_func_json_set_replace::val_json

|--> Json_wrapper::attempt_binary_update

|--> json_binary::Value::update_in_shadow

|--> TABLE::add_binary_diff

json_wrapper::attempt_binary_update : 做必要的數據類型檢查(是否符合partial update的條件)后,計算需要的空間,檢查是否有足夠的空閑空間Value::has_space()來替換成新值。

Value::update_in_shadow: 進一步將變化的數據存儲到binary diff對象中(TABLE::add_binary_diff),每個Binary_diff對象包含了要修改對象的偏移量,長度以及一個指向新數據的const指針

如下例,摘自函數Value::update_in_shadow的注釋,這里提取出來,以便于理解json binary的格式,以及如何產生Binary Diff

創建測試表:

root@test 10:00:45>create table t (a int primary key, b json);

Query OK, 0 rows affected (0.02 sec)

root@test 10:01:06>insert into t values (1, '[ "abc", "def" ]');

Query OK, 1 row affected (0.07 sec)

json數據的存儲格式如下:

0x02 - type: small JSON array

0x02 - number of elements (low byte)

0x00 - number of elements (high byte)

0x12 - number of bytes (low byte)

0x00 - number of bytes (high byte)

0x0C - type of element 0 (string)

0x0A - offset of element 0 (low byte)

0x00 - offset of element 0 (high byte)

0x0C - type of element 1 (string)

0x0E - offset of element 1 (low byte)

0x00 - offset of element 1 (high byte)

0x03 - length of element 0

'a'

'b' - content of element 0

'c'

0x03 - length of element 1

'd'

'e' - content of element 1

'f'

更新json列的'abc'為'XY', 則空出一個字節出來:

root@test 10:01:39>UPDATE t SET b = JSON_SET(b, '$[0]', 'XY');

Query OK, 1 row affected (0.01 sec)

Rows matched: 1 Changed: 1 Warnings: 0

此時的存儲格式為:

0x02 - type: small JSON array

0x02 - number of elements (low byte)

0x00 - number of elements (high byte)

0x12 - number of bytes (low byte)

0x00 - number of bytes (high byte)

0x0C - type of element 0 (string)

0x0A - offset of element 0 (low byte)

0x00 - offset of element 0 (high byte)

0x0C - type of element 1 (string)

0x0E - offset of element 1 (low byte)

0x00 - offset of element 1 (high byte)

CHANGED 0x02 - length of element 0

CHANGED 'X'

CHANGED 'Y' - content of element 0

(free) 'c'

0x03 - length of element 1

'd'

'e' - content of element 1

'f'

此處只影響到一個element,因此 只有一個binary diff

再執行更新:

UPDATE t SET j = JSON_SET(j, '$[1]', 'XYZW')

第二個element從3個字節更新成4個字節,顯然原地沒有足夠的空間,但可以利用其一個element的剩余空間

0x02 - type: small JSON array

0x02 - number of elements (low byte)

0x00 - number of elements (high byte)

0x12 - number of bytes (low byte)

0x00 - number of bytes (high byte)

0x0C - type of element 0 (string)

0x0A - offset of element 0 (low byte)

0x00 - offset of element 0 (high byte)

0x0C - type of element 1 (string)

CHANGED 0x0D - offset of element 1 (low byte)

0x00 - offset of element 1 (high byte)

0x02 - length of element 0

'X' - content of element 0

'Y' - content of element 0

CHANGED 0x04 - length of element 1

CHANGED 'X'

CHANGED 'Y'

CHANGED 'Z' - content of element 1

CHANGED 'W'

這里會產生兩個binary diff,一個更新offset, 一個更新數據

我們再執行一條update,將字符串修改成整數,這種情況下,原來存儲字符串offset的位置被更改成了整數,而原來字符串占用的空間變成Unused狀態。這里只

UPDATE t SET b= JSON_SET(b, '$[1]', 456)

0x02 - type: small JSON array

0x02 - number of elements (low byte)

0x00 - number of elements (high byte)

0x12 - number of bytes (low byte)

0x00 - number of bytes (high byte)

0x0C - type of element 0 (string)

0x0A - offset of element 0 (low byte)

0x00 - offset of element 0 (high byte)

CHANGED 0x05 - type of element 1 (int16)

CHANGED 0xC8 - value of element 1 (low byte)

CHANGED 0x01 - value of element 1 (high byte)

0x02 - length of element 0

'X' - content of element 0

'Y' - content of element 0

(free) 0x04 - length of element 1

(free) 'X'

(free) 'Y'

(free) 'Z' - content of element 1

(free) 'W

類型從string變成int16,使用之前offset的字段記錄int值,而原來string的空間則變成空閑狀態, 這里產生一個binary diff。

我們再來看看另外一個相似的函數Value::remove_in_shadow,即通過json_remove從列上移除一個字段,以下樣例同樣摘自函數的注釋:

json列的值為

{ "a": "x", "b": "y", "c": "z" }

存儲格式:

0x00 - type: JSONB_TYPE_SMALL_OBJECT

0x03 - number of elements (low byte)

0x00 - number of elements (high byte)

0x22 - number of bytes (low byte)

0x00 - number of bytes (high byte)

0x19 - offset of key "a" (high byte)

0x00 - offset of key "a" (low byte)

0x01 - length of key "a" (high byte)

0x00 - length of key "a" (low byte)

0x1a - offset of key "b" (high byte)

0x00 - offset of key "b" (low byte)

0x01 - length of key "b" (high byte)

0x00 - length of key "b" (low byte)

0x1b - offset of key "c" (high byte)

0x00 - offset of key "c" (low byte)

0x01 - length of key "c" (high byte)

0x00 - length of key "c" (low byte)

0x0c - type of value "a": JSONB_TYPE_STRING

0x1c - offset of value "a" (high byte)

0x00 - offset of value "a" (low byte)

0x0c - type of value "b": JSONB_TYPE_STRING

0x1e - offset of value "b" (high byte)

0x00 - offset of value "b" (low byte)

0x0c - type of value "c": JSONB_TYPE_STRING

0x20 - offset of value "c" (high byte)

0x00 - offset of value "c" (low byte)

0x61 - first key ('a')

0x62 - second key ('b')

0x63 - third key ('c')

0x01 - length of value "a"

0x78 - contents of value "a" ('x')

0x01 - length of value "b"

0x79 - contents of value "b" ('y')

0x01 - length of value "c"

0x7a - contents of value "c" ('z')

將其中的成員$.b移除掉:

UPDATE t SET j = JSON_REMOVE(j, '$.b');

格式為:

0x00 - type: JSONB_TYPE_SMALL_OBJECT

CHANGED 0x02 - number of elements (low byte)

0x00 - number of elements (high byte)

0x22 - number of bytes (low byte)

0x00 - number of bytes (high byte)

0x19 - offset of key "a" (high byte)

0x00 - offset of key "a" (low byte)

0x01 - length of key "a" (high byte)

0x00 - length of key "a" (low byte)

CHANGED 0x1b - offset of key "c" (high byte)

CHANGED 0x00 - offset of key "c" (low byte)

CHANGED 0x01 - length of key "c" (high byte)

CHANGED 0x00 - length of key "c" (low byte)

CHANGED 0x0c - type of value "a": JSONB_TYPE_STRING

CHANGED 0x1c - offset of value "a" (high byte)

CHANGED 0x00 - offset of value "a" (low byte)

CHANGED 0x0c - type of value "c": JSONB_TYPE_STRING

CHANGED 0x20 - offset of value "c" (high byte)

CHANGED 0x00 - offset of value "c" (low byte)

(free) 0x00

(free) 0x0c

(free) 0x1e

(free) 0x00

(free) 0x0c

(free) 0x20

(free) 0x00

0x61 - first key ('a')

(free) 0x62

0x63 - third key ('c')

0x01 - length of value "a"

0x78 - contents of value "a" ('x')

(free) 0x01

(free) 0x79

0x01 - length of value "c"

0x7a - contents of value "c" ('z')

這里會產生兩個binary diff,一個用于更新element個數,一個用于更新offset。

從上面的例子可以看到,每個Binary diff表示了一段連續更新的數據,有幾段連續更新的數據,就有幾個binary diff。 binary diff存儲到TABLE::m_partial_update_info->m_binary_diff_vectors中,

寫入logical diff

logical diff 主要用于優化寫binlog

Sql_cmd_update::update_single_table

|--> fill_record_n_invoke_before_triggers

|-->fill_record

|--> Item::save_in_field

|--> Item_func::save_possibly_as_json

|--> Item_func_json_set_replace::val_json

|-->TABLE::add_logical_diff

新的LOB存儲格式

相關代碼:

storage/innobase/lob/*, 所有的類和函數定義在namesapce lob下面

從上面的分析可以看到,Server層已經提供了所有修改的偏移量,新數據長度,已經判斷好了數據能夠原地存儲,對于innodb,則須要利用這些信息來實現partial update 。

在展開這個問題之前,我們先來看下innodb針對json列的新格式。從代碼中可以看到,為了實現partial update, innodb增加了幾種新的數據頁格式:

壓縮表:

FIL_PAGE_TYPE_ZLOB_FIRST

FIL_PAGE_TYPE_ZLOB_DATA

FIL_PAGE_TYPE_ZLOB_INDEX

FIL_PAGE_TYPE_ZLOB_FRAG

FIL_PAGE_TYPE_ZLOB_FRAG_ENTRY

普通表:

FIL_PAGE_TYPE_LOB_INDEX

FIL_PAGE_TYPE_LOB_DATA

FIL_PAGE_TYPE_LOB_FIRST

我們知道,傳統的LOB列通常是在聚集索引記錄內留一個外部存儲指針,指向lob存儲的page,如果一個page存儲不下,就會產生lob page鏈表。而新的存儲格式,則引入了lob index的概念,也就是為所有的lob page建立索引,格式如下:

ref pointer in cluster record

-------

|

FIL_PAGE_TYPE_LOG_FIRST

|

FIL_PAGE_TYPE_LOB_INDEX -----------> FIL_PAGE_TYPE_LOB_DATA

|

FIL_PAGE_TYPE_LOB_INDEX -------------> FIL_PAGE_TYPE_LOB_DATA

|

... ....

Note: 本文只討論非壓縮表的場景, 對于壓縮表引入了更加復雜的數據類型,以后有空再在本文補上。

ref Pointer格式如下(和之前相比,增加了版本號)

字段 字節數 描述

BTR_EXTERN_SPACE_ID 4 space id

BTR_EXTERN_PAGE_NO 4 第一個 lob page的no

BTR_EXTERN_OFFSET/BTR_EXTERN_VERSION 4 新的格式記錄version號

第一個FIL_PAGE_TYPE_LOG_FIRST頁面的操作定義在 lob::first_page_t類中格式如下(參考文件: include/lob0first.h lob/lob0first.cc):

字段 字節數 描述

OFFSET_VERSION 1 表示lob的版本號,當前為0,用于以后lob格式改變做版本區分

OFFSET_FLAGS 1 目前只使用第一個bit,被設置時表示無法做partial update, 用于通知purge線程某個更新操作產生的老版本LOB可以被完全釋放掉

OFFSET_LOB_VERSION 4 每個lob page都有個版本號,初始為1,每次更新后遞增

OFFSET_LAST_TRX_ID 6

OFFSET_LAST_UNDO_NO 4

OFFSET_DATA_LEN 4 存儲在該page上的數據長度

OFFSET_TRX_ID 6 創建存儲在該page上的事務id

OFFSET_INDEX_LIST 16 維護lob page鏈表

OFFSET_INDEX_FREE_NODES 16 維護空閑節點

LOB_PAGE_DATA 存儲數據的起始位置,注意第一個page同時包含了lob index 和lob data,但在第一個lob page中只包含了10個lob index記錄,每個lob index大小為60字節

除了第一個lob page外,其他所有的lob page都是通過lob index記錄來指向的,lob index之間鏈接成鏈表,每個index entry指向一個lob page,

普通Lob Page的格式如下

字段 字節數 描述

OFFSET_VERSION 1 lob data version,當前為0

OFFSET_DATA_LEN 4 數據長度

OFFSET_TRX_ID 6 創建該lob page的事務Id

LOB_PAGE_DATA lob data開始的位置

lob index entry的大小為60字節,主要包含如下內容(include/lob0index.h lob/lob0index.cc):

偏移量 字節數 描述

OFFSET_PREV 6 Pointer to the previous index entry

OFFSET_NEXT 6 Pointer to the next index entry

OFFSET_VERSIONS 16 Pointer to the list of old versions for this index entry

OFFSET_TRXID 6 The creator transaction identifier.

OFFSET_TRXID_MODIFIER 6 The modifier transaction identifier

OFFSET_TRX_UNDO_NO 4 the undo number of creator transaction.

OFFSET_TRX_UNDO_NO_MODIFIER 4 The undo number of modifier transaction.

OFFSET_PAGE_NO 4 The page number of LOB data page

OFFSET_DATA_LEN 4 The amount of LOB data it contains in bytes.

OFFSET_LOB_VERSION 4 The LOB version number to which this index entry belongs.

從index entry的記錄格式我們可以看到 兩個關鍵信息:

對lob page的修改會產生新的lob page(“lob::replace()”) 和新的lob index entry

lob page no及其數據長度,據此我們可以根據修改的數據在json column里的offset,通過lob index快速的定位到其所在的lob page

每個lob page的版本號: 為了實現mvcc多版本,用戶線程先從undo log中找到對應版本的clust record,找出其中存儲的版本號v1,然后在掃描lob index時,如index entry中記錄的版本號<= v1,則是可見的,如果> v1, 那么就需要根據OFFSET_VERSIONS鏈表,找到對應版本的index entry,并

根據這個老的Index entry找到對應的lob page, 如下所示:

EXTERN REF (v2)

|

LOB IDX ENTRY (v1)

|

LOB IDX ENTRY(v2) -----> LOB IDX ENTRY(v1)

|

LOG IDX ...(v1)

多版本讀判斷參考函數 'lob::read'

lob更新lob::update: 根據binary diff,依次replace

Note: 不是所有的lob數據都需要partial update, 額外的lob index同樣會帶來存儲開銷,因此定義了一個threshold(ref_t::LOB_BIG_THRESHOLD_SIZE),超過2個page才去做partial update; 另外row_format也要確保lob列不存儲列前綴到clust index ( ref btr_store_big_rec_extern_fields)

寫入binlog

在更新完一行后,對應的變更需要打包到線程的cache中(THD::binlog_write_row() --> pack_row()), 這時候要對partial update進行特殊處理,需要設置特定選項:

binlog_row_image = MINIMAL;

binlog_row_value_options=PARTIAL_JSON

如上例第一個update產生的binlog如下:

UPDATE t SET b = JSON_SET(b, '$[0]', 'XY');

binlog:

'/*!*/;

### UPDATE `test`.`t`

### WHERE

### @1=1 /* INT meta=0 nullable=0 is_null=0 */

### SET

### @2=JSON_REPLACE(@2, '$[0]', 'XY') /* JSON meta=4 nullable=1 is_null=0 */

由于存在主鍵,因此前鏡像只記錄了主鍵值,而后鏡像也只記錄了需要更新的列的內容,對于超大Json列,binlog上的開銷也是極小的,考慮到binlog通常會成為性能瓶頸點,預計這一特性會帶來不錯的吞吐量提升

原文鏈接

總結

以上是生活随笔為你收集整理的mysql update column_MySQL8.0 新特性:Partial Update of LOB Column的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。